Merge remote-tracking branch 'origin/master' into pr-5069
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
- River (Mapping mode, Tags, Focused window name)
|
- River (Mapping mode, Tags, Focused window name)
|
||||||
- Hyprland (Window Icons, Workspaces, Focused window name)
|
- Hyprland (Window Icons, Workspaces, Focused window name)
|
||||||
- Niri (Workspaces, Focused window name, Language)
|
- Niri (Workspaces, Focused window name, Language)
|
||||||
|
- Mango (Workspaces, Focused window name, Language, Keymode)
|
||||||
- DWL (Tags, Focused window name) [requires dwl ipc patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc)
|
- DWL (Tags, Focused window name) [requires dwl ipc patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc)
|
||||||
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
|
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
|
||||||
- Local time
|
- Local time
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ class AIconLabel : public ALabel {
|
|||||||
bool enable_click = false, bool enable_scroll = false);
|
bool enable_click = false, bool enable_scroll = false);
|
||||||
virtual ~AIconLabel() = default;
|
virtual ~AIconLabel() = default;
|
||||||
auto update() -> void override;
|
auto update() -> void override;
|
||||||
|
static std::tuple<std::string, std::string> extractIcon(const std::string& input);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Gtk::Image image_;
|
Gtk::Image image_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
|
unsigned app_icon_size_{24};
|
||||||
|
|
||||||
|
bool label_contains_icon{false};
|
||||||
|
|
||||||
bool iconEnabled() const;
|
bool iconEnabled() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ALabel : public AModule {
|
|||||||
bool setTooltipMarkup(const Glib::ustring& markup);
|
bool setTooltipMarkup(const Glib::ustring& markup);
|
||||||
|
|
||||||
bool handleToggle(GdkEventButton* const& e) override;
|
bool handleToggle(GdkEventButton* const& e) override;
|
||||||
|
void copyToClipboard(const std::string&);
|
||||||
virtual std::string getState(uint8_t value, bool lesser = false);
|
virtual std::string getState(uint8_t value, bool lesser = false);
|
||||||
|
|
||||||
std::map<std::string, GtkMenuItem*> submenus_;
|
std::map<std::string, GtkMenuItem*> submenus_;
|
||||||
|
|||||||
+2
-1
@@ -85,7 +85,8 @@ class AModule : public IModule {
|
|||||||
{std::make_pair(9, GdkEventType::GDK_BUTTON_PRESS), "on-click-forward"},
|
{std::make_pair(9, GdkEventType::GDK_BUTTON_PRESS), "on-click-forward"},
|
||||||
{std::make_pair(9, GdkEventType::GDK_BUTTON_RELEASE), "on-click-forward-release"},
|
{std::make_pair(9, GdkEventType::GDK_BUTTON_RELEASE), "on-click-forward-release"},
|
||||||
{std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-forward"},
|
{std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-forward"},
|
||||||
{std::make_pair(9, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-forward"}};
|
{std::make_pair(9, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-forward"},
|
||||||
|
{std::make_pair(10, GdkEventType::GDK_BUTTON_PRESS), "on-click-copy"}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
||||||
class Group : public AModule {
|
class Group : public AModule {
|
||||||
|
sigc::connection reveal_timeout_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Group(const std::string&, const std::string&, const Json::Value&, bool);
|
Group(const std::string&, const std::string&, const Json::Value&, bool);
|
||||||
~Group() override = default;
|
~Group() override = default;
|
||||||
@@ -26,6 +28,7 @@ class Group : public AModule {
|
|||||||
bool is_first_widget = true;
|
bool is_first_widget = true;
|
||||||
bool is_drawer = false;
|
bool is_drawer = false;
|
||||||
bool click_to_reveal = false;
|
bool click_to_reveal = false;
|
||||||
|
int reveal_delay = 0;
|
||||||
std::string add_class_to_drawer_children;
|
std::string add_class_to_drawer_children;
|
||||||
bool handleMouseEnter(GdkEventCrossing* const& ev) override;
|
bool handleMouseEnter(GdkEventCrossing* const& ev) override;
|
||||||
bool handleMouseLeave(GdkEventCrossing* const& ev) override;
|
bool handleMouseLeave(GdkEventCrossing* const& ev) override;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -51,6 +52,11 @@ class Battery : public ALabel {
|
|||||||
bool warnFirstTime_{true};
|
bool warnFirstTime_{true};
|
||||||
bool weightedAverage_{true};
|
bool weightedAverage_{true};
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
|
bool smoothPowerEnable_{false};
|
||||||
|
double time_constant_s_{260.0};
|
||||||
|
double smooth_power_{0.0}; // µW
|
||||||
|
std::chrono::steady_clock::time_point last_t_{std::chrono::steady_clock::now()};
|
||||||
|
std::string old_status_raw_{""};
|
||||||
|
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
util::SleeperThread thread_battery_update_;
|
util::SleeperThread thread_battery_update_;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <sys/statvfs.h>
|
#include <sys/statvfs.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "ALabel.hpp"
|
#include "ALabel.hpp"
|
||||||
#include "util/format.hpp"
|
#include "util/format.hpp"
|
||||||
@@ -19,7 +20,9 @@ class Disk : public ALabel {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
std::string path_;
|
std::string header_;
|
||||||
|
std::vector<std::string> paths_;
|
||||||
|
std::string separator_;
|
||||||
std::string unit_;
|
std::string unit_;
|
||||||
|
|
||||||
float calc_specific_divisor(const std::string& divisor);
|
float calc_specific_divisor(const std::string& divisor);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class Tags : public waybar::AModule {
|
|||||||
void handle_primary_clicked(uint32_t tag);
|
void handle_primary_clicked(uint32_t tag);
|
||||||
bool handle_button_press(GdkEventButton* event_button, uint32_t tag);
|
bool handle_button_press(GdkEventButton* event_button, uint32_t tag);
|
||||||
|
|
||||||
|
void handle_active_output(zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active);
|
||||||
|
|
||||||
struct zdwl_ipc_manager_v2* status_manager_;
|
struct zdwl_ipc_manager_v2* status_manager_;
|
||||||
struct wl_seat* seat_;
|
struct wl_seat* seat_;
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ class Tags : public waybar::AModule {
|
|||||||
const waybar::Bar& bar_;
|
const waybar::Bar& bar_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
std::vector<Gtk::Button> buttons_;
|
std::vector<Gtk::Button> buttons_;
|
||||||
|
bool hide_vacant_;
|
||||||
struct zdwl_ipc_output_v2* output_status_;
|
struct zdwl_ipc_output_v2* output_status_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Window : public AAppIconLabel, public sigc::trackable {
|
|||||||
void handle_layout(const uint32_t layout);
|
void handle_layout(const uint32_t layout);
|
||||||
void handle_title(const char* title);
|
void handle_title(const char* title);
|
||||||
void handle_appid(const char* ppid);
|
void handle_appid(const char* ppid);
|
||||||
|
void handle_active(const uint32_t active);
|
||||||
void handle_layout_symbol(const char* layout_symbol);
|
void handle_layout_symbol(const char* layout_symbol);
|
||||||
void handle_frame();
|
void handle_frame();
|
||||||
|
|
||||||
@@ -30,6 +31,9 @@ class Window : public AAppIconLabel, public sigc::trackable {
|
|||||||
std::string title_;
|
std::string title_;
|
||||||
std::string appid_;
|
std::string appid_;
|
||||||
std::string layout_symbol_;
|
std::string layout_symbol_;
|
||||||
|
bool active_;
|
||||||
|
bool hide_inactive_;
|
||||||
|
bool hide_empty_;
|
||||||
uint32_t layout_;
|
uint32_t layout_;
|
||||||
|
|
||||||
struct zdwl_ipc_output_v2* output_status_;
|
struct zdwl_ipc_output_v2* output_status_;
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class Language : public waybar::ALabel, public EventHandler {
|
|||||||
std::string short_description;
|
std::string short_description;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto removeXkbLayoutCssClass() -> void;
|
||||||
|
auto addXkbLayoutCssClass() -> void;
|
||||||
static auto getLayout(const std::string&) -> Layout;
|
static auto getLayout(const std::string&) -> Layout;
|
||||||
|
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class Workspace {
|
|||||||
|
|
||||||
int m_id;
|
int m_id;
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
|
std::string m_prevNameClass;
|
||||||
std::string m_output;
|
std::string m_output;
|
||||||
uint m_windows;
|
uint m_windows;
|
||||||
bool m_isActive = false;
|
bool m_isActive = false;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
auto allOutputs() const -> bool { return m_allOutputs; }
|
auto allOutputs() const -> bool { return m_allOutputs; }
|
||||||
auto showSpecial() const -> bool { return m_showSpecial; }
|
auto showSpecial() const -> bool { return m_showSpecial; }
|
||||||
auto activeOnly() const -> bool { return m_activeOnly; }
|
auto activeOnly() const -> bool { return m_activeOnly; }
|
||||||
|
auto hideActive() const -> bool { return m_hideActive; }
|
||||||
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
|
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
|
||||||
auto persistentOnly() const -> bool { return m_persistentOnly; }
|
auto persistentOnly() const -> bool { return m_persistentOnly; }
|
||||||
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
|
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
|
||||||
@@ -56,6 +57,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; }
|
auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; }
|
||||||
auto onClickWindow() const -> std::string { return m_onClickWindow; }
|
auto onClickWindow() const -> std::string { return m_onClickWindow; }
|
||||||
auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }
|
auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }
|
||||||
|
auto maxWindows() const -> int { return m_maxWindows; }
|
||||||
|
|
||||||
enum class ActiveWindowPosition { NONE, FIRST, LAST };
|
enum class ActiveWindowPosition { NONE, FIRST, LAST };
|
||||||
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
|
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
|
||||||
@@ -89,6 +91,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
|
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
|
||||||
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;
|
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;
|
||||||
auto populateWindowRewriteConfig(const Json::Value& config) -> void;
|
auto populateWindowRewriteConfig(const Json::Value& config) -> void;
|
||||||
|
auto populateMaxWindowsConfig(const Json::Value& config) -> void;
|
||||||
auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;
|
auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;
|
||||||
|
|
||||||
void registerIpc();
|
void registerIpc();
|
||||||
@@ -146,6 +149,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
bool m_allOutputs = false;
|
bool m_allOutputs = false;
|
||||||
bool m_showSpecial = false;
|
bool m_showSpecial = false;
|
||||||
bool m_activeOnly = false;
|
bool m_activeOnly = false;
|
||||||
|
bool m_hideActive = false;
|
||||||
bool m_specialVisibleOnly = false;
|
bool m_specialVisibleOnly = false;
|
||||||
bool m_persistentOnly = false;
|
bool m_persistentOnly = false;
|
||||||
bool m_moveToMonitor = false;
|
bool m_moveToMonitor = false;
|
||||||
@@ -202,6 +206,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
};
|
};
|
||||||
std::string m_onClickWindow;
|
std::string m_onClickWindow;
|
||||||
std::string m_currentActiveWindowAddress;
|
std::string m_currentActiveWindowAddress;
|
||||||
|
int m_maxWindows = 0;
|
||||||
|
|
||||||
std::vector<std::regex> m_ignoreWorkspaces;
|
std::vector<std::regex> m_ignoreWorkspaces;
|
||||||
std::vector<std::regex> m_ignoreWindows;
|
std::vector<std::regex> m_ignoreWindows;
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ class KeyboardState : public AModule {
|
|||||||
std::string capslock_format_;
|
std::string capslock_format_;
|
||||||
std::string scrolllock_format_;
|
std::string scrolllock_format_;
|
||||||
const std::chrono::seconds interval_;
|
const std::chrono::seconds interval_;
|
||||||
std::string icon_locked_;
|
std::unordered_map<std::string, std::vector<std::string>> key_icon_states_;
|
||||||
std::string icon_unlocked_;
|
|
||||||
std::string devices_path_;
|
std::string devices_path_;
|
||||||
|
|
||||||
struct libinput* libinput_;
|
struct libinput* libinput_;
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// include/modules/mango/backend.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util/json.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
class EventHandler {
|
||||||
|
public:
|
||||||
|
virtual void onEvent(const Json::Value& ev) = 0;
|
||||||
|
virtual ~EventHandler() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IPC {
|
||||||
|
public:
|
||||||
|
static IPC& getInstance();
|
||||||
|
IPC(const IPC&) = delete;
|
||||||
|
IPC& operator=(const IPC&) = delete;
|
||||||
|
|
||||||
|
void registerForIPC(const std::string& ev, EventHandler* handler);
|
||||||
|
void unregisterForIPC(EventHandler* handler);
|
||||||
|
|
||||||
|
static Json::Value send(const Json::Value& request);
|
||||||
|
static void sendAsync(const Json::Value& request);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lockData() { return std::unique_lock<std::mutex>(data_mutex_); }
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Json::Value> getMonitors() const;
|
||||||
|
Json::Value getMonitor(const std::string& name);
|
||||||
|
Json::Value getActiveClientForMonitor(const std::string& name) const;
|
||||||
|
std::string getKeyboardLayout() const;
|
||||||
|
std::string getKeymode() const;
|
||||||
|
std::string getLayoutSymbolForMonitor(const std::string& name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
IPC();
|
||||||
|
~IPC();
|
||||||
|
void startIPC();
|
||||||
|
static int connectToSocket();
|
||||||
|
void parseIPC(const std::string& line);
|
||||||
|
|
||||||
|
void handleMonitorUpdate(const Json::Value& mon);
|
||||||
|
void updateFocusingClient(const Json::Value& client);
|
||||||
|
void updateKeyboardLayout(const std::string& layout);
|
||||||
|
|
||||||
|
static Json::Value sendCommand(const std::string& cmd);
|
||||||
|
|
||||||
|
int sockfd_ = -1;
|
||||||
|
std::thread ipc_thread_;
|
||||||
|
mutable std::mutex data_mutex_;
|
||||||
|
std::unordered_map<std::string, Json::Value> monitors_;
|
||||||
|
std::unordered_map<uint64_t, Json::Value> clients_;
|
||||||
|
uint64_t focusing_client_id_ = 0;
|
||||||
|
std::string keyboard_layout_;
|
||||||
|
std::string keymode_;
|
||||||
|
Json::Value active_client_;
|
||||||
|
std::mutex callback_mutex_;
|
||||||
|
std::list<std::pair<std::string, EventHandler*>> callbacks_;
|
||||||
|
};
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/mango/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
class Keymode : public ALabel, public EventHandler {
|
||||||
|
public:
|
||||||
|
Keymode(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Keymode() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onEvent(const Json::Value& ev) override;
|
||||||
|
void doUpdate();
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
const Bar& bar_;
|
||||||
|
std::string last_keymode_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbregistry.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/mango/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
class Language : public ALabel, public EventHandler {
|
||||||
|
public:
|
||||||
|
Language(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Language() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateFromIPC();
|
||||||
|
void onEvent(const Json::Value& ev) override;
|
||||||
|
void doUpdate();
|
||||||
|
|
||||||
|
struct Layout {
|
||||||
|
std::string full_name;
|
||||||
|
std::string short_name;
|
||||||
|
std::string variant;
|
||||||
|
std::string short_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
Layout getLayout(const std::string& fullName);
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
const Bar& bar_;
|
||||||
|
|
||||||
|
std::vector<Layout> layouts_;
|
||||||
|
unsigned current_idx_;
|
||||||
|
std::string last_short_name_;
|
||||||
|
|
||||||
|
struct rxkb_context* rxkb_ctx_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/mango/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
class Layout : public ALabel, public EventHandler {
|
||||||
|
public:
|
||||||
|
Layout(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Layout() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onEvent(const Json::Value& ev) override;
|
||||||
|
void doUpdate();
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
const Bar& bar_;
|
||||||
|
std::string last_symbol_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <json/value.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "AAppIconLabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/mango/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
class Window : public AAppIconLabel, public EventHandler {
|
||||||
|
public:
|
||||||
|
Window(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Window() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onEvent(const Json::Value& ev) override;
|
||||||
|
void doUpdate();
|
||||||
|
void setClass(const std::string& className, bool enable);
|
||||||
|
|
||||||
|
const Bar& bar_;
|
||||||
|
std::string oldAppId_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <json/value.h>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/mango/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
class Workspaces : public AModule, public EventHandler {
|
||||||
|
public:
|
||||||
|
Workspaces(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Workspaces() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onEvent(const Json::Value& ev) override;
|
||||||
|
void doUpdate();
|
||||||
|
|
||||||
|
Gtk::Button& addButton(uint64_t idx);
|
||||||
|
void updateButtonState(Gtk::Button& button, const Json::Value& tag, const Json::Value& monitor);
|
||||||
|
std::string getIcon(const std::string& value, const Json::Value& tag);
|
||||||
|
bool handleButtonClick(GdkEventButton* event, uint64_t idx, bool isOverview);
|
||||||
|
|
||||||
|
const Bar& bar_;
|
||||||
|
Gtk::Box box_;
|
||||||
|
|
||||||
|
std::unordered_map<uint64_t, Gtk::Button> buttons_;
|
||||||
|
Gtk::Button* overview_button_ = nullptr;
|
||||||
|
|
||||||
|
std::string on_click_left_;
|
||||||
|
std::string on_click_middle_;
|
||||||
|
std::string on_click_right_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -82,6 +82,7 @@ class Idle : public State {
|
|||||||
class Playing : public State {
|
class Playing : public State {
|
||||||
Context* const ctx_;
|
Context* const ctx_;
|
||||||
sigc::connection timer_connection_;
|
sigc::connection timer_connection_;
|
||||||
|
sigc::connection idle_connection_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Playing(Context* const ctx) : ctx_{ctx} {}
|
Playing(Context* const ctx) : ctx_{ctx} {}
|
||||||
@@ -98,7 +99,10 @@ class Playing : public State {
|
|||||||
Playing(Playing const&) = delete;
|
Playing(Playing const&) = delete;
|
||||||
Playing& operator=(Playing const&) = delete;
|
Playing& operator=(Playing const&) = delete;
|
||||||
|
|
||||||
|
void timer() noexcept;
|
||||||
|
void idle() noexcept;
|
||||||
bool on_timer();
|
bool on_timer();
|
||||||
|
bool on_io(Glib::IOCondition const&);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Paused : public State {
|
class Paused : public State {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class Mpris : public ALabel {
|
|||||||
|
|
||||||
std::optional<std::string> artist;
|
std::optional<std::string> artist;
|
||||||
std::optional<std::string> album;
|
std::optional<std::string> album;
|
||||||
|
std::optional<std::string> album_artist;
|
||||||
std::optional<std::string> title;
|
std::optional<std::string> title;
|
||||||
std::optional<std::string> length; // as HH:MM:SS
|
std::optional<std::string> length; // as HH:MM:SS
|
||||||
std::optional<std::string> position; // same format
|
std::optional<std::string> position; // same format
|
||||||
@@ -76,6 +77,8 @@ class Mpris : public ALabel {
|
|||||||
std::string player_;
|
std::string player_;
|
||||||
std::vector<std::string> ignored_players_;
|
std::vector<std::string> ignored_players_;
|
||||||
|
|
||||||
|
bool prefer_album_artist_;
|
||||||
|
|
||||||
PlayerctlPlayerManager* manager;
|
PlayerctlPlayerManager* manager;
|
||||||
PlayerctlPlayer* player;
|
PlayerctlPlayer* player;
|
||||||
PlayerctlPlayer* last_active_player_ = nullptr;
|
PlayerctlPlayer* last_active_player_ = nullptr;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <gtkmm/button.h>
|
#include <gtkmm/button.h>
|
||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "modules/niri/backend.hpp"
|
#include "modules/niri/backend.hpp"
|
||||||
@@ -18,6 +20,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
private:
|
private:
|
||||||
void onEvent(const Json::Value& ev) override;
|
void onEvent(const Json::Value& ev) override;
|
||||||
void doUpdate();
|
void doUpdate();
|
||||||
|
void sortWorkspaces(std::vector<Json::Value>& workspaces) const;
|
||||||
Gtk::Button& addButton(const Json::Value& ws);
|
Gtk::Button& addButton(const Json::Value& ws);
|
||||||
std::string getIcon(const std::string& value, const Json::Value& ws);
|
std::string getIcon(const std::string& value, const Json::Value& ws);
|
||||||
|
|
||||||
@@ -25,6 +28,10 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
// Map from niri workspace id to button.
|
// Map from niri workspace id to button.
|
||||||
std::unordered_map<uint64_t, Gtk::Button> buttons_;
|
std::unordered_map<uint64_t, Gtk::Button> buttons_;
|
||||||
|
|
||||||
|
bool sort_by_id_ = false;
|
||||||
|
bool sort_by_name_ = false;
|
||||||
|
bool sort_by_coordinates_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules::niri
|
} // namespace waybar::modules::niri
|
||||||
|
|||||||
@@ -9,9 +9,18 @@ namespace waybar::modules {
|
|||||||
|
|
||||||
struct Profile {
|
struct Profile {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
// Legacy driver field, kept for backward compatibility with the
|
||||||
|
// `{driver}` format placeholder and with older power-profiles-daemon
|
||||||
|
// versions that only expose a single `Driver` DBus property.
|
||||||
std::string driver;
|
std::string driver;
|
||||||
|
std::string cpuDriver;
|
||||||
|
std::string platformDriver;
|
||||||
|
|
||||||
Profile(std::string n, std::string d) : name(std::move(n)), driver(std::move(d)) {}
|
Profile(std::string n, std::string d, std::string cd, std::string pd)
|
||||||
|
: name(std::move(n)),
|
||||||
|
driver(std::move(d)),
|
||||||
|
cpuDriver(std::move(cd)),
|
||||||
|
platformDriver(std::move(pd)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class PowerProfilesDaemon : public ALabel {
|
class PowerProfilesDaemon : public ALabel {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class Pulseaudio : public ALabel {
|
|||||||
const std::vector<std::string> getPulseIcon() const;
|
const std::vector<std::string> getPulseIcon() const;
|
||||||
|
|
||||||
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
||||||
|
util::PulseaudioTarget target = util::PulseaudioTarget::Sink;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class Tags : public waybar::AModule {
|
|||||||
void handle_focused_tags(uint32_t tags);
|
void handle_focused_tags(uint32_t tags);
|
||||||
void handle_view_tags(struct wl_array* tags);
|
void handle_view_tags(struct wl_array* tags);
|
||||||
void handle_urgent_tags(uint32_t tags);
|
void handle_urgent_tags(uint32_t tags);
|
||||||
|
void handle_focused_output(struct wl_output *output);
|
||||||
|
void handle_unfocused_output(struct wl_output *output);
|
||||||
|
|
||||||
void handle_show();
|
void handle_show();
|
||||||
void handle_primary_clicked(uint32_t tag);
|
void handle_primary_clicked(uint32_t tag);
|
||||||
@@ -31,9 +33,11 @@ class Tags : public waybar::AModule {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const waybar::Bar& bar_;
|
const waybar::Bar& bar_;
|
||||||
|
struct wl_output *output_; // stores the output this module belongs to
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
std::vector<Gtk::Button> buttons_;
|
std::vector<Gtk::Button> buttons_;
|
||||||
struct zriver_output_status_v1* output_status_;
|
struct zriver_output_status_v1* output_status_;
|
||||||
|
struct zriver_seat_status_v1* seat_status_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace waybar::modules::river */
|
} /* namespace waybar::modules::river */
|
||||||
|
|||||||
@@ -14,11 +14,14 @@ namespace waybar::modules::SNI {
|
|||||||
|
|
||||||
class Host {
|
class Host {
|
||||||
public:
|
public:
|
||||||
Host(const std::size_t id, const Json::Value&, const Bar&,
|
Host(const std::size_t id, const Json::Value&, const Bar&, const std::vector<std::string>&,
|
||||||
const std::function<void(std::unique_ptr<Item>&)>&,
|
const std::function<void(std::unique_ptr<Item>&)>&,
|
||||||
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);
|
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);
|
||||||
~Host();
|
~Host();
|
||||||
|
|
||||||
|
void checkIgnoreList(const std::vector<std::string>& ignore_list,
|
||||||
|
const std::function<void(std::unique_ptr<Item>&)>& on_remove);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
|
void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
|
||||||
void nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring,
|
void nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring,
|
||||||
@@ -47,6 +50,7 @@ class Host {
|
|||||||
unsigned retry_count_ = 0;
|
unsigned retry_count_ = 0;
|
||||||
const Json::Value& config_;
|
const Json::Value& config_;
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
|
const std::vector<std::string> ignore_list_;
|
||||||
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
||||||
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
|
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
|
||||||
const std::function<void()> on_update_;
|
const std::function<void()> on_update_;
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ class Tray : public AModule {
|
|||||||
private:
|
private:
|
||||||
void onAdd(std::unique_ptr<Item>& item);
|
void onAdd(std::unique_ptr<Item>& item);
|
||||||
void onRemove(std::unique_ptr<Item>& item);
|
void onRemove(std::unique_ptr<Item>& item);
|
||||||
|
void checkIgnoreList(std::unique_ptr<Item>* item);
|
||||||
|
std::vector<std::string> parseIgnoreList(const Json::Value& config);
|
||||||
void queueUpdate();
|
void queueUpdate();
|
||||||
|
|
||||||
static inline std::size_t nb_hosts_ = 0;
|
static inline std::size_t nb_hosts_ = 0;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
SNI::Watcher::singleton watcher_;
|
SNI::Watcher::singleton watcher_;
|
||||||
|
std::vector<std::string> ignore_list_;
|
||||||
SNI::Host host_;
|
SNI::Host host_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class Workspaces : public AModule, public sigc::trackable {
|
|||||||
static int convertWorkspaceNameToNum(const std::string& name);
|
static int convertWorkspaceNameToNum(const std::string& name);
|
||||||
static int windowRewritePriorityFunction(std::string const& window_rule);
|
static int windowRewritePriorityFunction(std::string const& window_rule);
|
||||||
|
|
||||||
|
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
|
||||||
|
bool isWorkspaceIgnored(std::string const& name);
|
||||||
void onCmd(const struct Ipc::ipc_response&);
|
void onCmd(const struct Ipc::ipc_response&);
|
||||||
void onEvent(const struct Ipc::ipc_response&);
|
void onEvent(const struct Ipc::ipc_response&);
|
||||||
bool filterButtons();
|
bool filterButtons();
|
||||||
@@ -50,6 +52,7 @@ class Workspaces : public AModule, public sigc::trackable {
|
|||||||
std::vector<std::string> workspaces_order_;
|
std::vector<std::string> workspaces_order_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
std::string m_formatWindowSeparator;
|
std::string m_formatWindowSeparator;
|
||||||
|
std::vector<std::regex> m_ignoreWorkspaces;
|
||||||
util::RegexCollection m_windowRewriteRules;
|
util::RegexCollection m_windowRewriteRules;
|
||||||
util::JsonParser parser_;
|
util::JsonParser parser_;
|
||||||
std::unordered_map<std::string, Gtk::Button> buttons_;
|
std::unordered_map<std::string, Gtk::Button> buttons_;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
|
#include "ext-workspace-v1-client-protocol.h"
|
||||||
#include "giomm/desktopappinfo.h"
|
#include "giomm/desktopappinfo.h"
|
||||||
#include "util/icon_loader.hpp"
|
#include "util/icon_loader.hpp"
|
||||||
#include "util/json.hpp"
|
#include "util/json.hpp"
|
||||||
@@ -80,6 +81,7 @@ class Task {
|
|||||||
std::string title_;
|
std::string title_;
|
||||||
std::string app_id_;
|
std::string app_id_;
|
||||||
uint32_t state_ = 0;
|
uint32_t state_ = 0;
|
||||||
|
struct ext_workspace_handle_v1* workspace_ = nullptr;
|
||||||
|
|
||||||
int32_t drag_start_x;
|
int32_t drag_start_x;
|
||||||
int32_t drag_start_y;
|
int32_t drag_start_y;
|
||||||
@@ -102,6 +104,9 @@ class Task {
|
|||||||
bool minimized() const { return state_ & MINIMIZED; }
|
bool minimized() const { return state_ & MINIMIZED; }
|
||||||
bool active() const { return state_ & ACTIVE; }
|
bool active() const { return state_ & ACTIVE; }
|
||||||
bool fullscreen() const { return state_ & FULLSCREEN; }
|
bool fullscreen() const { return state_ & FULLSCREEN; }
|
||||||
|
bool visible() const { return button_visible_; }
|
||||||
|
struct ext_workspace_handle_v1* workspace() const { return workspace_; }
|
||||||
|
void set_workspace(struct ext_workspace_handle_v1* workspace) { workspace_ = workspace; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Callbacks for the wlr protocol */
|
/* Callbacks for the wlr protocol */
|
||||||
@@ -142,6 +147,12 @@ using TaskPtr = std::unique_ptr<Task>;
|
|||||||
|
|
||||||
class Taskbar : public waybar::AModule {
|
class Taskbar : public waybar::AModule {
|
||||||
public:
|
public:
|
||||||
|
struct WorkspaceState {
|
||||||
|
Taskbar* taskbar;
|
||||||
|
struct ext_workspace_handle_v1* handle;
|
||||||
|
uint32_t state = 0;
|
||||||
|
};
|
||||||
|
|
||||||
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
|
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
~Taskbar();
|
~Taskbar();
|
||||||
void update();
|
void update();
|
||||||
@@ -156,22 +167,35 @@ class Taskbar : public waybar::AModule {
|
|||||||
std::map<std::string, std::string> app_ids_replace_map_;
|
std::map<std::string, std::string> app_ids_replace_map_;
|
||||||
|
|
||||||
struct zwlr_foreign_toplevel_manager_v1* manager_;
|
struct zwlr_foreign_toplevel_manager_v1* manager_;
|
||||||
|
struct ext_workspace_manager_v1* workspace_manager_;
|
||||||
struct wl_seat* seat_;
|
struct wl_seat* seat_;
|
||||||
|
std::vector<struct ext_workspace_group_handle_v1*> workspace_groups_;
|
||||||
|
std::vector<std::unique_ptr<WorkspaceState>> workspaces_;
|
||||||
|
struct ext_workspace_handle_v1* current_workspace_ = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Callbacks for global registration */
|
/* Callbacks for global registration */
|
||||||
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
|
void register_workspace_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
|
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
|
|
||||||
/* Callbacks for the wlr protocol */
|
/* Callbacks for the wlr protocol */
|
||||||
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
|
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
|
||||||
void handle_finished();
|
void handle_finished();
|
||||||
|
void handle_workspace_group_create(struct ext_workspace_group_handle_v1*);
|
||||||
|
void handle_workspace_group_removed(struct ext_workspace_group_handle_v1*);
|
||||||
|
void handle_workspace_create(struct ext_workspace_handle_v1*);
|
||||||
|
void handle_workspace_done();
|
||||||
|
void handle_workspace_finished();
|
||||||
|
void handle_workspace_removed(struct ext_workspace_handle_v1*);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void add_button(Gtk::Button&);
|
void add_button(Gtk::Button&);
|
||||||
void move_button(Gtk::Button&, int);
|
void move_button(Gtk::Button&, int);
|
||||||
void remove_button(Gtk::Button&);
|
void remove_button(Gtk::Button&);
|
||||||
void remove_task(uint32_t);
|
void remove_task(uint32_t);
|
||||||
|
void assign_current_workspace(Task&);
|
||||||
|
void update_bar_css_classes();
|
||||||
|
|
||||||
bool show_output(struct wl_output*) const;
|
bool show_output(struct wl_output*) const;
|
||||||
bool all_outputs() const;
|
bool all_outputs() const;
|
||||||
@@ -179,6 +203,9 @@ class Taskbar : public waybar::AModule {
|
|||||||
const IconLoader& icon_loader() const;
|
const IconLoader& icon_loader() const;
|
||||||
const std::unordered_set<std::string>& ignore_list() const;
|
const std::unordered_set<std::string>& ignore_list() const;
|
||||||
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_bar_css_class(const std::string&, bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace waybar::modules::wlr */
|
} /* namespace waybar::modules::wlr */
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ class AudioBackend {
|
|||||||
static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);
|
static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);
|
||||||
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
|
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
|
||||||
static void volumeModifyCb(pa_context*, int, void*);
|
static void volumeModifyCb(pa_context*, int, void*);
|
||||||
|
static void sourceVolumeModifyCb(pa_context*, int, void*);
|
||||||
void connectContext();
|
void connectContext();
|
||||||
|
|
||||||
pa_threaded_mainloop* mainloop_;
|
pa_threaded_mainloop* mainloop_;
|
||||||
pa_mainloop_api* mainloop_api_;
|
pa_mainloop_api* mainloop_api_;
|
||||||
pa_context* context_;
|
pa_context* context_;
|
||||||
pa_cvolume pa_volume_;
|
pa_cvolume pa_volume_;
|
||||||
|
pa_cvolume pa_source_volume_;
|
||||||
|
|
||||||
// SINK
|
// SINK
|
||||||
uint32_t sink_idx_{0};
|
uint32_t sink_idx_{0};
|
||||||
@@ -72,8 +74,10 @@ class AudioBackend {
|
|||||||
AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag);
|
AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag);
|
||||||
~AudioBackend();
|
~AudioBackend();
|
||||||
|
|
||||||
void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100);
|
void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100,
|
||||||
void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100);
|
PulseaudioTarget target = PulseaudioTarget::Sink);
|
||||||
|
void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100,
|
||||||
|
PulseaudioTarget target = PulseaudioTarget::Sink);
|
||||||
|
|
||||||
void setIgnoredSinks(const Json::Value& config);
|
void setIgnoredSinks(const Json::Value& config);
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
|
|
||||||
class pow_format {
|
class pow_format {
|
||||||
public:
|
public:
|
||||||
pow_format(long long val, std::string&& unit, bool binary = false)
|
pow_format(long long val, std::string&& unit, bool binary = false, int min_pow_for_decimal = 0)
|
||||||
: val_(val), unit_(unit), binary_(binary) {};
|
: val_(val), unit_(unit), binary_(binary), min_pow_for_decimal_(min_pow_for_decimal) {};
|
||||||
|
|
||||||
long long val_;
|
long long val_;
|
||||||
std::string unit_;
|
std::string unit_;
|
||||||
bool binary_;
|
bool binary_;
|
||||||
|
int min_pow_for_decimal_;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace fmt {
|
namespace fmt {
|
||||||
@@ -74,7 +75,8 @@ struct formatter<pow_format> {
|
|||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
default:
|
default:
|
||||||
format = "{coefficient:.1f}{prefix}{unit}";
|
format = pow < s.min_pow_for_decimal_ ? "{coefficient:.0f}{prefix}{unit}"
|
||||||
|
: "{coefficient:.1f}{prefix}{unit}";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return fmt::format_to(
|
return fmt::format_to(
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <json/value.h>
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
bool valid_host(const Json::Value& config);
|
||||||
|
} // namespace waybar::util
|
||||||
+11
-5
@@ -138,6 +138,11 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
|
|||||||
:[ When enabled, the calendar follows the ISO 8601 standard: weeks begin on
|
:[ When enabled, the calendar follows the ISO 8601 standard: weeks begin on
|
||||||
Monday, and the first week of the year is numbered 1. The default week format is
|
Monday, and the first week of the year is numbered 1. The default week format is
|
||||||
'{:%V}'.
|
'{:%V}'.
|
||||||
|
|[ *first-day-of-week*
|
||||||
|
:[ integer
|
||||||
|
:[
|
||||||
|
:[ The first day of the week, where 0 is Sunday and 6 is Saturday.
|
||||||
|
When not set, the first day of the week is determined by the locale settings.
|
||||||
|
|
||||||
3. Addressed by *clock: calendar: format*
|
3. Addressed by *clock: calendar: format*
|
||||||
[- *Option*
|
[- *Option*
|
||||||
@@ -207,11 +212,12 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
|
|||||||
"format-alt": "{:%A, %B %d, %Y (%R)} ",
|
"format-alt": "{:%A, %B %d, %Y (%R)} ",
|
||||||
"tooltip-format": "<tt><small>{calendar}</small></tt>",
|
"tooltip-format": "<tt><small>{calendar}</small></tt>",
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"mode" : "year",
|
"mode" : "year",
|
||||||
"mode-mon-col" : 3,
|
"mode-mon-col" : 3,
|
||||||
"weeks-pos" : "right",
|
"weeks-pos" : "right",
|
||||||
"on-scroll" : 1,
|
"first-day-of-week": 1,
|
||||||
"on-click-right": "mode",
|
"on-scroll" : 1,
|
||||||
|
"on-click-right" : "mode",
|
||||||
"format": {
|
"format": {
|
||||||
"months": "<span color='#ffead3'><b>{}</b></span>",
|
"months": "<span color='#ffead3'><b>{}</b></span>",
|
||||||
"days": "<span color='#ecc6d9'><b>{}</b></span>",
|
"days": "<span color='#ecc6d9'><b>{}</b></span>",
|
||||||
|
|||||||
@@ -83,6 +83,18 @@ The *cpu* module displays the current CPU utilization.
|
|||||||
default: true ++
|
default: true ++
|
||||||
Option to disable tooltip on hover.
|
Option to disable tooltip on hover.
|
||||||
|
|
||||||
|
*tooltip-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The format of the tooltip shown on hover. Supports the same replacements as *format*.
|
||||||
|
|
||||||
|
*format-<state>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The format to use when the given *state* (see *states*) is active. Supports the same replacements as *format*.
|
||||||
|
|
||||||
|
*tooltip-format-<state>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The tooltip format to use when the given *state* (see *states*) is active. Takes precedence over *tooltip-format*.
|
||||||
|
|
||||||
*expand*: ++
|
*expand*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -112,6 +124,8 @@ The *cpu* module displays the current CPU utilization.
|
|||||||
|
|
||||||
*{icon*{n}*}*: Icon for CPU core n usage. Use like {icon0}.
|
*{icon*{n}*}*: Icon for CPU core n usage. Use like {icon0}.
|
||||||
|
|
||||||
|
*{icon0}{icon1}{icon2}{icon3}*: All per-core icons concatenated. Equivalent to {icon0}{icon1}...{icon*N*} but adapts to the number of cores automatically.
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
Basic configuration:
|
Basic configuration:
|
||||||
@@ -134,6 +148,16 @@ CPU usage per core rendered as icons:
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Automatically determine number of icons according to number of logical cores:
|
||||||
|
|
||||||
|
```
|
||||||
|
"cpu": {
|
||||||
|
"interval": 1,
|
||||||
|
"format": "{icons} {usage:>2}% ",
|
||||||
|
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#cpu*
|
- *#cpu*
|
||||||
|
|||||||
+36
-9
@@ -6,17 +6,12 @@ waybar - disk module
|
|||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
The *disk* module displays the current disk space used.
|
The *disk* module displays information of multiple disks.
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
Addressed by *disk*
|
Addressed by *disk*
|
||||||
|
|
||||||
*path*: ++
|
|
||||||
typeof: string ++
|
|
||||||
default: "/" ++
|
|
||||||
Any path residing in the filesystem or mountpoint for which the information should be displayed.
|
|
||||||
|
|
||||||
*interval*: ++
|
*interval*: ++
|
||||||
typeof: integer++
|
typeof: integer++
|
||||||
default: 30 ++
|
default: 30 ++
|
||||||
@@ -25,7 +20,7 @@ Addressed by *disk*
|
|||||||
*format*: ++
|
*format*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: "{percentage_used}%" ++
|
default: "{percentage_used}%" ++
|
||||||
The format, how information should be displayed.
|
The format, how information for each disk should be displayed.
|
||||||
|
|
||||||
*rotate*: ++
|
*rotate*: ++
|
||||||
typeof: integer ++
|
typeof: integer ++
|
||||||
@@ -75,6 +70,26 @@ Addressed by *disk*
|
|||||||
typeof: string ++
|
typeof: string ++
|
||||||
Command to execute when scrolling down on the module.
|
Command to execute when scrolling down on the module.
|
||||||
|
|
||||||
|
*path*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: "/" ++
|
||||||
|
Deprecated path of filesystem or mountpoint to monitor.
|
||||||
|
|
||||||
|
*paths*: ++
|
||||||
|
typeof: array ++
|
||||||
|
default: ["/"] ++
|
||||||
|
Array of paths residing in the filesystem or mountpoint for which the information should be displayed.
|
||||||
|
|
||||||
|
*header*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: "" ++
|
||||||
|
Text to appear before the disk information defined in the format.
|
||||||
|
|
||||||
|
*separator*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: " " ++
|
||||||
|
Separator string between multiple disk information.
|
||||||
|
|
||||||
*smooth-scrolling-threshold*: ++
|
*smooth-scrolling-threshold*: ++
|
||||||
typeof: double ++
|
typeof: double ++
|
||||||
Threshold to be used when scrolling.
|
Threshold to be used when scrolling.
|
||||||
@@ -123,7 +138,7 @@ Addressed by *disk*
|
|||||||
|
|
||||||
*{free}*: Amount of available disk space for normal users. Automatically selects unit based on size remaining.
|
*{free}*: Amount of available disk space for normal users. Automatically selects unit based on size remaining.
|
||||||
|
|
||||||
*{path}*: The path specified in the configuration.
|
*{path}*: The path for each disk specified in the configuration.
|
||||||
|
|
||||||
*{specific_total}*: Total amount of space on the disk, partition, or mountpoint in a specific unit. Defaults to bytes.
|
*{specific_total}*: Total amount of space on the disk, partition, or mountpoint in a specific unit. Defaults to bytes.
|
||||||
|
|
||||||
@@ -143,10 +158,22 @@ Addressed by *disk*
|
|||||||
```
|
```
|
||||||
"disk": {
|
"disk": {
|
||||||
"interval": 30,
|
"interval": 30,
|
||||||
|
"format": "{percentage_free}% free on {path}",
|
||||||
|
"header": "Disks: ",
|
||||||
|
"paths": ["/", "/home"],
|
||||||
|
"separator": " ",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
"disk": {
|
||||||
|
"interval": 30,
|
||||||
|
"paths": ["/"],
|
||||||
"format": "{specific_free:0.2f} GB out of {specific_total:0.2f} GB available. Alternatively {free} out of {total} available",
|
"format": "{specific_free:0.2f} GB out of {specific_total:0.2f} GB available. Alternatively {free} out of {total} available",
|
||||||
"unit": "GB"
|
"unit": "GB"
|
||||||
// 1434.25 GB out of 2000.00 GB available. Alternatively 1.4TiB out of 1.9TiB available.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1434.25 GB out of 2000.00 GB available. Alternatively 1.4TiB out of 1.9TiB available.
|
||||||
```
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ Addressed by *dwl/tags*
|
|||||||
typeof: array ++
|
typeof: array ++
|
||||||
The label to display for each tag.
|
The label to display for each tag.
|
||||||
|
|
||||||
|
*hide-vacant*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, tags without clients and that are not active will be hidden.
|
||||||
|
|
||||||
*disable-click*: ++
|
*disable-click*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -46,8 +51,9 @@ Addressed by *dwl/tags*
|
|||||||
- *#tags button.empty*
|
- *#tags button.empty*
|
||||||
- *#tags button.focused*
|
- *#tags button.focused*
|
||||||
- *#tags button.urgent*
|
- *#tags button.urgent*
|
||||||
|
- *#tags button.output*
|
||||||
|
|
||||||
Note that occupied/focused/urgent status may overlap. That is, a tag may be
|
Note that occupied/focused/urgent/output status may overlap. That is, a tag may be
|
||||||
both occupied and focused at the same time.
|
both occupied and focused at the same time.
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ Addressed by *dwl/window*
|
|||||||
default: {title} ++
|
default: {title} ++
|
||||||
The format, how information should be displayed.
|
The format, how information should be displayed.
|
||||||
|
|
||||||
|
*hide-empty*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Option to hide the module when the content would be empty.
|
||||||
|
|
||||||
|
*hide-inactive*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Option to hide the module when the window is unfocused.
|
||||||
|
|
||||||
*rotate*: ++
|
*rotate*: ++
|
||||||
typeof: integer ++
|
typeof: integer ++
|
||||||
Positive value to rotate the text label (in 90 degree increments).
|
Positive value to rotate the text label (in 90 degree increments).
|
||||||
@@ -109,6 +119,10 @@ If no expression matches, the format output is left unchanged.
|
|||||||
|
|
||||||
Invalid expressions (e.g., mismatched parentheses) are skipped.
|
Invalid expressions (e.g., mismatched parentheses) are skipped.
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#window.active*
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ Addressed by *hyprland/language*
|
|||||||
typeof: string++
|
typeof: string++
|
||||||
Provide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.
|
Provide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.
|
||||||
|
|
||||||
|
*format-<lang>-<variant>* ++
|
||||||
|
typeof: string ++
|
||||||
|
Like *format-<lang>* but also matches the layout variant, taking precedence over *format-<lang>* when both the language and variant match.
|
||||||
|
|
||||||
*keyboard-name*: ++
|
*keyboard-name*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
|
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
|
||||||
@@ -52,6 +56,10 @@ Addressed by *hyprland/language*
|
|||||||
typeof: string ++
|
typeof: string ++
|
||||||
Allows specifying a different tooltip format for each language. The *<lang>* should be replaced with the language code. This can be used to provide a custom tooltip for each language.
|
Allows specifying a different tooltip format for each language. The *<lang>* should be replaced with the language code. This can be used to provide a custom tooltip for each language.
|
||||||
|
|
||||||
|
*tooltip-format-<lang>-<variant>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Like *tooltip-format-<lang>* but also matches the layout variant, taking precedence over *tooltip-format-<lang>* when both the language and variant match.
|
||||||
|
|
||||||
*expand*: ++
|
*expand*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ Addressed by *hyprland/window*
|
|||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
Show the active window of the monitor the bar belongs to, instead of the focused window.
|
Show the active window of the monitor the bar belongs to, instead of the focused window.
|
||||||
|
|
||||||
|
*fallback*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Text to display when the focused window title is empty (for example when no window is focused).
|
||||||
|
|
||||||
|
*tooltip-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {title} ++
|
||||||
|
The format of the tooltip shown on hover. Supports the same replacements as *format*. Requires *tooltip* to be enabled.
|
||||||
|
|
||||||
*icon*: ++
|
*icon*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
|
|||||||
@@ -98,6 +98,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
|||||||
- {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++
|
- {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++
|
||||||
See https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example.
|
See https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example.
|
||||||
|
|
||||||
|
*max-windows*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 0 (unlimited) ++
|
||||||
|
Maximum number of windows to show per workspace. When set, newest windows beyond the limit are not shown. Set to 0 for unlimited windows.
|
||||||
|
|
||||||
*show-special*: ++
|
*show-special*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -113,6 +118,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true, only persistent workspaces will be shown on bar.
|
If set to true, only persistent workspaces will be shown on bar.
|
||||||
|
|
||||||
|
*persistent-workspaces*: ++
|
||||||
|
typeof: object ++
|
||||||
|
default: empty ++
|
||||||
|
Lists workspaces that should always be shown, even when they do not exist. Keys are workspace names and values are arrays of output names on which the workspace should be shown (an empty array means all outputs). See the examples below.
|
||||||
|
|
||||||
*all-outputs*: ++
|
*all-outputs*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -123,6 +133,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true, only the active workspace will be shown.
|
If set to true, only the active workspace will be shown.
|
||||||
|
|
||||||
|
*hide-active*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, the active workspace will be hidden. Unless a workspace is persistent or special.
|
||||||
|
|
||||||
*move-to-monitor*: ++
|
*move-to-monitor*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ You must be a member of the input group to use this module.
|
|||||||
*format-icons*: ++
|
*format-icons*: ++
|
||||||
typeof: object ++
|
typeof: object ++
|
||||||
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
||||||
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
|
Based on the keyboard state, the corresponding icon gets selected. Supports two syntaxes:
|
||||||
|
- Common format-icons: "locked" and "unlocked" keys apply to all lock types.
|
||||||
|
- Per-lock-type format-icons: per-lock-type objects with "numlock", "capslock", "scrolllock" keys, each containing "locked" and "unlocked" icons.
|
||||||
|
See *icons*.
|
||||||
|
|
||||||
*numlock*: ++
|
*numlock*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
@@ -68,15 +71,21 @@ You must be a member of the input group to use this module.
|
|||||||
|
|
||||||
The following *format-icons* can be set.
|
The following *format-icons* can be set.
|
||||||
|
|
||||||
|
## Common format-icons for all lock types:
|
||||||
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
||||||
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
|
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked".
|
||||||
|
|
||||||
|
## Per-lock-type format-icons:
|
||||||
|
- *numlock*, *capslock*, *scrolllock*: Object containing "locked" and "unlocked" keys for lock-type-specific icons. Defaults to {"locked": "locked", "unlocked": "unlocked"} for each lock type.
|
||||||
|
|
||||||
# EXAMPLE:
|
# EXAMPLE:
|
||||||
|
|
||||||
|
## Common format-icons for all lock types:
|
||||||
```
|
```
|
||||||
"keyboard-state": {
|
"keyboard-state": {
|
||||||
"numlock": true,
|
"numlock": true,
|
||||||
"capslock": true,
|
"capslock": true,
|
||||||
|
"scrolllock": true,
|
||||||
"format": "{name} {icon}",
|
"format": "{name} {icon}",
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"locked": "",
|
"locked": "",
|
||||||
@@ -85,6 +94,30 @@ The following *format-icons* can be set.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Per-lock-type format-icons:
|
||||||
|
```
|
||||||
|
"keyboard-state": {
|
||||||
|
"numlock": true,
|
||||||
|
"capslock": true,
|
||||||
|
"scrolllock": true,
|
||||||
|
"format": "{name} {icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"numlock": {
|
||||||
|
"locked": "1",
|
||||||
|
"unlocked": "0"
|
||||||
|
},
|
||||||
|
"capslock": {
|
||||||
|
"locked": "A",
|
||||||
|
"unlocked": "a"
|
||||||
|
},
|
||||||
|
"scrolllock": {
|
||||||
|
"locked": "S",
|
||||||
|
"unlocked": "s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#keyboard-state*
|
- *#keyboard-state*
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
waybar-mango-keymode(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - mango keymode module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *keymode* module displays the current keyboard mode (e.g. "resize", "default") in the Mango compositor. It is hidden when no mode is active.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *mango/keymode*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {} ++
|
||||||
|
The format, how the mode should be displayed. *{mode}* is replaced by the current mode name.
|
||||||
|
|
||||||
|
*format-<mode>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Provide a custom format for a specific keymode. *<mode>* is the mode name as reported by Mango (e.g. "resize"). The value can contain *{mode}* as a placeholder.
|
||||||
|
If this option is set, it overrides the main *format* for that mode.
|
||||||
|
|
||||||
|
*menu*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Action that pops up a menu.
|
||||||
|
|
||||||
|
*menu-file*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Location of the menu descriptor file.
|
||||||
|
|
||||||
|
*menu-actions*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Actions for the menu buttons.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables the module to consume all leftover space.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{mode}*: The name of the current keymode.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"mango/keymode": {
|
||||||
|
"format": "[{mode}]",
|
||||||
|
"format-resize": " Resizing"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#keymode*
|
||||||
|
|
||||||
|
A CSS class with the current mode name (e.g. *.resize*) is added to the widget, allowing per‑mode styling:
|
||||||
|
|
||||||
|
```
|
||||||
|
#keymode.resize { background: #ff0000; }
|
||||||
|
```
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
waybar-mango-language(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - mango language module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *language* module displays the currently active keyboard layout in the Mango compositor.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *mango/language*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {} ++
|
||||||
|
The format, how the layout should be displayed. See *FORMAT REPLACEMENTS*.
|
||||||
|
|
||||||
|
*format-<lang>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Provide an alternative format string for a given language.
|
||||||
|
<lang> is the short description of the layout (e.g. "us", "de").
|
||||||
|
The value is used as the replacement for *{}* in the main *format*.
|
||||||
|
This option can be repeated for multiple languages.
|
||||||
|
|
||||||
|
*format-<lang>-<variant>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Like *format-<lang>* but also matches the layout variant, taking precedence over *format-<lang>* when both the language and variant match.
|
||||||
|
|
||||||
|
*menu*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Action that pops up a menu.
|
||||||
|
|
||||||
|
*menu-file*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Location of the menu descriptor file. It must contain an element of type GtkMenu with id *menu*.
|
||||||
|
|
||||||
|
*menu-actions*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Actions corresponding to the buttons of the menu.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables this module to consume all leftover space dynamically.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{short}*: Short name of the layout (e.g. "us"). This is also the default when no format is specified.
|
||||||
|
|
||||||
|
*{shortDescription}*: Short description of the layout (same as *{short}* in most cases).
|
||||||
|
|
||||||
|
*{long}*: Full name of the layout as reported by Mango (e.g. "English (US)").
|
||||||
|
|
||||||
|
*{variant}*: Variant of the layout, if any.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"mango/language": {
|
||||||
|
"format": " {long} ",
|
||||||
|
"format-us": "US",
|
||||||
|
"format-de": "DE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#language*
|
||||||
|
|
||||||
|
A CSS class matching the current layout's short name is added to the widget.
|
||||||
|
This allows per‑layout styling:
|
||||||
|
|
||||||
|
```
|
||||||
|
#language.us { color: #00ff00; }
|
||||||
|
#language.de { color: #ff0000; }
|
||||||
|
```
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
waybar-mango-layout(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - mango layout module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *layout* module displays the current layout symbol of the monitor (e.g. "S", "M") in the Mango compositor.
|
||||||
|
It supports dynamic CSS classes and custom formats based on the active layout symbol.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *mango/layout*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {symbol} ++
|
||||||
|
The default format, how the layout symbol should be displayed. *{symbol}* is replaced by the current layout symbol.
|
||||||
|
|
||||||
|
*format-<symbol>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: *none* ++
|
||||||
|
The custom format to use when a specific layout symbol is active (e.g., *format-S*, *format-M*). Note that the symbol string is strictly case-sensitive. If no match is found, it falls back to *format*.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables the module to consume all leftover space.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{symbol}*: The layout symbol reported by Mango (e.g., "S", "M", "Dwindle").
|
||||||
|
|
||||||
|
# CUSTOM FORMATS
|
||||||
|
|
||||||
|
You can define specific formats for different layouts by appending the exact layout symbol to the *format-* key in your configuration.
|
||||||
|
|
||||||
|
For example, if your Mango compositor reports the symbol "S" for a spiral layout and "M" for a master layout, you can use *format-S* and *format-M* to define unique icons or text for each. Keep in mind that JSON keys are case-sensitive, so if the compositor sends "S", the key must be exactly *format-S*.
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
The layout module provides dynamic CSS classes based on the current layout symbol, allowing you to style each layout differently.
|
||||||
|
|
||||||
|
* *#mango-layout*
|
||||||
|
* *.<symbol>* - The current layout symbol is dynamically added as a CSS class name (e.g., *.S*, *.M*).
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"mango/layout": {
|
||||||
|
"format": "[] {symbol}",
|
||||||
|
"format-S": " {symbol}",
|
||||||
|
"format-M": " {symbol}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Example
|
||||||
|
|
||||||
|
```
|
||||||
|
#mango-layout {
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific color for the "S" layout */
|
||||||
|
#mango-layout.S {
|
||||||
|
color: #a6e3a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific color for the "M" layout */
|
||||||
|
#mango-layout.M {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
waybar-mango-window(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - mango window module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *window* module displays the title and app ID of the currently focused window in the Mango compositor.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *mango/window*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {title} ++
|
||||||
|
The format string. See *FORMAT REPLACEMENTS*.
|
||||||
|
|
||||||
|
*rewrite*: ++
|
||||||
|
typeof: object ++
|
||||||
|
Rules to rewrite the window title. Each key is a regular expression and its value is the replacement string. Captures can be used with *$1*, *$2*, etc.
|
||||||
|
|
||||||
|
*icon*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Whether to show the application icon.
|
||||||
|
|
||||||
|
*icon-size*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
default: 24 ++
|
||||||
|
Size of the application icon in pixels.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables the module to consume all leftover space.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{title}*: The current window title.
|
||||||
|
|
||||||
|
*{app_id}*: The app ID of the focused window.
|
||||||
|
|
||||||
|
# REWRITE RULES
|
||||||
|
|
||||||
|
If the title matches a regular expression from the *rewrite* object, it is replaced by the corresponding value. Regular expression syntax follows ECMAScript rules. Unmatched titles are left unchanged.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"mango/window": {
|
||||||
|
"format": "{title}",
|
||||||
|
"rewrite": {
|
||||||
|
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||||
|
"(.*) - zsh": "> [$1]"
|
||||||
|
},
|
||||||
|
"icon": true,
|
||||||
|
"icon-size": 20
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#window*
|
||||||
|
- *#window.empty* – applied when no window is focused (module hidden by default)
|
||||||
|
- *#window.solo* – applied when only one window is present on the active workspace
|
||||||
|
- *#window.<app-id>* – applied when a single window with the given app ID is on the workspace
|
||||||
|
|
||||||
|
The classes *.empty*, *.solo*, and the app‑ID class are set on the module’s event box.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
waybar-mango-workspaces(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - mango workspaces module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *workspaces* module displays the tags (workspaces) of the Mango compositor. It shows an overview button when the overview mode is active (active tag is 0), and individual tag buttons otherwise.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *mango/workspaces*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {value} ++
|
||||||
|
The format for each tag button. See *FORMAT REPLACEMENTS*.
|
||||||
|
|
||||||
|
*format-icons*: ++
|
||||||
|
typeof: object ++
|
||||||
|
Icons to be used instead of the workspace index or name. Keys can be a workspace index (as a string), or one of the following special state keys: *default*, *active*, *urgent*, *empty*.
|
||||||
|
|
||||||
|
*disable-markup*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If true, the button label will not be interpreted as Pango markup.
|
||||||
|
|
||||||
|
*current-only*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If true, only the currently active workspace button is shown.
|
||||||
|
|
||||||
|
*hide-empty*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If true, buttons for empty (client_count == 0) workspaces are hidden, unless the workspace is active.
|
||||||
|
|
||||||
|
*on-click*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute on left click. Typically set to *activate* or *toggle*.
|
||||||
|
|
||||||
|
*on-click-middle*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command for middle click. Same actions as *on-click*.
|
||||||
|
|
||||||
|
*on-click-right*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command for right click. Same actions as *on-click*.
|
||||||
|
|
||||||
|
*overview-label*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: "OVERVIEW" ++
|
||||||
|
Label shown on the overview button when the overview is active.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables the module to consume leftover space.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{value}*: Workspace index (same as *{index}* for unnamed workspaces).
|
||||||
|
|
||||||
|
*{name}*: Workspace name (equal to the index for unnamed workspaces in Mango).
|
||||||
|
|
||||||
|
*{icon}*: Icon selected from *format-icons* based on workspace index and state.
|
||||||
|
|
||||||
|
*{index}*: Numeric index of the workspace.
|
||||||
|
|
||||||
|
*{output}*: Name of the output where the workspace is located.
|
||||||
|
|
||||||
|
# CLICK ACTIONS
|
||||||
|
|
||||||
|
When a tag button is clicked, the action from *on-click* (or its middle/right variants) is evaluated.
|
||||||
|
Supported actions:
|
||||||
|
|
||||||
|
- *activate*: dispatch view,<index>
|
||||||
|
- *toggle*: dispatch toggleview,<index>
|
||||||
|
|
||||||
|
When the overview button is clicked, the actions change to:
|
||||||
|
|
||||||
|
- *activate*: dispatch overview
|
||||||
|
- *toggle*: dispatch toggleoverview
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"mango/workspaces": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"1": "一",
|
||||||
|
"2": "二",
|
||||||
|
"active": "",
|
||||||
|
"default": "",
|
||||||
|
"urgent": "",
|
||||||
|
"empty": ""
|
||||||
|
},
|
||||||
|
"on-click": "activate",
|
||||||
|
"on-click-right": "toggle",
|
||||||
|
"overview-label": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#workspaces button*
|
||||||
|
- *#workspaces button.active* – the workspace is active (visible) on its output.
|
||||||
|
- *#workspaces button.urgent* – the workspace has at least one urgent window.
|
||||||
|
- *#workspaces button.empty* – the workspace contains no clients.
|
||||||
|
- *#workspaces button.current_output* – the workspace belongs to the output where the bar is shown.
|
||||||
|
- *#workspaces button.overview* – the overview button (visible in overview mode).
|
||||||
@@ -21,6 +21,10 @@ Addressed by *niri/language*
|
|||||||
typeof: string++
|
typeof: string++
|
||||||
Provide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.
|
Provide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.
|
||||||
|
|
||||||
|
*format-<lang>-<variant>* ++
|
||||||
|
typeof: string ++
|
||||||
|
Like *format-<lang>* but also matches the layout variant, taking precedence over *format-<lang>* when both the language and variant match.
|
||||||
|
|
||||||
*menu*: ++
|
*menu*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Action that popups the menu.
|
Action that popups the menu.
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ See the output of "niri msg windows" for examples
|
|||||||
|
|
||||||
*{app_id}*: The current app ID of the focused window.
|
*{app_id}*: The current app ID of the focused window.
|
||||||
|
|
||||||
|
*{col}*: The current column of the focused window in the workspace.
|
||||||
|
|
||||||
|
*{max_col}*: The maximum column inside the workspace of the focused window.
|
||||||
|
|
||||||
# REWRITE RULES
|
# REWRITE RULES
|
||||||
|
|
||||||
*rewrite* is an object where keys are regular expressions and values are
|
*rewrite* is an object where keys are regular expressions and values are
|
||||||
|
|||||||
@@ -17,6 +17,28 @@ Addressed by *niri/workspaces*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output.
|
If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output.
|
||||||
|
|
||||||
|
*sort-by-name*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Sort workspaces by name (numeric sort when all names are numbers). Unnamed workspaces fall back to their index on the output.
|
||||||
|
|
||||||
|
*sort-by-coordinates*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Sort workspaces by output and index. If both *sort-by-name* and *sort-by-coordinates* are true, sorting by name is applied.
|
||||||
|
|
||||||
|
*sort-by-id*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Sort workspaces by id, taking precedence over other sort options.
|
||||||
|
|
||||||
|
*sort-by-number*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Deprecated alias for *sort-by-id*; prefer *sort-by-id* instead.
|
||||||
|
|
||||||
|
If none of the sorting options are enabled, workspaces keep their output/index order.
|
||||||
|
|
||||||
*format*: ++
|
*format*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: {value} ++
|
default: {value} ++
|
||||||
@@ -41,6 +63,11 @@ Addressed by *niri/workspaces*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true, only the active or focused workspace will be shown.
|
If set to true, only the active or focused workspace will be shown.
|
||||||
|
|
||||||
|
*hide-empty*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, empty workspaces will not be shown.
|
||||||
|
|
||||||
*on-update*: ++
|
*on-update*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Command to execute when the module is updated.
|
Command to execute when the module is updated.
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ $XDG_CONFIG_HOME/waybar/config
|
|||||||
:[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
:[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
||||||
|[ *tooltip-format*
|
|[ *tooltip-format*
|
||||||
:[ string
|
:[ string
|
||||||
:[ "Power profile: {profile}\\nDriver: {driver}"
|
:[ "Power profile: {profile}\\nCPU driver: {cpu_driver}\\nPlatform driver: {platform_driver}"
|
||||||
:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. {cpu_driver} and {platform_driver} are substituted with the CPU and platform drivers reported by recent power-profiles-daemon versions. {driver} is kept for backward compatibility: it resolves to the legacy single driver on older daemons and falls back to the CPU driver on recent ones.
|
||||||
|[ *tooltip*
|
|[ *tooltip*
|
||||||
:[ bool
|
:[ bool
|
||||||
:[ true
|
:[ true
|
||||||
@@ -51,7 +51,7 @@ Compact display (default config):
|
|||||||
```
|
```
|
||||||
"power-profiles-daemon": {
|
"power-profiles-daemon": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"default": "",
|
"default": "",
|
||||||
@@ -67,7 +67,7 @@ Display the full profile name:
|
|||||||
```
|
```
|
||||||
"power-profiles-daemon": {
|
"power-profiles-daemon": {
|
||||||
"format": "{icon} {profile}",
|
"format": "{icon} {profile}",
|
||||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|||||||
@@ -43,18 +43,35 @@ The volume can be controlled by dragging the slider across the bar or clicking o
|
|||||||
default: true ++
|
default: true ++
|
||||||
Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider.
|
Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider.
|
||||||
|
|
||||||
|
*target*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: sink ++
|
||||||
|
The audio target to control. Can be either `sink` (output/speakers) or `source` (input/microphone).
|
||||||
|
|
||||||
|
*ignored-sinks*: ++
|
||||||
|
typeof: array ++
|
||||||
|
default: empty ++
|
||||||
|
A list of sink descriptions to ignore when tracking the default sink, so switching to those sinks does not update the slider.
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
||||||
"modules-right": [
|
"modules-right": [
|
||||||
"pulseaudio/slider",
|
"pulseaudio/slider#out",
|
||||||
|
"pulseaudio/slider#in",
|
||||||
],
|
],
|
||||||
"pulseaudio/slider": {
|
"pulseaudio/slider#out": {
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 100,
|
"max": 100,
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
"zero-on-mute": false,
|
"zero-on-mute": false,
|
||||||
"unmute-on-volume-change": false
|
"unmute-on-volume-change": false
|
||||||
|
},
|
||||||
|
"pulseaudio/slider#in": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"orientation": "horizontal",
|
||||||
|
"target": "source"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c
|
|||||||
default: false ++
|
default: false ++
|
||||||
Enables this module to consume all left over space dynamically.
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
*target*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: sink ++
|
||||||
|
The audio target to control when scrolling. Can be either `sink` (output/speakers) or `source` (input/microphone).
|
||||||
|
|
||||||
# FORMAT REPLACEMENTS
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.
|
*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ Addressed by *river/tags*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled.
|
If set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled.
|
||||||
|
|
||||||
|
*set-tags*: ++
|
||||||
|
typeof: array ++
|
||||||
|
An array of tag bitmasks, one per tag button. Left-clicking a tag sets the focused tags to the corresponding bitmask instead of the single tag. Requires *disable-click* to be false.
|
||||||
|
|
||||||
|
*toggle-tags*: ++
|
||||||
|
typeof: array ++
|
||||||
|
An array of tag bitmasks, one per tag button. Right-clicking a tag toggles the corresponding bitmask in the focused tags instead of the single tag. Requires *disable-click* to be false.
|
||||||
|
|
||||||
*expand*: ++
|
*expand*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -50,10 +58,13 @@ Addressed by *river/tags*
|
|||||||
- *#tags button.occupied*
|
- *#tags button.occupied*
|
||||||
- *#tags button.focused*
|
- *#tags button.focused*
|
||||||
- *#tags button.urgent*
|
- *#tags button.urgent*
|
||||||
|
- *#tags button.output*
|
||||||
|
|
||||||
Note that occupied/focused/urgent status may overlap. That is, a tag may be
|
Note that occupied/focused/urgent/output status may overlap. That is, a tag may be
|
||||||
both occupied and focused at the same time.
|
both occupied and focused at the same time.
|
||||||
|
|
||||||
|
The *output* style is applied when the river output (e.g. monitor) of the current bar is focused.
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
|
|
||||||
waybar(5), river(1)
|
waybar(5), river(1)
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ Addressed by *sway/workspaces*
|
|||||||
default: {value} ++
|
default: {value} ++
|
||||||
The format, how information should be displayed.
|
The format, how information should be displayed.
|
||||||
|
|
||||||
|
*format-for-negative-index*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: *format* ++
|
||||||
|
An alternative format, which will be used for workspaces with no explict or negative index ("num" in sway terms). Requires *format* to be set.
|
||||||
|
|
||||||
*format-icons*: ++
|
*format-icons*: ++
|
||||||
typeof: array ++
|
typeof: array ++
|
||||||
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
||||||
@@ -65,6 +70,11 @@ Addressed by *sway/workspaces*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true. Only focused workspaces will be shown.
|
If set to true. Only focused workspaces will be shown.
|
||||||
|
|
||||||
|
*ignore-workspaces*: ++
|
||||||
|
typeof: array ++
|
||||||
|
default: empty ++
|
||||||
|
List of regular expressions. Workspaces whose name matches any of the given patterns are not shown.
|
||||||
|
|
||||||
*persistent-workspaces*: ++
|
*persistent-workspaces*: ++
|
||||||
typeof: json (see below) ++
|
typeof: json (see below) ++
|
||||||
default: empty ++
|
default: empty ++
|
||||||
@@ -110,6 +120,12 @@ warp-on-scroll: ++
|
|||||||
default: false ++
|
default: false ++
|
||||||
Enables this module to consume all left over space dynamically.
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
*output-classes*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Specify additional CSS classes to be added to workspace indicators based on the output on which the associated workspace is located.
|
||||||
|
Keys are output names and values are class names like *${output}: {output-class}*.
|
||||||
|
Assignment in config is used to keep the stylesheet independent of the available outputs.
|
||||||
|
|
||||||
|
|
||||||
# FORMAT REPLACEMENTS
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
@@ -147,6 +163,7 @@ an empty list denoting all outputs.
|
|||||||
"3": [], // Always show a workspace with name '3', on all outputs if it does not exist
|
"3": [], // Always show a workspace with name '3', on all outputs if it does not exist
|
||||||
"4": ["eDP-1"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exist
|
"4": ["eDP-1"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exist
|
||||||
"5": ["eDP-1", "DP-2"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exist
|
"5": ["eDP-1", "DP-2"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exist
|
||||||
|
"6": ["MonitorMaker 3000 ABC0123"], // Always show a workspace with name '6' on outputs with an identifier 'MonitorMaker 3000 ABC0123' (usually a triple of vendor/model/serial)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -187,6 +204,13 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
"sway/workspaces": {
|
||||||
|
"format": "{index} - {name}",
|
||||||
|
"format-for-negative-index": "{name}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Style
|
# Style
|
||||||
|
|
||||||
- *#workspaces button*
|
- *#workspaces button*
|
||||||
@@ -197,3 +221,4 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
|||||||
- *#workspaces button.empty*
|
- *#workspaces button.empty*
|
||||||
- *#workspaces button.current_output*
|
- *#workspaces button.current_output*
|
||||||
- *#workspaces button#sway-workspace-${name}*
|
- *#workspaces button#sway-workspace-${name}*
|
||||||
|
- *#workspaces button.${output-class}*
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ Addressed by *temperature*
|
|||||||
|
|
||||||
*input-filename*: ++
|
*input-filename*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input*
|
The temperature filename of your *hwmon-path-abs* (also used by *hwmon-by-name*), e.g. *temp1_input*
|
||||||
|
|
||||||
|
*hwmon-by-name*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The substring to search for in */sys/class/hwmon/hwmonX/name* (where hwmonX is any folder in */sys/class/hwmon/*).
|
||||||
|
Waybar will search for every directory in */sys/class/hwmon/* and uses the directory in which the *name* matches *hwmon-by-name*.
|
||||||
|
|
||||||
*warning-threshold*: ++
|
*warning-threshold*: ++
|
||||||
typeof: integer ++
|
typeof: integer ++
|
||||||
|
|||||||
@@ -26,6 +26,16 @@ Addressed by *wayfire/workspaces*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
|
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
|
||||||
|
|
||||||
|
*disable-scroll*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.
|
||||||
|
|
||||||
|
*enable-bar-scroll*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to false, you can't scroll to cycle through workspaces from the entire bar. If set to true this behaviour is enabled.
|
||||||
|
|
||||||
*disable-markup*: ++
|
*disable-markup*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
|||||||
default: 1.0 ++
|
default: 1.0 ++
|
||||||
The speed at which to change the volume when scrolling.
|
The speed at which to change the volume when scrolling.
|
||||||
|
|
||||||
|
*scroll-scale*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: cubic_percent
|
||||||
|
The scale to use for the scrolling volume change. Options are 'linear', 'db', 'cubic', and 'cubic_percent'.
|
||||||
|
|
||||||
*on-click*: ++
|
*on-click*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Command to execute when clicked on the module.
|
Command to execute when clicked on the module.
|
||||||
@@ -107,7 +112,13 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
|||||||
|
|
||||||
# FORMAT REPLACEMENTS
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
*{volume}*: Volume in percentage.
|
*{volume}*: Volume in percentage, cubic scale as integer.
|
||||||
|
|
||||||
|
*{volume_linear}*: Volume in linear scale as float.
|
||||||
|
|
||||||
|
*{volume_cubic}*: Volume in cubic scale as float.
|
||||||
|
|
||||||
|
*{volume_db}*: Volume in decibel scale as float.
|
||||||
|
|
||||||
*{node_name}*: The node's nickname as reported by WirePlumber (*node.nick* property)
|
*{node_name}*: The node's nickname as reported by WirePlumber (*node.nick* property)
|
||||||
|
|
||||||
@@ -123,7 +134,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Separate Sink and Source Widgets
|
## Separate Sink and Source Widgets
|
||||||
|
|
||||||
```
|
```
|
||||||
"wireplumber#sink": {
|
"wireplumber#sink": {
|
||||||
@@ -143,6 +154,16 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Use a Different Scale
|
||||||
|
|
||||||
|
```
|
||||||
|
"wireplumber": {
|
||||||
|
"format": "{volume_db:.2f}dB",
|
||||||
|
"scroll-scale": "db",
|
||||||
|
"scroll-step": 2.5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#wireplumber*
|
- *#wireplumber*
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ Addressed by *wlr/taskbar*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.
|
If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.
|
||||||
|
|
||||||
|
*bar-css-states*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, application state is exposed as CSS classes on the Waybar
|
||||||
|
window. Maximized and fullscreen state is aggregated across applications
|
||||||
|
known to belong to the active workspace. See *Bar state style* below.
|
||||||
|
|
||||||
*format*: ++
|
*format*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: {icon} ++
|
default: {icon} ++
|
||||||
@@ -52,11 +59,21 @@ Addressed by *wlr/taskbar*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
|
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
|
||||||
|
|
||||||
|
*active-only*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, only the currently active application button is shown.
|
||||||
|
Other applications remain tracked and reappear when activated.
|
||||||
|
|
||||||
*sort-by-app-id*: ++
|
*sort-by-app-id*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
If set to true, group tasks by their app_id. Cannot be used with 'active-first'.
|
If set to true, group tasks by their app_id. Cannot be used with 'active-first'.
|
||||||
|
|
||||||
|
*justify*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The alignment of the text within the module's box, allowing options 'left', 'right', or 'center' to define the positioning.
|
||||||
|
|
||||||
*expand*: ++
|
*expand*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -161,3 +178,35 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
|
|||||||
- *#taskbar button.minimized*
|
- *#taskbar button.minimized*
|
||||||
- *#taskbar button.active*
|
- *#taskbar button.active*
|
||||||
- *#taskbar button.fullscreen*
|
- *#taskbar button.fullscreen*
|
||||||
|
|
||||||
|
# Bar state style
|
||||||
|
|
||||||
|
When *bar-css-states* is enabled, the following classes are added to
|
||||||
|
*window#waybar*:
|
||||||
|
|
||||||
|
- *window#waybar.toplevel-active*
|
||||||
|
- *window#waybar.toplevel-maximized*
|
||||||
|
- *window#waybar.toplevel-minimized*
|
||||||
|
- *window#waybar.toplevel-fullscreen*
|
||||||
|
|
||||||
|
The active, minimized classes describe the active application. The maximized
|
||||||
|
and fullscreen classes are set if any non-minimized application known to belong
|
||||||
|
to the active workspace has that state.
|
||||||
|
|
||||||
|
Workspace membership is learned when an application is activated and requires
|
||||||
|
the compositor to support *ext-workspace-v1*. Before an application has been
|
||||||
|
activated during the current Waybar session, its workspace may be unknown. On
|
||||||
|
compositors without *ext-workspace-v1*, these classes fall back to the active
|
||||||
|
application's state.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
window#waybar {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
window#waybar.toplevel-maximized {
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -375,6 +375,8 @@ A group may hide all but one element, showing them only on mouse hover. In order
|
|||||||
Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left.
|
Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left.
|
||||||
When the bar is vertical, it reads as top-to-bottom.
|
When the bar is vertical, it reads as top-to-bottom.
|
||||||
|
|
||||||
|
Group drawers are also given the `.expanded` CSS class when they are expanded.
|
||||||
|
|
||||||
```
|
```
|
||||||
"group/power": {
|
"group/power": {
|
||||||
"orientation": "inherit",
|
"orientation": "inherit",
|
||||||
|
|||||||
+20
@@ -182,6 +182,7 @@ src_files = files(
|
|||||||
'src/util/ustring_clen.cpp',
|
'src/util/ustring_clen.cpp',
|
||||||
'src/util/sanitize_str.cpp',
|
'src/util/sanitize_str.cpp',
|
||||||
'src/util/rewrite_string.cpp',
|
'src/util/rewrite_string.cpp',
|
||||||
|
'src/util/hosts_check.cpp',
|
||||||
'src/util/gtk_icon.cpp',
|
'src/util/gtk_icon.cpp',
|
||||||
'src/util/icon_loader.cpp',
|
'src/util/icon_loader.cpp',
|
||||||
'src/util/regex_collection.cpp',
|
'src/util/regex_collection.cpp',
|
||||||
@@ -349,6 +350,25 @@ if get_option('niri')
|
|||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if get_option('mango')
|
||||||
|
add_project_arguments('-DHAVE_MANGO', language: 'cpp')
|
||||||
|
src_files += files(
|
||||||
|
'src/modules/mango/backend.cpp',
|
||||||
|
'src/modules/mango/language.cpp',
|
||||||
|
'src/modules/mango/keymode.cpp',
|
||||||
|
'src/modules/mango/window.cpp',
|
||||||
|
'src/modules/mango/workspaces.cpp',
|
||||||
|
'src/modules/mango/layout.cpp'
|
||||||
|
)
|
||||||
|
man_files += files(
|
||||||
|
'man/waybar-mango-language.5.scd',
|
||||||
|
'man/waybar-mango-keymode.5.scd',
|
||||||
|
'man/waybar-mango-window.5.scd',
|
||||||
|
'man/waybar-mango-workspaces.5.scd',
|
||||||
|
'man/waybar-mango-layout.5.scd'
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
if true
|
if true
|
||||||
add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')
|
add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')
|
||||||
src_files += files(
|
src_files += files(
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for
|
|||||||
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
|
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
|
||||||
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
|
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
|
||||||
option('niri', type: 'boolean', description: 'Enable support for niri')
|
option('niri', type: 'boolean', description: 'Enable support for niri')
|
||||||
|
option('mango', type: 'boolean', description: 'Enable support for mango')
|
||||||
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')
|
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')
|
||||||
option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')
|
option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
},
|
},
|
||||||
"power-profiles-daemon": {
|
"power-profiles-daemon": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|||||||
+48
-1
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <gdkmm/pixbuf.h>
|
#include <gdkmm/pixbuf.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
||||||
@@ -9,6 +11,10 @@ AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const
|
|||||||
const std::string& format, uint16_t interval, bool ellipsize,
|
const std::string& format, uint16_t interval, bool ellipsize,
|
||||||
bool enable_click, bool enable_scroll)
|
bool enable_click, bool enable_scroll)
|
||||||
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
||||||
|
if (config["icon-size"].isUInt()) {
|
||||||
|
app_icon_size_ = config["icon-size"].asUInt();
|
||||||
|
}
|
||||||
|
image_.set_pixel_size(app_icon_size_);
|
||||||
event_box_.remove();
|
event_box_.remove();
|
||||||
label_.unset_name();
|
label_.unset_name();
|
||||||
label_.get_style_context()->remove_class(MODULE_CLASS);
|
label_.get_style_context()->remove_class(MODULE_CLASS);
|
||||||
@@ -55,13 +61,54 @@ AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const
|
|||||||
event_box_.add(box_);
|
event_box_.add(box_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, std::string> AIconLabel::extractIcon(const std::string& input) {
|
||||||
|
std::string icon_result = "";
|
||||||
|
std::string label_result = input;
|
||||||
|
try {
|
||||||
|
static const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))");
|
||||||
|
std::smatch icon_match;
|
||||||
|
if (std::regex_search(input, icon_match, icon_search)) {
|
||||||
|
icon_result = icon_match[0].str().substr(9);
|
||||||
|
|
||||||
|
static const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)");
|
||||||
|
label_result = std::regex_replace(input, clean_label_pattern, "");
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Error while parsing icon from label. {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(icon_result, label_result);
|
||||||
|
}
|
||||||
|
|
||||||
auto AIconLabel::update() -> void {
|
auto AIconLabel::update() -> void {
|
||||||
|
label_contains_icon = false;
|
||||||
|
|
||||||
|
auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str());
|
||||||
|
label_contains_icon = iconLabel.length() > 0;
|
||||||
|
|
||||||
|
if (label_contains_icon) {
|
||||||
|
label_.set_markup(cleanLabel);
|
||||||
|
|
||||||
|
if (iconLabel.front() == '/') {
|
||||||
|
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
||||||
|
auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size);
|
||||||
|
|
||||||
|
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||||
|
image_.get_window());
|
||||||
|
image_.set(surface);
|
||||||
|
image_.set_visible(true);
|
||||||
|
} else {
|
||||||
|
image_.set_from_icon_name(iconLabel, Gtk::ICON_SIZE_INVALID);
|
||||||
|
image_.set_visible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
image_.set_visible(image_.get_visible() && iconEnabled());
|
image_.set_visible(image_.get_visible() && iconEnabled());
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AIconLabel::iconEnabled() const {
|
bool AIconLabel::iconEnabled() const {
|
||||||
return config_["icon"].isBool() ? config_["icon"].asBool() : false;
|
return label_contains_icon || (config_["icon"].isBool() ? config_["icon"].asBool() : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|||||||
+9
-1
@@ -117,7 +117,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
|||||||
submenus_[key] = GTK_MENU_ITEM(item);
|
submenus_[key] = GTK_MENU_ITEM(item);
|
||||||
menuActionsMap_[key] = it->asString();
|
menuActionsMap_[key] = it->asString();
|
||||||
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
|
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
|
||||||
(gpointer)menuActionsMap_[key].c_str());
|
(gpointer)g_strdup(menuActionsMap_[key].c_str()));
|
||||||
}
|
}
|
||||||
g_object_unref(builder);
|
g_object_unref(builder);
|
||||||
} catch (std::runtime_error& e) {
|
} catch (std::runtime_error& e) {
|
||||||
@@ -209,6 +209,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ALabel::copyToClipboard(const std::string& literal) {
|
||||||
|
Gtk::Clipboard::get()->set_text(literal);
|
||||||
|
}
|
||||||
|
|
||||||
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
|
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
|
||||||
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
|
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
|
||||||
alt_ = !alt_;
|
alt_ = !alt_;
|
||||||
@@ -218,6 +222,10 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
|
|||||||
format_ = default_format_;
|
format_ = default_format_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config_["on-click-copy"].isBool() && config_["on-click-copy"].asBool()) {
|
||||||
|
copyToClipboard(label_.get_text());
|
||||||
|
}
|
||||||
return AModule::handleToggle(e);
|
return AModule::handleToggle(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
|||||||
std::find_if(eventMap_.cbegin(), eventMap_.cend(), [&config](const auto& eventEntry) {
|
std::find_if(eventMap_.cbegin(), eventMap_.cend(), [&config](const auto& eventEntry) {
|
||||||
// True if there is any non-release type event
|
// True if there is any non-release type event
|
||||||
return eventEntry.first.second != GdkEventType::GDK_BUTTON_RELEASE &&
|
return eventEntry.first.second != GdkEventType::GDK_BUTTON_RELEASE &&
|
||||||
config[eventEntry.second].isString();
|
(config[eventEntry.second].isString() || config[eventEntry.second].isBool());
|
||||||
}) != eventMap_.cend();
|
}) != eventMap_.cend();
|
||||||
|
|
||||||
if (enable_click || hasUserEvents) {
|
if (enable_click || hasUserEvents) {
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
#include <gtk-layer-shell.h>
|
#include <gtk-layer-shell.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
#include "factory.hpp"
|
#include "factory.hpp"
|
||||||
#include "group.hpp"
|
#include "group.hpp"
|
||||||
#include "util/enum.hpp"
|
#include "util/enum.hpp"
|
||||||
|
#include "util/hosts_check.hpp"
|
||||||
#include "util/kill_signal.hpp"
|
#include "util/kill_signal.hpp"
|
||||||
|
|
||||||
#ifdef HAVE_SWAY
|
#ifdef HAVE_SWAY
|
||||||
@@ -565,6 +567,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
|
|||||||
for (const auto& name : module_list) {
|
for (const auto& name : module_list) {
|
||||||
try {
|
try {
|
||||||
auto ref = name.asString();
|
auto ref = name.asString();
|
||||||
|
|
||||||
|
if (config[ref].isMember("hosts") && !waybar::util::valid_host(config[ref])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
AModule* module;
|
AModule* module;
|
||||||
|
|
||||||
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
|
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
|
||||||
|
|||||||
@@ -42,6 +42,13 @@
|
|||||||
#include "modules/niri/window.hpp"
|
#include "modules/niri/window.hpp"
|
||||||
#include "modules/niri/workspaces.hpp"
|
#include "modules/niri/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_MANGO
|
||||||
|
#include "modules/mango/language.hpp"
|
||||||
|
#include "modules/mango/keymode.hpp"
|
||||||
|
#include "modules/mango/window.hpp"
|
||||||
|
#include "modules/mango/workspaces.hpp"
|
||||||
|
#include "modules/mango/layout.hpp"
|
||||||
|
#endif
|
||||||
#ifdef HAVE_WAYFIRE
|
#ifdef HAVE_WAYFIRE
|
||||||
#include "modules/wayfire/window.hpp"
|
#include "modules/wayfire/window.hpp"
|
||||||
#include "modules/wayfire/workspaces.hpp"
|
#include "modules/wayfire/workspaces.hpp"
|
||||||
@@ -231,6 +238,23 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
|||||||
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
|
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_MANGO
|
||||||
|
if (ref == "mango/window") {
|
||||||
|
return new waybar::modules::mango::Window(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
if (ref == "mango/workspaces") {
|
||||||
|
return new waybar::modules::mango::Workspaces(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
if (ref == "mango/language") {
|
||||||
|
return new waybar::modules::mango::Language(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
if (ref == "mango/keymode") {
|
||||||
|
return new waybar::modules::mango::Keymode(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
if (ref == "mango/layout") {
|
||||||
|
return new waybar::modules::mango::Layout(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef HAVE_WAYFIRE
|
#ifdef HAVE_WAYFIRE
|
||||||
if (ref == "wayfire/window") {
|
if (ref == "wayfire/window") {
|
||||||
return new waybar::modules::wayfire::Window(id, bar_, config_[name]);
|
return new waybar::modules::wayfire::Window(id, bar_, config_[name]);
|
||||||
|
|||||||
+18
-1
@@ -63,6 +63,7 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
|||||||
? drawer_config["transition-left-to-right"].asBool()
|
? drawer_config["transition-left-to-right"].asBool()
|
||||||
: true);
|
: true);
|
||||||
click_to_reveal = drawer_config["click-to-reveal"].asBool();
|
click_to_reveal = drawer_config["click-to-reveal"].asBool();
|
||||||
|
reveal_delay = drawer_config["reveal-delay"].asInt();
|
||||||
|
|
||||||
const bool start_expanded =
|
const bool start_expanded =
|
||||||
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
|
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
|
||||||
@@ -95,22 +96,38 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
|||||||
void Group::show_group() {
|
void Group::show_group() {
|
||||||
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
||||||
revealer.set_reveal_child(true);
|
revealer.set_reveal_child(true);
|
||||||
|
box.get_style_context()->add_class("expanded");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Group::hide_group() {
|
void Group::hide_group() {
|
||||||
box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
||||||
revealer.set_reveal_child(false);
|
revealer.set_reveal_child(false);
|
||||||
|
box.get_style_context()->remove_class("expanded");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
|
bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
|
||||||
if (!click_to_reveal) {
|
if (!click_to_reveal) {
|
||||||
show_group();
|
if (reveal_delay > 0) {
|
||||||
|
if (reveal_timeout_.connected()) {
|
||||||
|
reveal_timeout_.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
reveal_timeout_ = Glib::signal_timeout().connect(
|
||||||
|
[this]() { show_group(); return false; },
|
||||||
|
reveal_delay);
|
||||||
|
} else {
|
||||||
|
show_group();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Group::handleMouseLeave(GdkEventCrossing* const& e) {
|
bool Group::handleMouseLeave(GdkEventCrossing* const& e) {
|
||||||
if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) {
|
if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) {
|
||||||
|
if (reveal_delay > 0 && reveal_timeout_.connected()) {
|
||||||
|
reveal_timeout_.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
hide_group();
|
hide_group();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -36,9 +36,15 @@ auto waybar::modules::Backlight::update() -> void {
|
|||||||
|
|
||||||
if (best->get_powered()) {
|
if (best->get_powered()) {
|
||||||
event_box_.show();
|
event_box_.show();
|
||||||
|
|
||||||
const uint8_t percent =
|
const uint8_t percent =
|
||||||
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
|
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
|
||||||
|
|
||||||
|
const uint8_t percent_exp =
|
||||||
|
best->get_max() == 0
|
||||||
|
? 100
|
||||||
|
: roundf(powf((float)best->get_actual() / best->get_max(), 1.0f / 2.718f) * 100);
|
||||||
|
|
||||||
// Get the state and apply state-specific format if available
|
// Get the state and apply state-specific format if available
|
||||||
auto state = getState(percent);
|
auto state = getState(percent);
|
||||||
std::string current_format = format_;
|
std::string current_format = format_;
|
||||||
@@ -49,8 +55,10 @@ auto waybar::modules::Backlight::update() -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string desc = fmt::format(fmt::runtime(current_format), fmt::arg("percent", percent),
|
std::string desc =
|
||||||
fmt::arg("icon", getIcon(percent)));
|
fmt::format(fmt::runtime(current_format), fmt::arg("percent", percent),
|
||||||
|
fmt::arg("percent_exp", percent_exp), fmt::arg("icon", getIcon(percent)),
|
||||||
|
fmt::arg("icon_exp", getIcon(percent_exp)));
|
||||||
label_.set_markup(desc);
|
label_.set_markup(desc);
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
std::string tooltip_format;
|
std::string tooltip_format;
|
||||||
|
|||||||
+29
-1
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "util/command.hpp"
|
#include "util/command.hpp"
|
||||||
#if defined(__FreeBSD__)
|
#if defined(__FreeBSD__)
|
||||||
@@ -33,6 +36,11 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
|
|||||||
}
|
}
|
||||||
udev_monitor_enable_receiving(mon_.get());
|
udev_monitor_enable_receiving(mon_.get());
|
||||||
|
|
||||||
|
if (config_["smooth-power"].isBool()) {
|
||||||
|
smoothPowerEnable_ = config_["smooth-power"].asBool();
|
||||||
|
if (smoothPowerEnable_ && config_["smooth-power-time-constant"].isNumeric())
|
||||||
|
time_constant_s_ = std::max(1.0, config_["smooth-power-time-constant"].asDouble());
|
||||||
|
}
|
||||||
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
|
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
|
||||||
#endif
|
#endif
|
||||||
spdlog::debug("battery: worker interval is {}", interval_.count());
|
spdlog::debug("battery: worker interval is {}", interval_.count());
|
||||||
@@ -577,11 +585,31 @@ waybar::modules::Battery::getInfos() {
|
|||||||
if (online && current_status != "Discharging") status = "Plugged";
|
if (online && current_status != "Discharging") status = "Plugged";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (total_energy_exists && total_power_exists && total_power != 0) {
|
||||||
|
if (!smoothPowerEnable_) {
|
||||||
|
smooth_power_ = total_power;
|
||||||
|
} else {
|
||||||
|
if (status != old_status_raw_) {
|
||||||
|
smooth_power_ = total_power;
|
||||||
|
last_t_ = std::chrono::steady_clock::now();
|
||||||
|
} else {
|
||||||
|
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||||
|
double dt_s =
|
||||||
|
std::chrono::duration_cast<std::chrono::duration<double> >(now - last_t_).count();
|
||||||
|
smooth_power_ = smooth_power_ + ((1 - std::exp(-dt_s / time_constant_s_)) *
|
||||||
|
(total_power - smooth_power_));
|
||||||
|
last_t_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_status_raw_ = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float time_remaining{0.0f};
|
float time_remaining{0.0f};
|
||||||
if (status == "Discharging" && time_to_empty_now_exists) {
|
if (status == "Discharging" && time_to_empty_now_exists) {
|
||||||
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
|
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
|
||||||
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
|
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
|
||||||
if (total_power != 0) time_remaining = (float)total_energy / total_power;
|
if (smooth_power_ != 0) time_remaining = (float)total_energy / smooth_power_;
|
||||||
} else if (status == "Charging" && time_to_full_now_exists) {
|
} else if (status == "Charging" && time_to_full_now_exists) {
|
||||||
if (time_to_full_now_exists && (time_to_full_now != 0))
|
if (time_to_full_now_exists && (time_to_full_now != 0))
|
||||||
time_remaining = -(float)time_to_full_now / 3600.0f;
|
time_remaining = -(float)time_to_full_now / 3600.0f;
|
||||||
|
|||||||
@@ -187,6 +187,45 @@ auto waybar::modules::Clock::update() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_tlpText_ = fmt_lib::vformat(m_locale_, m_tlpText_, fmt_lib::make_format_args(now));
|
m_tlpText_ = fmt_lib::vformat(m_locale_, m_tlpText_, fmt_lib::make_format_args(now));
|
||||||
|
|
||||||
|
// Pango doesn't support CSS classes but to continue using it while staying
|
||||||
|
// backwards compatible this approach uses post-posting to replace fake
|
||||||
|
// classes with attributes Pango does understand.
|
||||||
|
//
|
||||||
|
// The benefit of this approach is anyone using the original styling choices
|
||||||
|
// can continue doing that and folks can optionally opt into using classes.
|
||||||
|
//
|
||||||
|
// It's also forwards compatible to where if this implemention ever changes
|
||||||
|
// to support proper classes anyone using them will continue to work.
|
||||||
|
auto context = label_.get_style_context();
|
||||||
|
|
||||||
|
static const std::vector<std::pair<std::string, std::string>> calendar_class_map = {
|
||||||
|
{"calendar-today", "class='today'"},
|
||||||
|
{"calendar-days", "class='days'"},
|
||||||
|
{"calendar-weeks", "class='weeks'"},
|
||||||
|
{"calendar-weekdays", "class='weekdays'"},
|
||||||
|
{"calendar-months", "class='months'"}};
|
||||||
|
|
||||||
|
for (const auto& [css_class, search_str] : calendar_class_map) {
|
||||||
|
try {
|
||||||
|
context->add_class(css_class);
|
||||||
|
const Gdk::RGBA color = context->get_color();
|
||||||
|
context->remove_class(css_class);
|
||||||
|
|
||||||
|
const std::string replace_str = fmt::format(
|
||||||
|
"color='#{:02x}{:02x}{:02x}'", static_cast<int>(color.get_red() * 255),
|
||||||
|
static_cast<int>(color.get_green() * 255), static_cast<int>(color.get_blue() * 255));
|
||||||
|
|
||||||
|
m_tlpText_ = std::regex_replace(m_tlpText_, std::regex(search_str), replace_str);
|
||||||
|
} catch (const Glib::Error& e) {
|
||||||
|
spdlog::warn("Clock: Failed to fetch CSS color for {}: {}", css_class, e.what().raw());
|
||||||
|
continue;
|
||||||
|
} catch (...) {
|
||||||
|
// Catch-all for any other weirdness.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_tooltip_->set_markup(m_tlpText_);
|
m_tooltip_->set_markup(m_tlpText_);
|
||||||
label_.trigger_tooltip_query();
|
label_.trigger_tooltip_query();
|
||||||
}
|
}
|
||||||
@@ -514,6 +553,18 @@ using deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
|
|||||||
|
|
||||||
// Computations done similarly to Linux cal utility.
|
// Computations done similarly to Linux cal utility.
|
||||||
auto waybar::modules::Clock::first_day_of_week() -> weekday {
|
auto waybar::modules::Clock::first_day_of_week() -> weekday {
|
||||||
|
const auto firstdow = config_[kCldPlaceholder]["first-day-of-week"];
|
||||||
|
if (firstdow.isInt()) {
|
||||||
|
const int firstDay = firstdow.asInt();
|
||||||
|
if (!(firstDay >= 0 && firstDay <= 6)) {
|
||||||
|
spdlog::warn(
|
||||||
|
"Clock calender configuration first-day-of-week = {0} must be in range [0, 6]. Default "
|
||||||
|
"value is used instead",
|
||||||
|
firstDay);
|
||||||
|
} else {
|
||||||
|
return weekday{static_cast<unsigned>(firstDay)};
|
||||||
|
}
|
||||||
|
}
|
||||||
if (iso8601Calendar_) {
|
if (iso8601Calendar_) {
|
||||||
return Monday;
|
return Monday;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -51,13 +51,17 @@ auto waybar::modules::Cpu::update() -> void {
|
|||||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||||
std::vector<std::string> arg_names;
|
std::vector<std::string> arg_names;
|
||||||
arg_names.reserve(cpu_usage.size() * 2);
|
arg_names.reserve(cpu_usage.size() * 2);
|
||||||
|
std::string all_icons;
|
||||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||||
auto core_i = i - 1;
|
auto core_i = i - 1;
|
||||||
arg_names.push_back(fmt::format("usage{}", core_i));
|
arg_names.push_back(fmt::format("usage{}", core_i));
|
||||||
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
||||||
|
auto core_icon = getIcon(cpu_usage[i], icons);
|
||||||
|
all_icons += core_icon;
|
||||||
arg_names.push_back(fmt::format("icon{}", core_i));
|
arg_names.push_back(fmt::format("icon{}", core_i));
|
||||||
store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
|
store.push_back(fmt::arg(arg_names.back().c_str(), core_icon));
|
||||||
}
|
}
|
||||||
|
store.push_back(fmt::arg("icons", all_icons));
|
||||||
label_.set_markup(fmt::vformat(format, store));
|
label_.set_markup(fmt::vformat(format, store));
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
|
|||||||
@@ -38,13 +38,17 @@ auto waybar::modules::CpuUsage::update() -> void {
|
|||||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||||
std::vector<std::string> arg_names;
|
std::vector<std::string> arg_names;
|
||||||
arg_names.reserve(cpu_usage.size() * 2);
|
arg_names.reserve(cpu_usage.size() * 2);
|
||||||
|
std::string all_icons;
|
||||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||||
auto core_i = i - 1;
|
auto core_i = i - 1;
|
||||||
arg_names.push_back(fmt::format("usage{}", core_i));
|
arg_names.push_back(fmt::format("usage{}", core_i));
|
||||||
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
||||||
|
auto core_icon = getIcon(cpu_usage[i], icons);
|
||||||
|
all_icons += core_icon;
|
||||||
arg_names.push_back(fmt::format("icon{}", core_i));
|
arg_names.push_back(fmt::format("icon{}", core_i));
|
||||||
store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
|
store.push_back(fmt::arg(arg_names.back().c_str(), core_icon));
|
||||||
}
|
}
|
||||||
|
store.push_back(fmt::arg("icons", all_icons));
|
||||||
label_.set_markup(fmt::vformat(format, store));
|
label_.set_markup(fmt::vformat(format, store));
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
|
|||||||
+111
-62
@@ -1,15 +1,32 @@
|
|||||||
#include "modules/disk.hpp"
|
#include "modules/disk.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
using namespace waybar::util;
|
using namespace waybar::util;
|
||||||
|
|
||||||
waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
|
waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
|
||||||
: ALabel(config, "disk", id, "{}%", 30), path_("/") {
|
: ALabel(config, "disk", id, "{}%", 30), header_(""), paths_(), separator_(" ") {
|
||||||
thread_ = [this] {
|
thread_ = [this] {
|
||||||
dp.emit();
|
dp.emit();
|
||||||
thread_.sleep_for(interval_);
|
thread_.sleep_for(interval_);
|
||||||
};
|
};
|
||||||
if (config["path"].isString()) {
|
if (config["header"].isString()) {
|
||||||
path_ = config["path"].asString();
|
header_ = config["header"].asString();
|
||||||
|
}
|
||||||
|
if (config["path"].isString() && !config["paths"].isArray()) {
|
||||||
|
spdlog::warn("Disk: path is deprecated use paths instead!");
|
||||||
|
paths_.push_back(config["path"].asString());
|
||||||
|
}
|
||||||
|
if (config["paths"].isArray()) {
|
||||||
|
for (const auto& path : config["paths"]) {
|
||||||
|
paths_.push_back(path.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!config["path"].isString() && !config["paths"].isArray()) {
|
||||||
|
paths_.emplace_back("/");
|
||||||
|
}
|
||||||
|
if (config["separator"].isString()) {
|
||||||
|
separator_ = config["separator"].asString();
|
||||||
}
|
}
|
||||||
if (config["unit"].isString()) {
|
if (config["unit"].isString()) {
|
||||||
unit_ = config["unit"].asString();
|
unit_ = config["unit"].asString();
|
||||||
@@ -17,76 +34,108 @@ waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto waybar::modules::Disk::update() -> void {
|
auto waybar::modules::Disk::update() -> void {
|
||||||
struct statvfs /* {
|
std::string tooltip_label;
|
||||||
unsigned long f_bsize; // filesystem block size
|
std::string label = header_;
|
||||||
unsigned long f_frsize; // fragment size
|
|
||||||
fsblkcnt_t f_blocks; // size of fs in f_frsize units
|
|
||||||
fsblkcnt_t f_bfree; // # free blocks
|
|
||||||
fsblkcnt_t f_bavail; // # free blocks for unprivileged users
|
|
||||||
fsfilcnt_t f_files; // # inodes
|
|
||||||
fsfilcnt_t f_ffree; // # free inodes
|
|
||||||
fsfilcnt_t f_favail; // # free inodes for unprivileged users
|
|
||||||
unsigned long f_fsid; // filesystem ID
|
|
||||||
unsigned long f_flag; // mount flags
|
|
||||||
unsigned long f_namemax; // maximum filename length
|
|
||||||
}; */
|
|
||||||
stats;
|
|
||||||
int err = statvfs(path_.c_str(), &stats);
|
|
||||||
|
|
||||||
/* Conky options
|
bool had_valid_disk = false;
|
||||||
fs_bar - Bar that shows how much space is used
|
|
||||||
fs_free - Free space on a file system
|
|
||||||
fs_free_perc - Free percentage of space
|
|
||||||
fs_size - File system size
|
|
||||||
fs_used - File system used space
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (err != 0 || stats.f_blocks == 0) {
|
for (size_t i = 0; i < paths_.size(); ++i) {
|
||||||
event_box_.hide();
|
const auto& path = paths_[i];
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float specific_free, specific_used, specific_total, divisor;
|
struct statvfs /* {
|
||||||
|
unsigned long f_bsize; // filesystem block size
|
||||||
|
unsigned long f_frsize; // fragment size
|
||||||
|
fsblkcnt_t f_blocks; // size of fs in f_frsize units
|
||||||
|
fsblkcnt_t f_bfree; // # free blocks
|
||||||
|
fsblkcnt_t f_bavail; // # free blocks for unprivileged users
|
||||||
|
fsfilcnt_t f_files; // # inodes
|
||||||
|
fsfilcnt_t f_ffree; // # free inodes
|
||||||
|
fsfilcnt_t f_favail; // # free inodes for unprivileged users
|
||||||
|
unsigned long f_fsid; // filesystem ID
|
||||||
|
unsigned long f_flag; // mount flags
|
||||||
|
unsigned long f_namemax; // maximum filename length
|
||||||
|
}; */
|
||||||
|
stats;
|
||||||
|
|
||||||
divisor = calc_specific_divisor(unit_);
|
int err = statvfs(path.c_str(), &stats);
|
||||||
specific_free = (stats.f_bavail * stats.f_frsize) / divisor;
|
|
||||||
specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor;
|
|
||||||
specific_total = (stats.f_blocks * stats.f_frsize) / divisor;
|
|
||||||
|
|
||||||
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
|
/* Conky options
|
||||||
auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
|
fs_bar - Bar that shows how much space is used
|
||||||
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
|
fs_free - Free space on a file system
|
||||||
auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
|
fs_free_perc - Free percentage of space
|
||||||
|
fs_size - File system size
|
||||||
|
fs_used - File system used space
|
||||||
|
*/
|
||||||
|
|
||||||
auto format = format_;
|
if (err != 0 || stats.f_blocks == 0) {
|
||||||
auto state = getState(percentage_used);
|
spdlog::warn("Disk: statvfs failed for path '{}' (errno={})", path, errno);
|
||||||
if (!state.empty() && config_["format-" + state].isString()) {
|
continue;
|
||||||
format = config_["format-" + state].asString();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (format.empty()) {
|
float specific_free, specific_used, specific_total, divisor;
|
||||||
event_box_.hide();
|
|
||||||
} else {
|
divisor = calc_specific_divisor(unit_);
|
||||||
event_box_.show();
|
specific_free = (stats.f_bavail * stats.f_frsize) / divisor;
|
||||||
label_.set_markup(fmt::format(
|
specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor;
|
||||||
fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
specific_total = (stats.f_blocks * stats.f_frsize) / divisor;
|
||||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
|
||||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
|
||||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
|
||||||
fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)));
|
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
|
||||||
}
|
auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
|
||||||
|
|
||||||
|
std::string disk_format = format_;
|
||||||
|
auto state = getState(percentage_used);
|
||||||
|
if (!state.empty() && config_["format-" + state].isString()) {
|
||||||
|
disk_format = config_["format-" + state].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!disk_format.empty()) {
|
||||||
|
if (had_valid_disk) {
|
||||||
|
label += separator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
label += fmt::format(
|
||||||
|
fmt::runtime(disk_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||||
|
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
|
||||||
|
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
|
||||||
|
fmt::arg("total", total), fmt::arg("path", path),
|
||||||
|
fmt::arg("specific_free", specific_free), fmt::arg("specific_used", specific_used),
|
||||||
|
fmt::arg("specific_total", specific_total));
|
||||||
|
}
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
|
||||||
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
|
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
|
||||||
if (config_["tooltip-format"].isString()) {
|
if (config_["tooltip-format"].isString()) {
|
||||||
tooltip_format = config_["tooltip-format"].asString();
|
tooltip_format = config_["tooltip-format"].asString();
|
||||||
}
|
}
|
||||||
label_.set_tooltip_markup(fmt::format(
|
|
||||||
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
if (!tooltip_format.empty()) {
|
||||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
if (had_valid_disk) {
|
||||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
tooltip_label += "\n";
|
||||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
}
|
||||||
fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)));
|
|
||||||
|
tooltip_label += fmt::format(
|
||||||
|
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks,
|
||||||
|
fmt::arg("free", free),
|
||||||
|
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
|
||||||
|
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
|
||||||
|
fmt::arg("total", total), fmt::arg("path", path),
|
||||||
|
fmt::arg("specific_free", specific_free), fmt::arg("specific_used", specific_used),
|
||||||
|
fmt::arg("specific_total", specific_total));
|
||||||
|
}
|
||||||
|
|
||||||
|
had_valid_disk = true;
|
||||||
|
}
|
||||||
|
if (had_valid_disk) {
|
||||||
|
event_box_.show();
|
||||||
|
} else {
|
||||||
|
event_box_.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
label_.set_markup(label);
|
||||||
|
|
||||||
|
if (tooltipEnabled() && !tooltip_label.empty()) {
|
||||||
|
label_.set_tooltip_markup(tooltip_label);
|
||||||
}
|
}
|
||||||
// Call parent update
|
// Call parent update
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
@@ -109,7 +158,7 @@ float waybar::modules::Disk::calc_specific_divisor(const std::string& divisor) {
|
|||||||
return 1000.0 * 1000.0 * 1000.0 * 1000.0;
|
return 1000.0 * 1000.0 * 1000.0 * 1000.0;
|
||||||
} else if (divisor == "TiB") {
|
} else if (divisor == "TiB") {
|
||||||
return 1024.0 * 1024.0 * 1024.0 * 1024.0;
|
return 1024.0 * 1024.0 * 1024.0 * 1024.0;
|
||||||
} else { // default to Bytes if it is anything that we don't recongnise
|
} else { // default to Bytes if it is anything that we don't recognise
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-23
@@ -26,7 +26,7 @@ static void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {
|
static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {
|
||||||
// Intentionally empty
|
static_cast<Tags*>(data)->handle_active_output(zdwl_output_v2, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state,
|
static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state,
|
||||||
@@ -70,32 +70,31 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{
|
|||||||
|
|
||||||
static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
|
static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
|
||||||
const char* interface, uint32_t version) {
|
const char* interface, uint32_t version) {
|
||||||
|
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
|
||||||
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
|
auto* self = static_cast<Tags*>(data);
|
||||||
auto* self = static_cast<Tags*>(data);
|
|
||||||
|
|
||||||
if (self->status_manager_) {
|
if (self->status_manager_) {
|
||||||
zdwl_ipc_manager_v2_destroy(self->status_manager_);
|
zdwl_ipc_manager_v2_destroy(self->status_manager_);
|
||||||
self->status_manager_ = nullptr;
|
self->status_manager_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
|
self->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
|
||||||
wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
|
wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
|
||||||
}
|
|
||||||
|
|
||||||
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
|
||||||
auto* self = static_cast<Tags*>(data);
|
|
||||||
|
|
||||||
if (self->seat_) {
|
|
||||||
wl_seat_destroy(self->seat_);
|
|
||||||
self->seat_ = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
version = std::min<uint32_t>(version, 1);
|
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
|
auto* self = static_cast<Tags*>(data);
|
||||||
|
|
||||||
self->seat_ = static_cast<struct wl_seat*>(
|
if (self->seat_) {
|
||||||
wl_registry_bind(registry, name, &wl_seat_interface, version));
|
wl_seat_destroy(self->seat_);
|
||||||
}
|
self->seat_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = std::min<uint32_t>(version, 1);
|
||||||
|
|
||||||
|
self->seat_ =
|
||||||
|
static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
|
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
|
||||||
/* Ignore event */
|
/* Ignore event */
|
||||||
@@ -110,7 +109,11 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
|||||||
seat_{nullptr},
|
seat_{nullptr},
|
||||||
bar_(bar),
|
bar_(bar),
|
||||||
box_{bar.orientation, 0},
|
box_{bar.orientation, 0},
|
||||||
|
hide_vacant_(false),
|
||||||
output_status_{nullptr} {
|
output_status_{nullptr} {
|
||||||
|
if (config_["hide-vacant"].asBool()) {
|
||||||
|
hide_vacant_ = config_["hide-vacant"].asBool();
|
||||||
|
}
|
||||||
struct wl_display* display = Client::inst()->wl_display;
|
struct wl_display* display = Client::inst()->wl_display;
|
||||||
struct wl_registry* registry = wl_display_get_registry(display);
|
struct wl_registry* registry = wl_display_get_registry(display);
|
||||||
|
|
||||||
@@ -163,7 +166,7 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
|||||||
i <<= 1;
|
i <<= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||||
output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);
|
output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);
|
||||||
zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);
|
zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);
|
||||||
|
|
||||||
@@ -221,6 +224,24 @@ void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint
|
|||||||
} else {
|
} else {
|
||||||
button.get_style_context()->remove_class("urgent");
|
button.get_style_context()->remove_class("urgent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hide_vacant_ && !clients && !(state & TAG_ACTIVE)) {
|
||||||
|
button.set_visible(false);
|
||||||
|
} else {
|
||||||
|
button.set_visible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tags::handle_active_output(zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {
|
||||||
|
if (output_status_ == zdwl_output_v2) {
|
||||||
|
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||||
|
if (active == 0) {
|
||||||
|
buttons_[i].get_style_context()->remove_class("output");
|
||||||
|
} else {
|
||||||
|
buttons_[i].get_style_context()->add_class("output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace waybar::modules::dwl */
|
} /* namespace waybar::modules::dwl */
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ static void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {
|
static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {
|
||||||
// Intentionally empty
|
static_cast<Window*>(data)->handle_active(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state,
|
static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state,
|
||||||
@@ -74,7 +74,17 @@ static const wl_registry_listener registry_listener_impl = {.global = handle_glo
|
|||||||
.global_remove = handle_global_remove};
|
.global_remove = handle_global_remove};
|
||||||
|
|
||||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
: AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar) {
|
: AAppIconLabel(config, "window", id, "{}", 0, true),
|
||||||
|
bar_(bar),
|
||||||
|
active_(false),
|
||||||
|
hide_inactive_(false),
|
||||||
|
hide_empty_(false) {
|
||||||
|
if (config_["hide-inactive"].isBool()) {
|
||||||
|
hide_inactive_ = config["hide-inactive"].asBool();
|
||||||
|
}
|
||||||
|
if (config_["hide-empty"].isBool()) {
|
||||||
|
hide_empty_ = config["hide-empty"].asBool();
|
||||||
|
}
|
||||||
struct wl_display* display = Client::inst()->wl_display;
|
struct wl_display* display = Client::inst()->wl_display;
|
||||||
struct wl_registry* registry = wl_display_get_registry(display);
|
struct wl_registry* registry = wl_display_get_registry(display);
|
||||||
|
|
||||||
@@ -102,6 +112,8 @@ void Window::handle_title(const char* title) { title_ = Glib::Markup::escape_tex
|
|||||||
|
|
||||||
void Window::handle_appid(const char* appid) { appid_ = Glib::Markup::escape_text(appid); }
|
void Window::handle_appid(const char* appid) { appid_ = Glib::Markup::escape_text(appid); }
|
||||||
|
|
||||||
|
void Window::handle_active(const uint32_t active) { active_ = active != 0; }
|
||||||
|
|
||||||
void Window::handle_layout_symbol(const char* layout_symbol) {
|
void Window::handle_layout_symbol(const char* layout_symbol) {
|
||||||
layout_symbol_ = Glib::Markup::escape_text(layout_symbol);
|
layout_symbol_ = Glib::Markup::escape_text(layout_symbol);
|
||||||
}
|
}
|
||||||
@@ -118,6 +130,19 @@ void Window::handle_frame() {
|
|||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
label_.set_tooltip_markup(title_);
|
label_.set_tooltip_markup(title_);
|
||||||
}
|
}
|
||||||
|
if (hide_empty_ && title_.empty()) {
|
||||||
|
box_.set_visible(false);
|
||||||
|
} else {
|
||||||
|
if (active_) {
|
||||||
|
box_.get_style_context()->add_class("active");
|
||||||
|
box_.set_visible(true);
|
||||||
|
} else {
|
||||||
|
box_.get_style_context()->remove_class("active");
|
||||||
|
if (hide_inactive_) {
|
||||||
|
box_.set_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::dwl
|
} // namespace waybar::modules::dwl
|
||||||
|
|||||||
@@ -85,6 +85,25 @@ auto Language::update() -> void {
|
|||||||
label_.hide();
|
label_.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tooltip support
|
||||||
|
if (tooltipEnabled()) {
|
||||||
|
std::string tooltipFormat;
|
||||||
|
if (config_["tooltip-format"].isString()) {
|
||||||
|
tooltipFormat = config_["tooltip-format"].asString();
|
||||||
|
} else {
|
||||||
|
tooltipFormat = "{long}";
|
||||||
|
}
|
||||||
|
auto tooltipText = trim(fmt::format(
|
||||||
|
fmt::runtime(tooltipFormat),
|
||||||
|
fmt::arg("long", layout_.full_name),
|
||||||
|
fmt::arg("short", layout_.short_name),
|
||||||
|
fmt::arg("shortDescription", layout_.short_description),
|
||||||
|
fmt::arg("variant", layout_.variant)));
|
||||||
|
label_.set_tooltip_text(tooltipText);
|
||||||
|
} else {
|
||||||
|
label_.set_tooltip_text("");
|
||||||
|
}
|
||||||
|
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +149,9 @@ void Language::onEvent(const std::string& ev) {
|
|||||||
|
|
||||||
layoutName = waybar::util::sanitize_string(layoutName);
|
layoutName = waybar::util::sanitize_string(layoutName);
|
||||||
|
|
||||||
|
removeXkbLayoutCssClass();
|
||||||
layout_ = getLayout(layoutName);
|
layout_ = getLayout(layoutName);
|
||||||
|
addXkbLayoutCssClass();
|
||||||
|
|
||||||
spdlog::debug("hyprland language onevent with {}", layoutName);
|
spdlog::debug("hyprland language onevent with {}", layoutName);
|
||||||
|
|
||||||
@@ -152,6 +173,7 @@ void Language::initLanguage() {
|
|||||||
searcher = waybar::util::sanitize_string(searcher);
|
searcher = waybar::util::sanitize_string(searcher);
|
||||||
|
|
||||||
layout_ = getLayout(searcher);
|
layout_ = getLayout(searcher);
|
||||||
|
addXkbLayoutCssClass();
|
||||||
|
|
||||||
spdlog::debug("hyprland language initLanguage found {}", layout_.full_name);
|
spdlog::debug("hyprland language initLanguage found {}", layout_.full_name);
|
||||||
|
|
||||||
@@ -161,6 +183,15 @@ void Language::initLanguage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Language::removeXkbLayoutCssClass() -> void {
|
||||||
|
label_.get_style_context()->remove_class(layout_.short_name);
|
||||||
|
spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name);
|
||||||
|
}
|
||||||
|
auto Language::addXkbLayoutCssClass() -> void {
|
||||||
|
label_.get_style_context()->add_class(layout_.short_name);
|
||||||
|
spdlog::debug("hyprland language add new short_name css class {}", layout_.short_name);
|
||||||
|
}
|
||||||
|
|
||||||
auto Language::getLayout(const std::string& fullName) -> Layout {
|
auto Language::getLayout(const std::string& fullName) -> Layout {
|
||||||
auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
||||||
rxkb_context_parse_default_ruleset(context);
|
rxkb_context_parse_default_ruleset(context);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <glibmm/main.h>
|
#include <glibmm/main.h>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -10,6 +11,33 @@
|
|||||||
#include "util/command.hpp"
|
#include "util/command.hpp"
|
||||||
#include "util/icon_loader.hpp"
|
#include "util/icon_loader.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr std::string_view kCssClassPrefix = "ws-";
|
||||||
|
|
||||||
|
// Convert a workspace name to a valid CSS class name.
|
||||||
|
// Lowercases, replaces non-alphanumeric runs with single hyphens,
|
||||||
|
// and prefixes digit-leading names (CSS classes can't start with a digit).
|
||||||
|
std::string sanitizeCssClass(const std::string& name) {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(name.size() + kCssClassPrefix.size());
|
||||||
|
for (auto c : name) {
|
||||||
|
auto uc = static_cast<unsigned char>(c);
|
||||||
|
if (std::isalnum(uc)) {
|
||||||
|
result += static_cast<char>(std::tolower(uc));
|
||||||
|
} else if (!result.empty() && result.back() != '-') {
|
||||||
|
result += '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result.empty() && result.back() == '-') {
|
||||||
|
result.pop_back();
|
||||||
|
}
|
||||||
|
if (!result.empty() && std::isdigit(static_cast<unsigned char>(result.front()))) {
|
||||||
|
result.insert(0, kCssClassPrefix);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace waybar::modules::hyprland {
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
||||||
@@ -270,6 +298,11 @@ bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payloa
|
|||||||
std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map) {
|
std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map) {
|
||||||
spdlog::trace("Selecting icon for workspace {}", name());
|
spdlog::trace("Selecting icon for workspace {}", name());
|
||||||
if (isUrgent()) {
|
if (isUrgent()) {
|
||||||
|
auto urgentNamedIconIt = icons_map.find("urgent:" + name());
|
||||||
|
if (urgentNamedIconIt != icons_map.end()) {
|
||||||
|
return urgentNamedIconIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
auto urgentIconIt = icons_map.find("urgent");
|
auto urgentIconIt = icons_map.find("urgent");
|
||||||
if (urgentIconIt != icons_map.end()) {
|
if (urgentIconIt != icons_map.end()) {
|
||||||
return urgentIconIt->second;
|
return urgentIconIt->second;
|
||||||
@@ -288,6 +321,11 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isActive()) {
|
if (isActive()) {
|
||||||
|
auto activeNamedIconIt = icons_map.find("active:" + name());
|
||||||
|
if (activeNamedIconIt != icons_map.end()) {
|
||||||
|
return activeNamedIconIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
auto activeIconIt = icons_map.find("active");
|
auto activeIconIt = icons_map.find("active");
|
||||||
if (activeIconIt != icons_map.end()) {
|
if (activeIconIt != icons_map.end()) {
|
||||||
return activeIconIt->second;
|
return activeIconIt->second;
|
||||||
@@ -295,6 +333,11 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSpecial()) {
|
if (isSpecial()) {
|
||||||
|
auto specialNamedIconIt = icons_map.find("special:" + name());
|
||||||
|
if (specialNamedIconIt != icons_map.end()) {
|
||||||
|
return specialNamedIconIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
auto specialIconIt = icons_map.find("special");
|
auto specialIconIt = icons_map.find("special");
|
||||||
if (specialIconIt != icons_map.end()) {
|
if (specialIconIt != icons_map.end()) {
|
||||||
return specialIconIt->second;
|
return specialIconIt->second;
|
||||||
@@ -335,12 +378,22 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Workspace::update(const std::string& workspace_icon) {
|
void Workspace::update(const std::string& workspace_icon) {
|
||||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||||
m_button.hide();
|
m_button.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
if (this->m_workspaceManager.hideActive() && \
|
||||||
|
this->isActive() && \
|
||||||
|
!this->isPersistent() && \
|
||||||
|
!this->isSpecial()) {
|
||||||
|
// clang-format on
|
||||||
|
m_button.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// clang-format off
|
||||||
if (this->m_workspaceManager.activeOnly() && \
|
if (this->m_workspaceManager.activeOnly() && \
|
||||||
!this->isActive() && \
|
!this->isActive() && \
|
||||||
!this->isPersistent() && \
|
!this->isPersistent() && \
|
||||||
@@ -367,6 +420,17 @@ void Workspace::update(const std::string& workspace_icon) {
|
|||||||
addOrRemoveClass(styleContext, isVisible(), "visible");
|
addOrRemoveClass(styleContext, isVisible(), "visible");
|
||||||
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
||||||
|
|
||||||
|
// Add workspace name as CSS class for per-workspace styling
|
||||||
|
if (!m_prevNameClass.empty()) {
|
||||||
|
styleContext->remove_class(m_prevNameClass);
|
||||||
|
m_prevNameClass.clear();
|
||||||
|
}
|
||||||
|
auto nameClass = sanitizeCssClass(name());
|
||||||
|
if (!nameClass.empty()) {
|
||||||
|
styleContext->add_class(nameClass);
|
||||||
|
m_prevNameClass = nameClass;
|
||||||
|
}
|
||||||
|
|
||||||
std::string windows;
|
std::string windows;
|
||||||
// Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no
|
// Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no
|
||||||
// need to compute this if enableTaskbar() is true
|
// need to compute this if enableTaskbar() is true
|
||||||
@@ -374,13 +438,14 @@ void Workspace::update(const std::string& workspace_icon) {
|
|||||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||||
|
|
||||||
bool isNotFirst = false;
|
bool isNotFirst = false;
|
||||||
|
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
for (const auto& window_repr : m_windowMap) {
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
if (isNotFirst) {
|
if (isNotFirst) {
|
||||||
windows.append(windowSeparator);
|
windows.append(windowSeparator);
|
||||||
}
|
}
|
||||||
isNotFirst = true;
|
isNotFirst = true;
|
||||||
windows.append(window_repr.repr_rewrite);
|
windows.append(it->repr_rewrite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,12 +536,16 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||||
for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) {
|
auto rend_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.rend() : m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
|
for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) {
|
||||||
processWindow(*it);
|
processWindow(*it);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const auto& window_repr : m_windowMap) {
|
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||||
processWindow(window_repr);
|
|
||||||
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
|
processWindow(*it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -662,6 +662,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
|
|||||||
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
|
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
|
||||||
populateBoolConfig(config, "persistent-only", m_persistentOnly);
|
populateBoolConfig(config, "persistent-only", m_persistentOnly);
|
||||||
populateBoolConfig(config, "active-only", m_activeOnly);
|
populateBoolConfig(config, "active-only", m_activeOnly);
|
||||||
|
populateBoolConfig(config, "hide-active", m_hideActive);
|
||||||
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
|
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
|
||||||
populateBoolConfig(config, "enable-bar-scroll", m_barScroll);
|
populateBoolConfig(config, "enable-bar-scroll", m_barScroll);
|
||||||
|
|
||||||
@@ -670,6 +671,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
|
|||||||
populateIgnoreWorkspacesConfig(config);
|
populateIgnoreWorkspacesConfig(config);
|
||||||
populateFormatWindowSeparatorConfig(config);
|
populateFormatWindowSeparatorConfig(config);
|
||||||
populateWindowRewriteConfig(config);
|
populateWindowRewriteConfig(config);
|
||||||
|
populateMaxWindowsConfig(config);
|
||||||
|
|
||||||
if (withWindows) {
|
if (withWindows) {
|
||||||
populateWorkspaceTaskbarConfig(config);
|
populateWorkspaceTaskbarConfig(config);
|
||||||
@@ -751,6 +753,15 @@ auto Workspaces::populateWindowRewriteConfig(const Json::Value& config) -> void
|
|||||||
[this](std::string& window_rule) { return windowRewritePriorityFunction(window_rule); });
|
[this](std::string& window_rule) { return windowRewritePriorityFunction(window_rule); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Workspaces::populateMaxWindowsConfig(const Json::Value& config) -> void {
|
||||||
|
if (config["max-windows"].isInt()) {
|
||||||
|
m_maxWindows = config["max-windows"].asInt();
|
||||||
|
if (m_maxWindows < 0) {
|
||||||
|
m_maxWindows = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value& config) -> void {
|
auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value& config) -> void {
|
||||||
const auto& workspaceTaskbar = config["workspace-taskbar"];
|
const auto& workspaceTaskbar = config["workspace-taskbar"];
|
||||||
if (!workspaceTaskbar.isObject()) {
|
if (!workspaceTaskbar.isObject()) {
|
||||||
|
|||||||
+13
-5
@@ -1,5 +1,7 @@
|
|||||||
#include "modules/image.hpp"
|
#include "modules/image.hpp"
|
||||||
|
|
||||||
|
#include <config.hpp>
|
||||||
|
|
||||||
waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
|
waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
|
||||||
: AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
|
: AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||||
box_.pack_start(image_);
|
box_.pack_start(image_);
|
||||||
@@ -35,6 +37,13 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
|
|||||||
size_ = 16;
|
size_ = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config_["path"].isString()) {
|
||||||
|
auto result = Config::tryExpandPath(config_["path"].asString(), "");
|
||||||
|
path_ = result.empty() ? "" : result.front();
|
||||||
|
} else {
|
||||||
|
path_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
delayWorker();
|
delayWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +63,12 @@ void waybar::modules::Image::refresh(int sig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto waybar::modules::Image::update() -> void {
|
auto waybar::modules::Image::update() -> void {
|
||||||
if (config_["path"].isString()) {
|
if (config_["exec"].isString()) {
|
||||||
path_ = config_["path"].asString();
|
|
||||||
} else if (config_["exec"].isString()) {
|
|
||||||
output_ = util::command::exec(config_["exec"].asString(), "");
|
output_ = util::command::exec(config_["exec"].asString(), "");
|
||||||
parseOutputRaw();
|
parseOutputRaw();
|
||||||
} else {
|
// expand path if "~" or "$HOME" is present in original path
|
||||||
path_ = "";
|
auto result = Config::tryExpandPath(path_, "");
|
||||||
|
path_ = result.empty() ? "" : result.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) {
|
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) {
|
||||||
|
|||||||
@@ -78,6 +78,53 @@ auto supportsLockStates(const libevdev* dev) -> bool {
|
|||||||
libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
|
libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto isCommonFormatIcons(const Json::Value& config) -> bool {
|
||||||
|
return config["format-icons"].isObject() && (config["format-icons"]["locked"].isString() ||
|
||||||
|
config["format-icons"]["unlocked"].isString());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keyStateToIcons(const Json::Value& config)
|
||||||
|
-> std::unordered_map<std::string, std::vector<std::string>> {
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> key_icon_states;
|
||||||
|
std::vector<std::string> default_icons = {"unlocked", "locked"};
|
||||||
|
|
||||||
|
if (isCommonFormatIcons(config)) {
|
||||||
|
std::vector<std::string> icons = {
|
||||||
|
config["format-icons"]["unlocked"].isString()
|
||||||
|
? config["format-icons"]["unlocked"].asString()
|
||||||
|
: "unlocked",
|
||||||
|
config["format-icons"]["locked"].isString() ? config["format-icons"]["locked"].asString()
|
||||||
|
: "locked",
|
||||||
|
};
|
||||||
|
key_icon_states["Lock"] = icons;
|
||||||
|
return key_icon_states;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found_any = false;
|
||||||
|
for (const auto& key : std::vector<std::string>{"numlock", "capslock", "scrolllock"}) {
|
||||||
|
std::string map_key = key.substr(0, key.length() - 4);
|
||||||
|
map_key[0] = std::toupper(map_key[0]);
|
||||||
|
if (config["format-icons"].isObject() && config["format-icons"][key].isObject()) {
|
||||||
|
std::string unlocked = config["format-icons"][key]["unlocked"].isString()
|
||||||
|
? config["format-icons"][key]["unlocked"].asString()
|
||||||
|
: "unlocked";
|
||||||
|
std::string locked = config["format-icons"][key]["locked"].isString()
|
||||||
|
? config["format-icons"][key]["locked"].asString()
|
||||||
|
: "locked";
|
||||||
|
key_icon_states[map_key] = {unlocked, locked};
|
||||||
|
found_any = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_any) {
|
||||||
|
key_icon_states["Num"] = default_icons;
|
||||||
|
key_icon_states["Caps"] = default_icons;
|
||||||
|
key_icon_states["Scroll"] = default_icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key_icon_states;
|
||||||
|
}
|
||||||
|
|
||||||
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
|
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
|
||||||
const Json::Value& config)
|
const Json::Value& config)
|
||||||
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
||||||
@@ -98,12 +145,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
|||||||
: "{name} {icon}"),
|
: "{name} {icon}"),
|
||||||
interval_(
|
interval_(
|
||||||
std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
|
std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
|
||||||
icon_locked_(config_["format-icons"]["locked"].isString()
|
key_icon_states_(keyStateToIcons(config_)),
|
||||||
? config_["format-icons"]["locked"].asString()
|
|
||||||
: "locked"),
|
|
||||||
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
|
|
||||||
? config_["format-icons"]["unlocked"].asString()
|
|
||||||
: "unlocked"),
|
|
||||||
devices_path_("/dev/input/"),
|
devices_path_("/dev/input/"),
|
||||||
libinput_(nullptr),
|
libinput_(nullptr),
|
||||||
libinput_devices_({}) {
|
libinput_devices_({}) {
|
||||||
@@ -290,7 +332,7 @@ auto waybar::modules::KeyboardState::update() -> void {
|
|||||||
bool state;
|
bool state;
|
||||||
Gtk::Label& label;
|
Gtk::Label& label;
|
||||||
const std::string& format;
|
const std::string& format;
|
||||||
const char* name;
|
const std::string name;
|
||||||
} label_states[] = {
|
} label_states[] = {
|
||||||
{(bool)numl, numlock_label_, numlock_format_, "Num"},
|
{(bool)numl, numlock_label_, numlock_format_, "Num"},
|
||||||
{(bool)capsl, capslock_label_, capslock_format_, "Caps"},
|
{(bool)capsl, capslock_label_, capslock_format_, "Caps"},
|
||||||
@@ -298,8 +340,21 @@ auto waybar::modules::KeyboardState::update() -> void {
|
|||||||
};
|
};
|
||||||
for (auto& label_state : label_states) {
|
for (auto& label_state : label_states) {
|
||||||
std::string text;
|
std::string text;
|
||||||
|
std::string map_key = isCommonFormatIcons(config_) ? "Lock" : label_state.name;
|
||||||
|
|
||||||
|
if (key_icon_states_.find(map_key) == key_icon_states_.end()) {
|
||||||
|
spdlog::warn("keyboard-state: Missing icon configuration for '{}'", map_key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& icons = key_icon_states_[map_key];
|
||||||
|
if (icons.size() < 2) {
|
||||||
|
spdlog::warn("keyboard-state: Invalid icon vector size for '{}'", map_key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
text = fmt::format(fmt::runtime(label_state.format),
|
text = fmt::format(fmt::runtime(label_state.format),
|
||||||
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
|
fmt::arg("icon", label_state.state ? icons[1] : icons[0]),
|
||||||
fmt::arg("name", label_state.name));
|
fmt::arg("name", label_state.name));
|
||||||
label_state.label.set_markup(text);
|
label_state.label.set_markup(text);
|
||||||
if (label_state.state) {
|
if (label_state.state) {
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
#include "modules/mango/backend.hpp"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util/scoped_fd.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
int IPC::connectToSocket() {
|
||||||
|
const char* socket_path = getenv("MANGO_INSTANCE_SIGNATURE");
|
||||||
|
if (!socket_path) {
|
||||||
|
throw std::runtime_error("Mango IPC: MANGO_INSTANCE_SIGNATURE not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||||
|
if (fd == -1) throw std::runtime_error("socket() failed");
|
||||||
|
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||||
|
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||||
|
|
||||||
|
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
throw std::runtime_error("connect() failed");
|
||||||
|
}
|
||||||
|
return fd.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value IPC::sendCommand(const std::string& cmd) {
|
||||||
|
util::ScopedFd fd(IPC::connectToSocket());
|
||||||
|
std::string full_cmd = cmd + "\n";
|
||||||
|
|
||||||
|
ssize_t total_written = 0;
|
||||||
|
while (total_written < (ssize_t)full_cmd.size()) {
|
||||||
|
ssize_t res = write(fd, full_cmd.c_str() + total_written, full_cmd.size() - total_written);
|
||||||
|
if (res < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
throw std::runtime_error("Failed to write command");
|
||||||
|
}
|
||||||
|
total_written += res;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[4096];
|
||||||
|
std::string response;
|
||||||
|
while (true) {
|
||||||
|
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
if (n <= 0) {
|
||||||
|
if (n == 0) break;
|
||||||
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||||
|
throw std::runtime_error("Read error");
|
||||||
|
}
|
||||||
|
buf[n] = '\0';
|
||||||
|
response += buf;
|
||||||
|
if (response.find('\n') != std::string::npos) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value root;
|
||||||
|
std::istringstream iss(response);
|
||||||
|
Json::CharReaderBuilder builder;
|
||||||
|
std::string errors;
|
||||||
|
if (!Json::parseFromStream(builder, iss, &root, &errors)) {
|
||||||
|
throw std::runtime_error("JSON parse error: " + errors);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value IPC::send(const Json::Value& request) {
|
||||||
|
if (!request.isMember("command")) {
|
||||||
|
throw std::runtime_error("Mango IPC: request must have 'command' field");
|
||||||
|
}
|
||||||
|
return sendCommand(request["command"].asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::sendAsync(const Json::Value& request) {
|
||||||
|
if (!request.isMember("command")) {
|
||||||
|
spdlog::error("Mango IPC: request must have 'command' field");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string cmd = request["command"].asString();
|
||||||
|
|
||||||
|
std::thread([cmd]() {
|
||||||
|
try {
|
||||||
|
IPC::sendCommand(cmd);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("IPC async send failed: {}", e.what());
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::IPC() : sockfd_(-1), active_client_(Json::nullValue) { startIPC(); }
|
||||||
|
|
||||||
|
IPC::~IPC() {
|
||||||
|
if (sockfd_ != -1) close(sockfd_);
|
||||||
|
if (ipc_thread_.joinable()) ipc_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::startIPC() {
|
||||||
|
sockfd_ = IPC::connectToSocket();
|
||||||
|
|
||||||
|
ipc_thread_ = std::thread([this]() {
|
||||||
|
spdlog::info("Mango IPC thread started");
|
||||||
|
|
||||||
|
struct pollfd pfd;
|
||||||
|
pfd.fd = sockfd_;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
|
||||||
|
const std::vector<std::string> subs = {"watch all-monitors"};
|
||||||
|
for (const auto& cmd : subs) {
|
||||||
|
if (write(sockfd_, cmd.c_str(), cmd.size()) != (ssize_t)cmd.size() ||
|
||||||
|
write(sockfd_, "\n", 1) != 1) {
|
||||||
|
spdlog::error("Failed to subscribe to {}", cmd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[4096];
|
||||||
|
std::string buffer;
|
||||||
|
while (true) {
|
||||||
|
int ret = poll(&pfd, 1, 1000);
|
||||||
|
if (ret == 0) continue;
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
spdlog::error("IPC poll error: {}", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||||
|
spdlog::info("Mango IPC socket closed or invalid");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pfd.revents & POLLIN) {
|
||||||
|
ssize_t n = read(sockfd_, buf, sizeof(buf));
|
||||||
|
if (n == 0) {
|
||||||
|
spdlog::info("Mango IPC connection closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
spdlog::error("IPC read error: {}", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buffer.append(buf, n);
|
||||||
|
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = buffer.find('\n')) != std::string::npos) {
|
||||||
|
std::string line = buffer.substr(0, pos);
|
||||||
|
buffer.erase(0, pos + 1);
|
||||||
|
if (line.empty()) continue;
|
||||||
|
try {
|
||||||
|
parseIPC(line);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Failed to parse IPC line: {} - {}", line, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::parseIPC(const std::string& line) {
|
||||||
|
Json::Value root;
|
||||||
|
Json::CharReaderBuilder builder;
|
||||||
|
std::string errors;
|
||||||
|
std::istringstream iss(line);
|
||||||
|
if (!Json::parseFromStream(builder, iss, &root, &errors)) {
|
||||||
|
throw std::runtime_error("JSON parse error: " + errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.isMember("monitors") && root["monitors"].isArray()) {
|
||||||
|
for (const auto& mon : root["monitors"]) {
|
||||||
|
handleMonitorUpdate(mon);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value active_monitor;
|
||||||
|
for (const auto& mon : root["monitors"]) {
|
||||||
|
if (mon["active"].asBool()) {
|
||||||
|
active_monitor = mon;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!active_monitor.isNull()) {
|
||||||
|
const auto& active_client = active_monitor["active_client"];
|
||||||
|
updateFocusingClient(active_client);
|
||||||
|
|
||||||
|
if (active_monitor.isMember("keyboardlayout")) {
|
||||||
|
updateKeyboardLayout(active_monitor["keyboardlayout"].asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_monitor.isMember("keymode")) {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
keymode_ = active_monitor["keymode"].asString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EventHandler*> handlers_to_notify;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||||
|
for (auto& [ev, handler] : callbacks_) {
|
||||||
|
if (ev == "monitor") {
|
||||||
|
handlers_to_notify.push_back(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* handler : handlers_to_notify) {
|
||||||
|
handler->onEvent(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::debug("Unhandled IPC message: {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Json::Value> IPC::getMonitors() const {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
return monitors_;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC& IPC::getInstance() {
|
||||||
|
static IPC instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value IPC::getMonitor(const std::string& name) {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
auto it = monitors_.find(name);
|
||||||
|
if (it != monitors_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return Json::nullValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IPC::getKeyboardLayout() const {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
return keyboard_layout_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IPC::getKeymode() const {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
return keymode_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value IPC::getActiveClientForMonitor(const std::string& name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
auto it = monitors_.find(name);
|
||||||
|
if (it != monitors_.end() && it->second.isMember("active_client")) {
|
||||||
|
return it->second["active_client"];
|
||||||
|
}
|
||||||
|
return Json::nullValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IPC::getLayoutSymbolForMonitor(const std::string& name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
auto it = monitors_.find(name);
|
||||||
|
if (it != monitors_.end() && it->second.isMember("layout_symbol")) {
|
||||||
|
return it->second["layout_symbol"].asString();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::handleMonitorUpdate(const Json::Value& mon) {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
monitors_[mon["name"].asString()] = mon;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::updateFocusingClient(const Json::Value& client) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
active_client_ = client;
|
||||||
|
|
||||||
|
if (client.isNull() || !client.isObject() || client["id"].isNull()) {
|
||||||
|
focusing_client_id_ = 0;
|
||||||
|
} else {
|
||||||
|
focusing_client_id_ = client["id"].asUInt64();
|
||||||
|
clients_[focusing_client_id_] = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::updateKeyboardLayout(const std::string& layout) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
keyboard_layout_ = layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::registerForIPC(const std::string& ev, EventHandler* handler) {
|
||||||
|
if (!handler) return;
|
||||||
|
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||||
|
callbacks_.emplace_back(ev, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::unregisterForIPC(EventHandler* handler) {
|
||||||
|
if (!handler) return;
|
||||||
|
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||||
|
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
|
||||||
|
if (it->second == handler)
|
||||||
|
it = callbacks_.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#include "modules/mango/keymode.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
Keymode::Keymode(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: ALabel(config, "keymode", id, "{}", 0, false), bar_(bar) {
|
||||||
|
IPC::getInstance().registerForIPC("monitor", this);
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Keymode::~Keymode() { IPC::getInstance().unregisterForIPC(this); }
|
||||||
|
|
||||||
|
void Keymode::onEvent(const Json::Value& ev) { dp.emit(); }
|
||||||
|
|
||||||
|
void Keymode::doUpdate() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
std::string current = IPC::getInstance().getKeymode();
|
||||||
|
|
||||||
|
// if keymode is empty, hide the label
|
||||||
|
if (current.empty()) {
|
||||||
|
label_.hide();
|
||||||
|
last_keymode_.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if keymode is the same as last time, skip style changes
|
||||||
|
if (current != last_keymode_) {
|
||||||
|
if (!last_keymode_.empty()) label_.get_style_context()->remove_class(last_keymode_);
|
||||||
|
label_.get_style_context()->add_class(current);
|
||||||
|
last_keymode_ = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// support config's format-keymode custom format (such as format-default, format-resize, etc.)
|
||||||
|
std::string text;
|
||||||
|
std::string format_key = "format-" + current;
|
||||||
|
if (config_.isMember(format_key)) {
|
||||||
|
text = fmt::format(fmt::runtime(config_[format_key].asString()), fmt::arg("mode", current));
|
||||||
|
} else {
|
||||||
|
text = fmt::format(fmt::runtime(format_), fmt::arg("mode", current));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text.empty()) {
|
||||||
|
label_.show();
|
||||||
|
label_.set_markup(text);
|
||||||
|
} else {
|
||||||
|
label_.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keymode::update() {
|
||||||
|
doUpdate();
|
||||||
|
ALabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#include "modules/mango/language.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <xkbcommon/xkbregistry.h>
|
||||||
|
|
||||||
|
#include "util/string.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: ALabel(config, "language", id, "{}", 0, false), bar_(bar), rxkb_ctx_(nullptr) {
|
||||||
|
rxkb_ctx_ = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
||||||
|
if (rxkb_ctx_) {
|
||||||
|
rxkb_context_parse_default_ruleset(rxkb_ctx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::getInstance().registerForIPC("monitor", this);
|
||||||
|
updateFromIPC();
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Language::~Language() {
|
||||||
|
IPC::getInstance().unregisterForIPC(this);
|
||||||
|
if (rxkb_ctx_) rxkb_context_unref(rxkb_ctx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Language::updateFromIPC() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
std::string layout = IPC::getInstance().getKeyboardLayout();
|
||||||
|
|
||||||
|
layouts_.clear();
|
||||||
|
if (!layout.empty()) {
|
||||||
|
Layout l = getLayout(layout);
|
||||||
|
layouts_.push_back(l);
|
||||||
|
current_idx_ = 0;
|
||||||
|
} else {
|
||||||
|
current_idx_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Language::doUpdate() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (layouts_.empty() || current_idx_ >= layouts_.size()) {
|
||||||
|
label_.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& layout = layouts_[current_idx_];
|
||||||
|
|
||||||
|
if (!last_short_name_.empty()) label_.get_style_context()->remove_class(last_short_name_);
|
||||||
|
if (!layout.short_name.empty()) {
|
||||||
|
label_.get_style_context()->add_class(layout.short_name);
|
||||||
|
last_short_name_ = layout.short_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string layoutName;
|
||||||
|
std::string variant_key = "format-" + layout.short_description + "-" + layout.variant;
|
||||||
|
if (!layout.variant.empty() && config_.isMember(variant_key)) {
|
||||||
|
layoutName =
|
||||||
|
fmt::format(fmt::runtime(config_[variant_key].asString()),
|
||||||
|
fmt::arg("long", layout.full_name), fmt::arg("short", layout.short_name),
|
||||||
|
fmt::arg("shortDescription", layout.short_description),
|
||||||
|
fmt::arg("variant", layout.variant));
|
||||||
|
} else if (config_.isMember("format-" + layout.short_description)) {
|
||||||
|
std::string key = "format-" + layout.short_description;
|
||||||
|
layoutName =
|
||||||
|
fmt::format(fmt::runtime(config_[key].asString()), fmt::arg("long", layout.full_name),
|
||||||
|
fmt::arg("short", layout.short_name),
|
||||||
|
fmt::arg("shortDescription", layout.short_description),
|
||||||
|
fmt::arg("variant", layout.variant));
|
||||||
|
} else {
|
||||||
|
layoutName = fmt::format(fmt::runtime(format_), fmt::arg("long", layout.full_name),
|
||||||
|
fmt::arg("short", layout.short_name),
|
||||||
|
fmt::arg("shortDescription", layout.short_description),
|
||||||
|
fmt::arg("variant", layout.variant));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!layoutName.empty()) {
|
||||||
|
label_.show();
|
||||||
|
label_.set_markup(layoutName);
|
||||||
|
} else {
|
||||||
|
label_.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Language::update() {
|
||||||
|
updateFromIPC();
|
||||||
|
doUpdate();
|
||||||
|
ALabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Language::onEvent(const Json::Value& ev) {
|
||||||
|
updateFromIPC();
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Language::Layout Language::getLayout(const std::string& fullName) {
|
||||||
|
if (rxkb_ctx_) {
|
||||||
|
rxkb_layout* layout = rxkb_layout_first(rxkb_ctx_);
|
||||||
|
while (layout != nullptr) {
|
||||||
|
std::string desc = rxkb_layout_get_description(layout);
|
||||||
|
if (desc == fullName) {
|
||||||
|
std::string short_name = rxkb_layout_get_name(layout);
|
||||||
|
const char* variant_ptr = rxkb_layout_get_variant(layout);
|
||||||
|
std::string variant = variant_ptr ? variant_ptr : "";
|
||||||
|
const char* brief_ptr = rxkb_layout_get_brief(layout);
|
||||||
|
std::string short_description = brief_ptr ? brief_ptr : "";
|
||||||
|
|
||||||
|
if (short_description.empty()) {
|
||||||
|
short_description = short_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
short_description = short_name;
|
||||||
|
|
||||||
|
Layout info{desc, short_name, variant, short_description};
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
layout = rxkb_layout_next(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::warn("mango language: rxkb failed to find layout '{}', using string parsing fallback",
|
||||||
|
fullName);
|
||||||
|
|
||||||
|
Layout l;
|
||||||
|
l.full_name = fullName;
|
||||||
|
l.variant = "";
|
||||||
|
|
||||||
|
size_t paren_start = fullName.find('(');
|
||||||
|
size_t paren_end = fullName.find(')');
|
||||||
|
if (paren_start != std::string::npos && paren_end != std::string::npos &&
|
||||||
|
paren_end > paren_start) {
|
||||||
|
l.short_name = fullName.substr(paren_start + 1, paren_end - paren_start - 1);
|
||||||
|
} else if (fullName.length() >= 2) {
|
||||||
|
l.short_name = fullName.substr(0, 2);
|
||||||
|
} else {
|
||||||
|
l.short_name = fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::transform(l.short_name.begin(), l.short_name.end(), l.short_name.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
|
||||||
|
l.short_description = l.short_name;
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#include "modules/mango/layout.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
Layout::Layout(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: ALabel(config, "layout", id, "{}", 0, false), bar_(bar) {
|
||||||
|
IPC::getInstance().registerForIPC("monitor", this);
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::~Layout() { IPC::getInstance().unregisterForIPC(this); }
|
||||||
|
|
||||||
|
void Layout::onEvent(const Json::Value& ev) { dp.emit(); }
|
||||||
|
|
||||||
|
void Layout::doUpdate() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
std::string symbol = IPC::getInstance().getLayoutSymbolForMonitor(bar_.output->name);
|
||||||
|
|
||||||
|
if (symbol.empty()) {
|
||||||
|
label_.hide();
|
||||||
|
last_symbol_.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol != last_symbol_) {
|
||||||
|
if (!last_symbol_.empty()) label_.get_style_context()->remove_class(last_symbol_);
|
||||||
|
label_.get_style_context()->add_class(symbol);
|
||||||
|
last_symbol_ = symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
std::string format_key = "format-" + symbol;
|
||||||
|
|
||||||
|
if (config_.isMember(format_key)) {
|
||||||
|
text = fmt::format(fmt::runtime(config_[format_key].asString()), fmt::arg("symbol", symbol));
|
||||||
|
} else {
|
||||||
|
text = fmt::format(fmt::runtime(format_), fmt::arg("symbol", symbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text.empty()) {
|
||||||
|
label_.show();
|
||||||
|
label_.set_markup(text);
|
||||||
|
} else {
|
||||||
|
label_.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Layout::update() {
|
||||||
|
doUpdate();
|
||||||
|
ALabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#include "modules/mango/window.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include "util/rewrite_string.hpp"
|
||||||
|
#include "util/sanitize_str.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) {
|
||||||
|
IPC::getInstance().registerForIPC("monitor", this);
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::~Window() { IPC::getInstance().unregisterForIPC(this); }
|
||||||
|
|
||||||
|
void Window::onEvent(const Json::Value& ev) { dp.emit(); }
|
||||||
|
|
||||||
|
void Window::doUpdate() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
const Json::Value& client = IPC::getInstance().getActiveClientForMonitor(bar_.output->name);
|
||||||
|
|
||||||
|
// judge whether to hide: active_client is null or title field is null
|
||||||
|
if (client.isNull() || !client.isObject() || client["title"].isNull()) {
|
||||||
|
event_box_.hide();
|
||||||
|
label_.hide();
|
||||||
|
updateAppIconName("", "");
|
||||||
|
setClass("empty", true);
|
||||||
|
if (!oldAppId_.empty()) setClass(oldAppId_, false);
|
||||||
|
oldAppId_.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have a valid client, show the label and update content
|
||||||
|
event_box_.show();
|
||||||
|
label_.show();
|
||||||
|
setClass("empty", false);
|
||||||
|
|
||||||
|
std::string title = client["title"].asString();
|
||||||
|
std::string appid = client["appid"].asString();
|
||||||
|
std::string sanitized_title = waybar::util::sanitize_string(title);
|
||||||
|
std::string sanitized_appid = waybar::util::sanitize_string(appid);
|
||||||
|
|
||||||
|
label_.set_markup(waybar::util::rewriteString(
|
||||||
|
fmt::format(fmt::runtime(format_), fmt::arg("title", sanitized_title),
|
||||||
|
fmt::arg("app_id", sanitized_appid)),
|
||||||
|
config_["rewrite"]));
|
||||||
|
|
||||||
|
updateAppIconName(appid, "");
|
||||||
|
if (tooltipEnabled()) label_.set_tooltip_markup(title);
|
||||||
|
|
||||||
|
// Solo judgment
|
||||||
|
bool solo = false;
|
||||||
|
if (client.isMember("tags") && client["tags"].isArray() && client["tags"].size() == 1) {
|
||||||
|
int tag_idx = client["tags"][0].asInt();
|
||||||
|
const auto& monitors = IPC::getInstance().getMonitors();
|
||||||
|
auto mon_it = monitors.find(client["monitor"].asString());
|
||||||
|
if (mon_it != monitors.end()) {
|
||||||
|
const auto& tags = mon_it->second["tags"];
|
||||||
|
for (const auto& tag : tags) {
|
||||||
|
if (tag["index"].asInt() == tag_idx) {
|
||||||
|
solo = (tag["client_count"].asInt() == 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setClass("solo", solo);
|
||||||
|
if (!appid.empty()) setClass(appid, solo);
|
||||||
|
|
||||||
|
if (oldAppId_ != appid) {
|
||||||
|
if (!oldAppId_.empty()) setClass(oldAppId_, false);
|
||||||
|
oldAppId_ = appid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::update() {
|
||||||
|
doUpdate();
|
||||||
|
AAppIconLabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::setClass(const std::string& className, bool enable) {
|
||||||
|
auto style_context = event_box_.get_style_context();
|
||||||
|
if (enable) {
|
||||||
|
if (!style_context->has_class(className)) style_context->add_class(className);
|
||||||
|
} else {
|
||||||
|
style_context->remove_class(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
#include "modules/mango/workspaces.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace waybar::modules::mango {
|
||||||
|
|
||||||
|
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
||||||
|
box_.set_name("workspaces");
|
||||||
|
if (!id.empty()) box_.get_style_context()->add_class(id);
|
||||||
|
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||||
|
event_box_.add(box_);
|
||||||
|
|
||||||
|
if (config_["on-click"].isString()) on_click_left_ = config_["on-click"].asString();
|
||||||
|
if (config_["on-click-middle"].isString())
|
||||||
|
on_click_middle_ = config_["on-click-middle"].asString();
|
||||||
|
if (config_["on-click-right"].isString()) on_click_right_ = config_["on-click-right"].asString();
|
||||||
|
|
||||||
|
overview_button_ = new Gtk::Button("OVERVIEW");
|
||||||
|
overview_button_->set_relief(Gtk::RELIEF_NONE);
|
||||||
|
box_.pack_start(*overview_button_, false, false, 0);
|
||||||
|
|
||||||
|
if (!on_click_left_.empty() || !on_click_middle_.empty() || !on_click_right_.empty()) {
|
||||||
|
overview_button_->add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
|
overview_button_->signal_button_press_event().connect(
|
||||||
|
[this](GdkEventButton* event) -> bool { return handleButtonClick(event, 0, true); }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::getInstance().registerForIPC("monitor", this);
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspaces::~Workspaces() {
|
||||||
|
IPC::getInstance().unregisterForIPC(this);
|
||||||
|
|
||||||
|
if (overview_button_) {
|
||||||
|
box_.remove(*overview_button_);
|
||||||
|
delete overview_button_;
|
||||||
|
overview_button_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [idx, btn] : buttons_) {
|
||||||
|
box_.remove(btn);
|
||||||
|
}
|
||||||
|
buttons_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::onEvent(const Json::Value& ev) { dp.emit(); }
|
||||||
|
|
||||||
|
void Workspaces::doUpdate() {
|
||||||
|
Json::Value monitor = IPC::getInstance().getMonitor(bar_.output->name);
|
||||||
|
if (monitor.isNull()) return;
|
||||||
|
|
||||||
|
const auto& tags = monitor["tags"];
|
||||||
|
|
||||||
|
bool overview_mode = false;
|
||||||
|
if (monitor.isMember("active_tags") && monitor["active_tags"].isArray()) {
|
||||||
|
const auto& active_tags = monitor["active_tags"];
|
||||||
|
if (active_tags.size() == 1 && active_tags[0].asInt() == 0) {
|
||||||
|
overview_mode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [idx, btn] : buttons_) {
|
||||||
|
btn.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overview_mode) {
|
||||||
|
overview_button_->show();
|
||||||
|
auto style = overview_button_->get_style_context();
|
||||||
|
style->add_class("overview");
|
||||||
|
if (monitor["active"].asBool())
|
||||||
|
style->add_class("current_output");
|
||||||
|
else
|
||||||
|
style->remove_class("current_output");
|
||||||
|
|
||||||
|
std::string label =
|
||||||
|
config_["overview-label"].isString() ? config_["overview-label"].asString() : "OVERVIEW";
|
||||||
|
|
||||||
|
if (!config_["disable-markup"].asBool()) {
|
||||||
|
if (auto gtk_label = dynamic_cast<Gtk::Label*>(overview_button_->get_child())) {
|
||||||
|
gtk_label->set_markup(label);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
overview_button_->set_label(label);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
overview_button_->hide();
|
||||||
|
|
||||||
|
for (auto btn_it = buttons_.begin(); btn_it != buttons_.end();) {
|
||||||
|
uint64_t id = btn_it->first;
|
||||||
|
bool found = std::any_of(tags.begin(), tags.end(), [id](const Json::Value& tag) {
|
||||||
|
return tag["index"].asUInt64() == id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
box_.remove(btn_it->second);
|
||||||
|
btn_it = buttons_.erase(btn_it);
|
||||||
|
} else {
|
||||||
|
++btn_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& tag : tags) {
|
||||||
|
uint64_t idx = tag["index"].asUInt64();
|
||||||
|
auto btn_it = buttons_.find(idx);
|
||||||
|
Gtk::Button& button = (btn_it == buttons_.end()) ? addButton(idx) : btn_it->second;
|
||||||
|
updateButtonState(button, tag, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint64_t> indices;
|
||||||
|
for (const auto& tag : tags) indices.push_back(tag["index"].asUInt64());
|
||||||
|
std::sort(indices.begin(), indices.end());
|
||||||
|
int pos = 0;
|
||||||
|
for (uint64_t idx : indices) {
|
||||||
|
box_.reorder_child(buttons_[idx], pos + 1);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::update() {
|
||||||
|
doUpdate();
|
||||||
|
AModule::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk::Button& Workspaces::addButton(uint64_t idx) {
|
||||||
|
auto [it, _] = buttons_.emplace(idx, std::to_string(idx));
|
||||||
|
auto& button = it->second;
|
||||||
|
box_.pack_start(button, false, false, 0);
|
||||||
|
button.set_relief(Gtk::RELIEF_NONE);
|
||||||
|
|
||||||
|
if (!on_click_left_.empty() || !on_click_middle_.empty() || !on_click_right_.empty()) {
|
||||||
|
button.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
|
button.signal_button_press_event().connect(
|
||||||
|
[this, idx](GdkEventButton* event) -> bool { return handleButtonClick(event, idx, false); },
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.show_all();
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::updateButtonState(Gtk::Button& button, const Json::Value& tag,
|
||||||
|
const Json::Value& monitor) {
|
||||||
|
auto style = button.get_style_context();
|
||||||
|
bool active = tag["is_active"].asBool();
|
||||||
|
bool urgent = tag["is_urgent"].asBool();
|
||||||
|
bool empty = (tag["client_count"].asInt() == 0);
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
style->add_class("active");
|
||||||
|
else
|
||||||
|
style->remove_class("active");
|
||||||
|
|
||||||
|
if (urgent)
|
||||||
|
style->add_class("urgent");
|
||||||
|
else
|
||||||
|
style->remove_class("urgent");
|
||||||
|
|
||||||
|
if (empty)
|
||||||
|
style->add_class("empty");
|
||||||
|
else
|
||||||
|
style->remove_class("empty");
|
||||||
|
|
||||||
|
if (monitor["active"].asBool())
|
||||||
|
style->add_class("current_output");
|
||||||
|
else
|
||||||
|
style->remove_class("current_output");
|
||||||
|
|
||||||
|
uint64_t idx = tag["index"].asUInt64();
|
||||||
|
std::string name = std::to_string(idx);
|
||||||
|
if (config_["format"].isString()) {
|
||||||
|
name = fmt::format(fmt::runtime(config_["format"].asString()),
|
||||||
|
fmt::arg("icon", getIcon(name, tag)), fmt::arg("value", name),
|
||||||
|
fmt::arg("index", idx), fmt::arg("output", monitor["name"].asString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config_["disable-markup"].asBool()) {
|
||||||
|
if (auto gtk_label = dynamic_cast<Gtk::Label*>(button.get_child())) {
|
||||||
|
gtk_label->set_markup(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
button.set_label(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_["current-only"].asBool()) {
|
||||||
|
if (active)
|
||||||
|
button.show();
|
||||||
|
else
|
||||||
|
button.hide();
|
||||||
|
} else if (config_["hide-empty"].asBool() && empty && !active) {
|
||||||
|
button.hide();
|
||||||
|
} else {
|
||||||
|
button.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Workspaces::getIcon(const std::string& value, const Json::Value& tag) {
|
||||||
|
const auto& icons = config_["format-icons"];
|
||||||
|
if (!icons) return value;
|
||||||
|
|
||||||
|
if (tag["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
|
||||||
|
if (tag["is_active"].asBool() && icons["active"]) return icons["active"].asString();
|
||||||
|
if (tag["client_count"].asInt() == 0 && icons["empty"]) return icons["empty"].asString();
|
||||||
|
|
||||||
|
std::string idx = std::to_string(tag["index"].asUInt());
|
||||||
|
if (icons[idx]) return icons[idx].asString();
|
||||||
|
if (icons["default"]) return icons["default"].asString();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspaces::handleButtonClick(GdkEventButton* event, uint64_t idx, bool isOverview) {
|
||||||
|
std::string action;
|
||||||
|
if (event->button == 1)
|
||||||
|
action = on_click_left_;
|
||||||
|
else if (event->button == 2)
|
||||||
|
action = on_click_middle_;
|
||||||
|
else if (event->button == 3)
|
||||||
|
action = on_click_right_;
|
||||||
|
|
||||||
|
if (action.empty()) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::string cmd;
|
||||||
|
if (isOverview) {
|
||||||
|
if (action == "activate")
|
||||||
|
cmd = "dispatch overview";
|
||||||
|
else if (action == "toggle")
|
||||||
|
cmd = "dispatch toggleoverview";
|
||||||
|
} else {
|
||||||
|
if (action == "activate")
|
||||||
|
cmd = "dispatch view," + std::to_string(idx);
|
||||||
|
else if (action == "toggle")
|
||||||
|
cmd = "dispatch toggleview," + std::to_string(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmd.empty()) {
|
||||||
|
Json::Value req;
|
||||||
|
req["command"] = cmd;
|
||||||
|
IPC::sendAsync(req);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("Error sending IPC command: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::mango
|
||||||
+106
-32
@@ -19,39 +19,37 @@ auto format_as(enum mpd_idle val) {
|
|||||||
|
|
||||||
namespace waybar::modules::detail {
|
namespace waybar::modules::detail {
|
||||||
|
|
||||||
#define IDLE_RUN_NOIDLE_AND_CMD(...) \
|
#define RUN_NOIDLE_AND_CMD(STATE, ...) \
|
||||||
if (idle_connection_.connected()) { \
|
if (idle_connection_.connected()) { \
|
||||||
idle_connection_.disconnect(); \
|
idle_connection_.disconnect(); \
|
||||||
auto conn = ctx_->connection().get(); \
|
auto conn = ctx_->connection().get(); \
|
||||||
if (!mpd_run_noidle(conn)) { \
|
if (!mpd_run_noidle(conn)) { \
|
||||||
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \
|
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \
|
||||||
spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \
|
spdlog::error("mpd: STATE: failed to unregister for IDLE events"); \
|
||||||
ctx_->checkErrors(conn); \
|
ctx_->checkErrors(conn); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
__VA_ARGS__; \
|
__VA_ARGS__; \
|
||||||
}
|
}
|
||||||
|
|
||||||
void Idle::play() {
|
void Idle::play() {
|
||||||
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn));
|
RUN_NOIDLE_AND_CMD(Idle, mpd_run_play(conn));
|
||||||
|
|
||||||
ctx_->setState(std::make_unique<Playing>(ctx_));
|
ctx_->setState(std::make_unique<Playing>(ctx_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Idle::pause() {
|
void Idle::pause() {
|
||||||
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true));
|
RUN_NOIDLE_AND_CMD(Idle, mpd_run_pause(conn, true));
|
||||||
|
|
||||||
ctx_->setState(std::make_unique<Paused>(ctx_));
|
ctx_->setState(std::make_unique<Paused>(ctx_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Idle::stop() {
|
void Idle::stop() {
|
||||||
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn));
|
RUN_NOIDLE_AND_CMD(Idle, mpd_run_stop(conn));
|
||||||
|
|
||||||
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef IDLE_RUN_NOIDLE_AND_CMD
|
|
||||||
|
|
||||||
void Idle::update() noexcept {
|
void Idle::update() noexcept {
|
||||||
// This is intentionally blank.
|
// This is intentionally blank.
|
||||||
}
|
}
|
||||||
@@ -97,19 +95,16 @@ bool Idle::on_io(Glib::IOCondition const&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx_->fetchState();
|
ctx_->fetchState();
|
||||||
|
ctx_->emit();
|
||||||
mpd_state state = ctx_->state();
|
mpd_state state = ctx_->state();
|
||||||
|
|
||||||
if (state == MPD_STATE_STOP) {
|
if (state == MPD_STATE_STOP) {
|
||||||
ctx_->emit();
|
|
||||||
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
||||||
} else if (state == MPD_STATE_PLAY) {
|
} else if (state == MPD_STATE_PLAY) {
|
||||||
ctx_->emit();
|
|
||||||
ctx_->setState(std::make_unique<Playing>(ctx_));
|
ctx_->setState(std::make_unique<Playing>(ctx_));
|
||||||
} else if (state == MPD_STATE_PAUSE) {
|
} else if (state == MPD_STATE_PAUSE) {
|
||||||
ctx_->emit();
|
|
||||||
ctx_->setState(std::make_unique<Paused>(ctx_));
|
ctx_->setState(std::make_unique<Paused>(ctx_));
|
||||||
} else {
|
} else {
|
||||||
ctx_->emit();
|
|
||||||
// self transition
|
// self transition
|
||||||
ctx_->setState(std::make_unique<Idle>(ctx_));
|
ctx_->setState(std::make_unique<Idle>(ctx_));
|
||||||
}
|
}
|
||||||
@@ -118,18 +113,49 @@ bool Idle::on_io(Glib::IOCondition const&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Playing::entry() noexcept {
|
void Playing::entry() noexcept {
|
||||||
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
|
timer();
|
||||||
timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1);
|
idle();
|
||||||
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
|
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playing::exit() noexcept {
|
void Playing::exit() noexcept {
|
||||||
|
if (idle_connection_.connected()) {
|
||||||
|
idle_connection_.disconnect();
|
||||||
|
spdlog::debug("mpd: Playing: unwatching FD");
|
||||||
|
}
|
||||||
|
|
||||||
if (timer_connection_.connected()) {
|
if (timer_connection_.connected()) {
|
||||||
timer_connection_.disconnect();
|
timer_connection_.disconnect();
|
||||||
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
|
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Playing::timer() noexcept {
|
||||||
|
if (timer_connection_.connected()) {
|
||||||
|
timer_connection_.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
|
||||||
|
timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Playing::idle() noexcept {
|
||||||
|
auto conn = ctx_->connection().get();
|
||||||
|
assert(conn != nullptr);
|
||||||
|
|
||||||
|
if (!mpd_send_idle_mask(
|
||||||
|
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
|
||||||
|
ctx_->checkErrors(conn);
|
||||||
|
spdlog::error("mpd: Playing: failed to register for IDLE events");
|
||||||
|
} else if (!idle_connection_.connected()) {
|
||||||
|
spdlog::trace("mpd: Playing: watching FD");
|
||||||
|
sigc::slot<bool, Glib::IOCondition const&> idle_slot = sigc::mem_fun(*this, &Playing::on_io);
|
||||||
|
idle_connection_ =
|
||||||
|
Glib::signal_io().connect(idle_slot, mpd_connection_get_fd(conn),
|
||||||
|
Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Playing::on_timer() {
|
bool Playing::on_timer() {
|
||||||
// Attempt to connect with MPD.
|
// Attempt to connect with MPD.
|
||||||
try {
|
try {
|
||||||
@@ -141,9 +167,12 @@ bool Playing::on_timer() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RUN_NOIDLE_AND_CMD(Playing);
|
||||||
|
|
||||||
ctx_->fetchState();
|
ctx_->fetchState();
|
||||||
|
|
||||||
if (!ctx_->is_playing()) {
|
if (!ctx_->is_playing()) {
|
||||||
|
ctx_->emit();
|
||||||
if (ctx_->is_paused()) {
|
if (ctx_->is_paused()) {
|
||||||
ctx_->setState(std::make_unique<Paused>(ctx_));
|
ctx_->setState(std::make_unique<Paused>(ctx_));
|
||||||
} else {
|
} else {
|
||||||
@@ -153,6 +182,50 @@ bool Playing::on_timer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx_->emit();
|
ctx_->emit();
|
||||||
|
|
||||||
|
idle();
|
||||||
|
} catch (std::exception const& e) {
|
||||||
|
spdlog::warn("mpd: Playing: error: {}", e.what());
|
||||||
|
ctx_->setState(std::make_unique<Disconnected>(ctx_));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Playing::on_io(Glib::IOCondition const&) {
|
||||||
|
auto conn = ctx_->connection().get();
|
||||||
|
|
||||||
|
// callback should do this:
|
||||||
|
enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false);
|
||||||
|
spdlog::debug("mpd: Playing: recv_idle events -> {}", events);
|
||||||
|
|
||||||
|
mpd_response_finish(conn);
|
||||||
|
try {
|
||||||
|
ctx_->checkErrors(conn);
|
||||||
|
|
||||||
|
ctx_->fetchState();
|
||||||
|
|
||||||
|
if (!ctx_->is_playing()) {
|
||||||
|
ctx_->emit();
|
||||||
|
if (ctx_->is_paused()) {
|
||||||
|
ctx_->setState(std::make_unique<Paused>(ctx_));
|
||||||
|
} else {
|
||||||
|
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx_->emit();
|
||||||
|
|
||||||
|
if (!mpd_send_idle_mask(
|
||||||
|
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
|
||||||
|
ctx_->checkErrors(conn);
|
||||||
|
spdlog::error("mpd: Playing: failed to register for IDLE events");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer the next timer
|
||||||
|
timer();
|
||||||
} catch (std::exception const& e) {
|
} catch (std::exception const& e) {
|
||||||
spdlog::warn("mpd: Playing: error: {}", e.what());
|
spdlog::warn("mpd: Playing: error: {}", e.what());
|
||||||
ctx_->setState(std::make_unique<Disconnected>(ctx_));
|
ctx_->setState(std::make_unique<Disconnected>(ctx_));
|
||||||
@@ -163,21 +236,23 @@ bool Playing::on_timer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Playing::stop() {
|
void Playing::stop() {
|
||||||
if (timer_connection_.connected()) {
|
RUN_NOIDLE_AND_CMD(
|
||||||
timer_connection_.disconnect();
|
Playing, if (timer_connection_.connected()) {
|
||||||
|
timer_connection_.disconnect();
|
||||||
|
|
||||||
mpd_run_stop(ctx_->connection().get());
|
mpd_run_stop(ctx_->connection().get());
|
||||||
}
|
});
|
||||||
|
|
||||||
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
ctx_->setState(std::make_unique<Stopped>(ctx_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playing::pause() {
|
void Playing::pause() {
|
||||||
if (timer_connection_.connected()) {
|
RUN_NOIDLE_AND_CMD(
|
||||||
timer_connection_.disconnect();
|
Playing, if (timer_connection_.connected()) {
|
||||||
|
timer_connection_.disconnect();
|
||||||
|
|
||||||
mpd_run_pause(ctx_->connection().get(), true);
|
mpd_run_pause(ctx_->connection().get(), true);
|
||||||
}
|
});
|
||||||
|
|
||||||
ctx_->setState(std::make_unique<Paused>(ctx_));
|
ctx_->setState(std::make_unique<Paused>(ctx_));
|
||||||
}
|
}
|
||||||
@@ -211,7 +286,6 @@ bool Paused::on_timer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx_->fetchState();
|
ctx_->fetchState();
|
||||||
|
|
||||||
ctx_->emit();
|
ctx_->emit();
|
||||||
|
|
||||||
if (ctx_->is_paused()) {
|
if (ctx_->is_paused()) {
|
||||||
@@ -282,7 +356,6 @@ bool Stopped::on_timer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx_->fetchState();
|
ctx_->fetchState();
|
||||||
|
|
||||||
ctx_->emit();
|
ctx_->emit();
|
||||||
|
|
||||||
if (ctx_->is_stopped()) {
|
if (ctx_->is_stopped()) {
|
||||||
@@ -386,4 +459,5 @@ bool Disconnected::on_timer() {
|
|||||||
|
|
||||||
void Disconnected::update() noexcept { ctx_->do_update(); }
|
void Disconnected::update() noexcept { ctx_->do_update(); }
|
||||||
|
|
||||||
|
#undef RUN_NOIDLE_AND_CMD
|
||||||
} // namespace waybar::modules::detail
|
} // namespace waybar::modules::detail
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
|
|||||||
dynamic_separator_(" - "),
|
dynamic_separator_(" - "),
|
||||||
truncate_hours_(true),
|
truncate_hours_(true),
|
||||||
tooltip_len_limits_(false),
|
tooltip_len_limits_(false),
|
||||||
|
prefer_album_artist_(false),
|
||||||
// this character is used in Gnome so it's fine to use it here
|
// this character is used in Gnome so it's fine to use it here
|
||||||
ellipsis_("\u2026"),
|
ellipsis_("\u2026"),
|
||||||
player_("playerctld"),
|
player_("playerctld"),
|
||||||
@@ -68,6 +69,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
|
|||||||
if (config_["enable-tooltip-len-limits"].isBool()) {
|
if (config_["enable-tooltip-len-limits"].isBool()) {
|
||||||
tooltip_len_limits_ = config["enable-tooltip-len-limits"].asBool();
|
tooltip_len_limits_ = config["enable-tooltip-len-limits"].asBool();
|
||||||
}
|
}
|
||||||
|
if (config_["prefer-album-artist"].isBool()) {
|
||||||
|
prefer_album_artist_ = config["prefer-album-artist"].asBool();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config["artist-len"].isUInt()) {
|
if (config["artist-len"].isUInt()) {
|
||||||
@@ -206,6 +210,12 @@ auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) ->
|
|||||||
|
|
||||||
auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
|
auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
|
||||||
auto artist = info.artist.value_or(std::string());
|
auto artist = info.artist.value_or(std::string());
|
||||||
|
if (prefer_album_artist_) {
|
||||||
|
auto album_artist = info.album_artist.value_or(std::string());
|
||||||
|
if (!album_artist.empty()) {
|
||||||
|
artist = album_artist;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (truncated && artist_len_ >= 0) waybar::util::utf8_truncate(artist, ellipsis_, artist_len_);
|
if (truncated && artist_len_ >= 0) waybar::util::utf8_truncate(artist, ellipsis_, artist_len_);
|
||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
@@ -521,6 +531,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
|||||||
.status_string = player_status,
|
.status_string = player_status,
|
||||||
.artist = std::nullopt,
|
.artist = std::nullopt,
|
||||||
.album = std::nullopt,
|
.album = std::nullopt,
|
||||||
|
.album_artist = std::nullopt,
|
||||||
.title = std::nullopt,
|
.title = std::nullopt,
|
||||||
.length = std::nullopt,
|
.length = std::nullopt,
|
||||||
};
|
};
|
||||||
@@ -532,6 +543,13 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
|||||||
}
|
}
|
||||||
if (error) goto errorexit;
|
if (error) goto errorexit;
|
||||||
|
|
||||||
|
if (auto* album_artist_ =
|
||||||
|
playerctl_player_print_metadata_prop(player, "xesam:albumArtist", &error)) {
|
||||||
|
spdlog::debug("mpris[{}]: albumArtist = {}", info.name, album_artist_);
|
||||||
|
info.album_artist = album_artist_;
|
||||||
|
g_free(album_artist_);
|
||||||
|
}
|
||||||
|
|
||||||
if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
|
if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
|
||||||
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
||||||
info.album = album_;
|
info.album = album_;
|
||||||
|
|||||||
@@ -368,6 +368,10 @@ auto waybar::modules::Network::update() -> void {
|
|||||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
|
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
|
||||||
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
|
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
|
||||||
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
|
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
|
||||||
|
fmt::arg("bandwidthDownBytesCompact",
|
||||||
|
pow_format(bandwidth_down / elapsed_seconds, "B", false, 2)),
|
||||||
|
fmt::arg("bandwidthUpBytesCompact",
|
||||||
|
pow_format(bandwidth_up / elapsed_seconds, "B", false, 2)),
|
||||||
fmt::arg("bandwidthTotalBytes",
|
fmt::arg("bandwidthTotalBytes",
|
||||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
|
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
|
||||||
if (text.compare(label_.get_label()) != 0) {
|
if (text.compare(label_.get_label()) != 0) {
|
||||||
@@ -401,6 +405,10 @@ auto waybar::modules::Network::update() -> void {
|
|||||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
|
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
|
||||||
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
|
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
|
||||||
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
|
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
|
||||||
|
fmt::arg("bandwidthDownBytesCompact",
|
||||||
|
pow_format(bandwidth_down / elapsed_seconds, "B", false, 2)),
|
||||||
|
fmt::arg("bandwidthUpBytesCompact",
|
||||||
|
pow_format(bandwidth_up / elapsed_seconds, "B", false, 2)),
|
||||||
fmt::arg("bandwidthTotalBytes",
|
fmt::arg("bandwidthTotalBytes",
|
||||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
|
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
|
||||||
if (label_.get_tooltip_text() != tooltip_text) {
|
if (label_.get_tooltip_text() != tooltip_text) {
|
||||||
|
|||||||
@@ -196,6 +196,18 @@ void IPC::parseIPC(const std::string& line) {
|
|||||||
for (auto& win : windows_) {
|
for (auto& win : windows_) {
|
||||||
win["is_focused"] = focused && win["id"].asUInt64() == id;
|
win["is_focused"] = focused && win["id"].asUInt64() == id;
|
||||||
}
|
}
|
||||||
|
} else if (const auto &payload = ev["WindowLayoutsChanged"]) {
|
||||||
|
const auto &values = payload["changes"];
|
||||||
|
for (const auto &changed : values) {
|
||||||
|
const auto id = changed[0].asUInt64();
|
||||||
|
const auto &change = changed[1];
|
||||||
|
for (auto &win : windows_) {
|
||||||
|
if (win["id"].asUInt64() == id) {
|
||||||
|
win["layout"] = change;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
|||||||
gIPC->registerForIPC("WindowOpenedOrChanged", this);
|
gIPC->registerForIPC("WindowOpenedOrChanged", this);
|
||||||
gIPC->registerForIPC("WindowClosed", this);
|
gIPC->registerForIPC("WindowClosed", this);
|
||||||
gIPC->registerForIPC("WindowFocusChanged", this);
|
gIPC->registerForIPC("WindowFocusChanged", this);
|
||||||
|
gIPC->registerForIPC("WindowLayoutsChanged", this);
|
||||||
|
|
||||||
dp.emit();
|
dp.emit();
|
||||||
}
|
}
|
||||||
@@ -54,15 +55,25 @@ void Window::doUpdate() {
|
|||||||
if (it != windows.cend()) {
|
if (it != windows.cend()) {
|
||||||
const auto& window = *it;
|
const auto& window = *it;
|
||||||
|
|
||||||
|
auto max_col = -1;
|
||||||
|
for (const auto &win : windows) {
|
||||||
|
if (win["workspace_id"].asUInt64() != window["workspace_id"].asUInt64()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto col = win["layout"]["pos_in_scrolling_layout"][0].asInt64();
|
||||||
|
if (col > max_col) max_col = col;
|
||||||
|
}
|
||||||
const auto title = window["title"].asString();
|
const auto title = window["title"].asString();
|
||||||
const auto appId = window["app_id"].asString();
|
const auto appId = window["app_id"].asString();
|
||||||
|
const auto col = window["layout"]["pos_in_scrolling_layout"][0].asInt64();
|
||||||
const auto sanitizedTitle = waybar::util::sanitize_string(title);
|
const auto sanitizedTitle = waybar::util::sanitize_string(title);
|
||||||
const auto sanitizedAppId = waybar::util::sanitize_string(appId);
|
const auto sanitizedAppId = waybar::util::sanitize_string(appId);
|
||||||
|
|
||||||
label_.show();
|
label_.show();
|
||||||
label_.set_markup(waybar::util::rewriteString(
|
label_.set_markup(waybar::util::rewriteString(
|
||||||
fmt::format(fmt::runtime(format_), fmt::arg("title", sanitizedTitle),
|
fmt::format(fmt::runtime(format_), fmt::arg("title", sanitizedTitle),
|
||||||
fmt::arg("app_id", sanitizedAppId)),
|
fmt::arg("app_id", sanitizedAppId), fmt::arg("col", col),
|
||||||
|
fmt::arg("max_col", max_col)),
|
||||||
config_["rewrite"]));
|
config_["rewrite"]));
|
||||||
|
|
||||||
updateAppIconName(appId, "");
|
updateAppIconName(appId, "");
|
||||||
@@ -82,7 +93,12 @@ void Window::doUpdate() {
|
|||||||
oldAppId_ = appId;
|
oldAppId_ = appId;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
label_.hide();
|
label_.show();
|
||||||
|
label_.set_markup(waybar::util::rewriteString(
|
||||||
|
fmt::format(fmt::runtime(format_), fmt::arg("title", ""),
|
||||||
|
fmt::arg("app_id", "")),
|
||||||
|
config_["rewrite"]));
|
||||||
|
|
||||||
updateAppIconName("", "");
|
updateAppIconName("", "");
|
||||||
setClass("solo", false);
|
setClass("solo", false);
|
||||||
if (!oldAppId_.empty()) setClass(oldAppId_, false);
|
if (!oldAppId_.empty()) setClass(oldAppId_, false);
|
||||||
|
|||||||
@@ -4,10 +4,34 @@
|
|||||||
#include <gtkmm/label.h>
|
#include <gtkmm/label.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace waybar::modules::niri {
|
namespace waybar::modules::niri {
|
||||||
|
|
||||||
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
|
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
: AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
: AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
||||||
|
const auto config_sort_by_number = config_["sort-by-number"];
|
||||||
|
if (config_sort_by_number.isBool()) {
|
||||||
|
spdlog::warn("[niri/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();
|
||||||
|
}
|
||||||
|
|
||||||
box_.set_name("workspaces");
|
box_.set_name("workspaces");
|
||||||
if (!id.empty()) {
|
if (!id.empty()) {
|
||||||
box_.get_style_context()->add_class(id);
|
box_.get_style_context()->add_class(id);
|
||||||
@@ -41,6 +65,8 @@ void Workspaces::doUpdate() {
|
|||||||
return ws["output"].asString() == bar_.output->name;
|
return ws["output"].asString() == bar_.output->name;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sortWorkspaces(my_workspaces);
|
||||||
|
|
||||||
// Remove buttons for removed workspaces.
|
// Remove buttons for removed workspaces.
|
||||||
for (auto it = buttons_.begin(); it != buttons_.end();) {
|
for (auto it = buttons_.begin(); it != buttons_.end();) {
|
||||||
auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),
|
auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),
|
||||||
@@ -118,9 +144,9 @@ void Workspaces::doUpdate() {
|
|||||||
button.hide();
|
button.hide();
|
||||||
} else if (config_["hide-empty"].asBool()) {
|
} else if (config_["hide-empty"].asBool()) {
|
||||||
if (ws["active_window_id"].isNull() && !ws["is_focused"].asBool())
|
if (ws["active_window_id"].isNull() && !ws["is_focused"].asBool())
|
||||||
button.hide();
|
button.hide();
|
||||||
else
|
else
|
||||||
button.show();
|
button.show();
|
||||||
} else {
|
} else {
|
||||||
button.show();
|
button.show();
|
||||||
}
|
}
|
||||||
@@ -130,8 +156,7 @@ void Workspaces::doUpdate() {
|
|||||||
for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {
|
for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {
|
||||||
const auto& ws = *it;
|
const auto& ws = *it;
|
||||||
|
|
||||||
auto pos = ws["idx"].asUInt() - 1;
|
const auto pos = static_cast<int>(std::distance(my_workspaces.cbegin(), it));
|
||||||
if (alloutputs) pos = it - my_workspaces.cbegin();
|
|
||||||
|
|
||||||
auto& button = buttons_[ws["id"].asUInt64()];
|
auto& button = buttons_[ws["id"].asUInt64()];
|
||||||
box_.reorder_child(button, pos);
|
box_.reorder_child(button, pos);
|
||||||
@@ -200,4 +225,59 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Workspaces::sortWorkspaces(std::vector<Json::Value>& workspaces) const {
|
||||||
|
auto get_name = [](const Json::Value& ws) -> std::string {
|
||||||
|
if (ws["name"]) return ws["name"].asString();
|
||||||
|
return std::to_string(ws["idx"].asUInt());
|
||||||
|
};
|
||||||
|
|
||||||
|
auto is_numeric = [](const std::string& value) {
|
||||||
|
return !value.empty() &&
|
||||||
|
std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c); });
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool names_are_numeric =
|
||||||
|
std::all_of(workspaces.begin(), workspaces.end(),
|
||||||
|
[&](const auto& ws) { return is_numeric(get_name(ws)); });
|
||||||
|
|
||||||
|
auto compare_numeric_strings = [](const std::string& a, const std::string& b) {
|
||||||
|
if (a.size() != b.size()) return a.size() < b.size();
|
||||||
|
return a < b;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::sort(workspaces.begin(), workspaces.end(), [&](const auto& a, const auto& b) {
|
||||||
|
if (sort_by_id_) {
|
||||||
|
return a["id"].asUInt64() < b["id"].asUInt64();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort_by_name_) {
|
||||||
|
const auto a_name = get_name(a);
|
||||||
|
const auto b_name = get_name(b);
|
||||||
|
if (a_name == b_name) return a["id"].asUInt64() < b["id"].asUInt64();
|
||||||
|
if (names_are_numeric) return compare_numeric_strings(a_name, b_name);
|
||||||
|
return a_name < b_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort_by_coordinates_) {
|
||||||
|
const auto& a_output = a["output"].asString();
|
||||||
|
const auto& b_output = b["output"].asString();
|
||||||
|
if (a_output == b_output) {
|
||||||
|
const auto a_idx = a["idx"].asUInt();
|
||||||
|
const auto b_idx = b["idx"].asUInt();
|
||||||
|
if (a_idx == b_idx) return a["id"].asUInt64() < b["id"].asUInt64();
|
||||||
|
return a_idx < b_idx;
|
||||||
|
}
|
||||||
|
return a_output < b_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to sorting by workspace index on each output.
|
||||||
|
const auto& a_output = a["output"].asString();
|
||||||
|
const auto& b_output = b["output"].asString();
|
||||||
|
const auto a_idx = a["idx"].asUInt();
|
||||||
|
const auto b_idx = b["idx"].asUInt();
|
||||||
|
if (a_output == b_output) return a_idx < b_idx;
|
||||||
|
return a_output < b_output;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::niri
|
} // namespace waybar::modules::niri
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
|
|||||||
if (config_["tooltip-format"].isString()) {
|
if (config_["tooltip-format"].isString()) {
|
||||||
tooltipFormat_ = config_["tooltip-format"].asString();
|
tooltipFormat_ = config_["tooltip-format"].asString();
|
||||||
} else {
|
} else {
|
||||||
tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}";
|
tooltipFormat_ =
|
||||||
|
"Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}";
|
||||||
}
|
}
|
||||||
// Fasten your seatbelt, we're up for quite a ride. The rest of the
|
// Fasten your seatbelt, we're up for quite a ride. The rest of the
|
||||||
// init is performed asynchronously. There's 2 callbacks involved.
|
// init is performed asynchronously. There's 2 callbacks involved.
|
||||||
@@ -95,15 +96,51 @@ void PowerProfilesDaemon::populateInitState() {
|
|||||||
powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles");
|
powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles");
|
||||||
for (auto& variantDict : profilesVariant.get()) {
|
for (auto& variantDict : profilesVariant.get()) {
|
||||||
Glib::ustring name;
|
Glib::ustring name;
|
||||||
|
// Legacy single `Driver` property, still exposed by older
|
||||||
|
// power-profiles-daemon versions.
|
||||||
Glib::ustring driver;
|
Glib::ustring driver;
|
||||||
|
Glib::ustring cpuDriver;
|
||||||
|
Glib::ustring platformDriver;
|
||||||
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
|
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
|
||||||
name = p->second.get();
|
name = p->second.get();
|
||||||
}
|
}
|
||||||
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
|
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
|
||||||
driver = d->second.get();
|
driver = d->second.get();
|
||||||
}
|
}
|
||||||
|
if (auto cd = variantDict.find("CpuDriver"); cd != variantDict.end()) {
|
||||||
|
cpuDriver = cd->second.get();
|
||||||
|
}
|
||||||
|
if (auto pd = variantDict.find("PlatformDriver"); pd != variantDict.end()) {
|
||||||
|
platformDriver = pd->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent power-profiles-daemon versions split the single `Driver`
|
||||||
|
// property into `CpuDriver` and `PlatformDriver`. When talking to an
|
||||||
|
// older daemon that only exposes `Driver`, fall back to it so the new
|
||||||
|
// {cpu_driver}/{platform_driver} placeholders still resolve.
|
||||||
|
if (cpuDriver.empty()) {
|
||||||
|
cpuDriver = driver;
|
||||||
|
}
|
||||||
|
if (platformDriver.empty()) {
|
||||||
|
platformDriver = driver;
|
||||||
|
}
|
||||||
|
// Conversely, keep the legacy {driver} placeholder working against a
|
||||||
|
// recent daemon that no longer exposes `Driver` by deriving it from
|
||||||
|
// the CPU driver.
|
||||||
|
if (driver.empty()) {
|
||||||
|
driver = cpuDriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver.empty()) {
|
||||||
|
driver = "Unavailable";
|
||||||
|
cpuDriver = "Unavailable";
|
||||||
|
platformDriver = "Unavailable";
|
||||||
|
spdlog::warn("Cannot find power profiles daemon driver.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!name.empty()) {
|
if (!name.empty()) {
|
||||||
availableProfiles_.emplace_back(std::move(name), std::move(driver));
|
availableProfiles_.emplace_back(std::move(name), std::move(driver), std::move(cpuDriver),
|
||||||
|
std::move(platformDriver));
|
||||||
} else {
|
} else {
|
||||||
spdlog::error(
|
spdlog::error(
|
||||||
"Power profiles daemon: power-profiles-daemon sent us an empty power profile name. "
|
"Power profiles daemon: power-profiles-daemon sent us an empty power profile name. "
|
||||||
@@ -153,7 +190,10 @@ auto PowerProfilesDaemon::update() -> void {
|
|||||||
// Set label
|
// Set label
|
||||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
store.push_back(fmt::arg("profile", profile.name));
|
store.push_back(fmt::arg("profile", profile.name));
|
||||||
|
// Legacy placeholder, kept for backward compatibility with existing configs.
|
||||||
store.push_back(fmt::arg("driver", profile.driver));
|
store.push_back(fmt::arg("driver", profile.driver));
|
||||||
|
store.push_back(fmt::arg("cpu_driver", profile.cpuDriver));
|
||||||
|
store.push_back(fmt::arg("platform_driver", profile.platformDriver));
|
||||||
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
|
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
|
||||||
label_.set_markup(fmt::vformat(format_, store));
|
label_.set_markup(fmt::vformat(format_, store));
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
|
|||||||
|
|
||||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||||
|
|
||||||
|
if (config_["target"].isString() && config_["target"].asString() == "source") {
|
||||||
|
target = util::PulseaudioTarget::Source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {
|
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {
|
||||||
@@ -33,7 +37,7 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {
|
|||||||
? util::ChangeType::Increase
|
? util::ChangeType::Increase
|
||||||
: util::ChangeType::Decrease;
|
: util::ChangeType::Decrease;
|
||||||
|
|
||||||
backend->changeVolume(change_type, step, max_volume);
|
backend->changeVolume(change_type, step, max_volume, target);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void PulseaudioSlider::onValueChanged() {
|
|||||||
if (unmute_on_volume_change) {
|
if (unmute_on_volume_change) {
|
||||||
backend->unmute(target);
|
backend->unmute(target);
|
||||||
}
|
}
|
||||||
backend->changeVolume(slider_value, min_, max_);
|
backend->changeVolume(slider_value, min_, max_, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,33 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
|
|||||||
.urgent_tags = listen_urgent_tags,
|
.urgent_tags = listen_urgent_tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||||
|
struct wl_output* output) {
|
||||||
|
static_cast<Tags *>(data)->handle_focused_output(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||||
|
struct wl_output* output) {
|
||||||
|
static_cast<Tags *>(data)->handle_unfocused_output(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||||
|
const char* title) {
|
||||||
|
// This module doesn't care
|
||||||
|
}
|
||||||
|
|
||||||
|
static void listen_mode(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||||
|
const char* mode) {
|
||||||
|
// This module doesn't care
|
||||||
|
}
|
||||||
|
|
||||||
|
static const zriver_seat_status_v1_listener seat_status_listener_impl{
|
||||||
|
.focused_output = listen_focused_output,
|
||||||
|
.unfocused_output = listen_unfocused_output,
|
||||||
|
.focused_view = listen_focused_view,
|
||||||
|
.mode = listen_mode,
|
||||||
|
};
|
||||||
|
|
||||||
static void listen_command_success(void* data,
|
static void listen_command_success(void* data,
|
||||||
struct zriver_command_callback_v1* zriver_command_callback_v1,
|
struct zriver_command_callback_v1* zriver_command_callback_v1,
|
||||||
const char* output) {
|
const char* output) {
|
||||||
@@ -87,8 +114,10 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
|||||||
control_{nullptr},
|
control_{nullptr},
|
||||||
seat_{nullptr},
|
seat_{nullptr},
|
||||||
bar_(bar),
|
bar_(bar),
|
||||||
|
output_{nullptr},
|
||||||
box_{bar.orientation, 0},
|
box_{bar.orientation, 0},
|
||||||
output_status_{nullptr} {
|
output_status_{nullptr},
|
||||||
|
seat_status_{nullptr} {
|
||||||
struct wl_display* display = Client::inst()->wl_display;
|
struct wl_display* display = Client::inst()->wl_display;
|
||||||
struct wl_registry* registry = wl_display_get_registry(display);
|
struct wl_registry* registry = wl_display_get_registry(display);
|
||||||
wl_registry_add_listener(registry, ®istry_listener_impl, this);
|
wl_registry_add_listener(registry, ®istry_listener_impl, this);
|
||||||
@@ -109,6 +138,11 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the output this module belongs to; the river_output_status and
|
||||||
|
// river_seat_status objects (and their listeners) are created lazily in
|
||||||
|
// handle_show() to avoid leaking objects without listeners here.
|
||||||
|
output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||||
|
|
||||||
box_.set_name("tags");
|
box_.set_name("tags");
|
||||||
if (!id.empty()) {
|
if (!id.empty()) {
|
||||||
box_.get_style_context()->add_class(id);
|
box_.get_style_context()->add_class(id);
|
||||||
@@ -160,6 +194,10 @@ Tags::~Tags() {
|
|||||||
zriver_output_status_v1_destroy(output_status_);
|
zriver_output_status_v1_destroy(output_status_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (seat_status_) {
|
||||||
|
zriver_seat_status_v1_destroy(seat_status_);
|
||||||
|
}
|
||||||
|
|
||||||
if (control_) {
|
if (control_) {
|
||||||
zriver_control_v1_destroy(control_);
|
zriver_control_v1_destroy(control_);
|
||||||
}
|
}
|
||||||
@@ -171,10 +209,12 @@ Tags::~Tags() {
|
|||||||
|
|
||||||
void Tags::handle_show() {
|
void Tags::handle_show() {
|
||||||
if (!status_manager_) return;
|
if (!status_manager_) return;
|
||||||
struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_);
|
||||||
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
|
|
||||||
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
|
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
|
||||||
|
|
||||||
|
seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);
|
||||||
|
zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);
|
||||||
|
|
||||||
zriver_status_manager_v1_destroy(status_manager_);
|
zriver_status_manager_v1_destroy(status_manager_);
|
||||||
status_manager_ = nullptr;
|
status_manager_ = nullptr;
|
||||||
}
|
}
|
||||||
@@ -266,4 +306,20 @@ void Tags::handle_urgent_tags(uint32_t tags) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tags::handle_focused_output(struct wl_output *output) {
|
||||||
|
if (output_ == output) {
|
||||||
|
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||||
|
buttons_[i].get_style_context()->add_class("output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tags::handle_unfocused_output(struct wl_output *output) {
|
||||||
|
if (output_ == output) {
|
||||||
|
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||||
|
buttons_[i].get_style_context()->remove_class("output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace waybar::modules::river */
|
} /* namespace waybar::modules::river */
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ static const unsigned RETRY_DELAY_MS = 200;
|
|||||||
static const unsigned MAX_RETRIES = 10;
|
static const unsigned MAX_RETRIES = 10;
|
||||||
|
|
||||||
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||||
|
const std::vector<std::string>& ignore_list,
|
||||||
const std::function<void(std::unique_ptr<Item>&)>& on_add,
|
const std::function<void(std::unique_ptr<Item>&)>& on_add,
|
||||||
const std::function<void(std::unique_ptr<Item>&)>& on_remove,
|
const std::function<void(std::unique_ptr<Item>&)>& on_remove,
|
||||||
const std::function<void()>& on_update)
|
const std::function<void()>& on_update)
|
||||||
@@ -20,6 +21,7 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
|||||||
sigc::mem_fun(*this, &Host::busAcquired))),
|
sigc::mem_fun(*this, &Host::busAcquired))),
|
||||||
config_(config),
|
config_(config),
|
||||||
bar_(bar),
|
bar_(bar),
|
||||||
|
ignore_list_(ignore_list),
|
||||||
on_add_(on_add),
|
on_add_(on_add),
|
||||||
on_remove_(on_remove),
|
on_remove_(on_remove),
|
||||||
on_update_(on_update) {}
|
on_update_(on_update) {}
|
||||||
@@ -39,6 +41,42 @@ Host::~Host() {
|
|||||||
g_clear_object(&watcher_);
|
g_clear_object(&watcher_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Host::checkIgnoreList(const std::vector<std::string>& ignore_list,
|
||||||
|
const std::function<void(std::unique_ptr<Item>&)>& on_remove) {
|
||||||
|
spdlog::debug("Host::checkIgnoreList - checking {} items against {} patterns", items_.size(),
|
||||||
|
ignore_list.size());
|
||||||
|
|
||||||
|
for (auto it = items_.begin(); it != items_.end();) {
|
||||||
|
auto& item = *it;
|
||||||
|
spdlog::debug(" Checking item: bus_name='{}', category='{}', icon_name='{}', title='{}'",
|
||||||
|
item->bus_name, item->category, item->icon_name, item->title);
|
||||||
|
|
||||||
|
bool should_remove = false;
|
||||||
|
|
||||||
|
for (const auto& ignored : ignore_list) {
|
||||||
|
if (item->bus_name.find(ignored) != std::string::npos ||
|
||||||
|
item->category.find(ignored) != std::string::npos ||
|
||||||
|
item->icon_name.find(ignored) != std::string::npos ||
|
||||||
|
item->id.find(ignored) != std::string::npos ||
|
||||||
|
item->title.find(ignored) != std::string::npos) {
|
||||||
|
spdlog::info(
|
||||||
|
"Host: Ignoring item bus_name='{}', category='{}', icon_name='{}', title='{}' - "
|
||||||
|
"matched pattern '{}'",
|
||||||
|
item->bus_name, item->category, item->icon_name, item->title, ignored);
|
||||||
|
on_remove(item);
|
||||||
|
should_remove = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_remove) {
|
||||||
|
it = items_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Host::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
|
void Host::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
|
||||||
watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher",
|
watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher",
|
||||||
sigc::mem_fun(*this, &Host::nameAppeared),
|
sigc::mem_fun(*this, &Host::nameAppeared),
|
||||||
@@ -82,7 +120,8 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
|
|||||||
spdlog::error("Host: {}", error->message);
|
spdlog::error("Host: {}", error->message);
|
||||||
g_clear_object(&host->cancellable_);
|
g_clear_object(&host->cancellable_);
|
||||||
if (host->retry_count_ >= MAX_RETRIES) {
|
if (host->retry_count_ >= MAX_RETRIES) {
|
||||||
spdlog::warn("Host: giving up on watcher proxy creation after {} retries", host->retry_count_);
|
spdlog::warn("Host: giving up on watcher proxy creation after {} retries",
|
||||||
|
host->retry_count_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
host->retry_count_ += 1;
|
host->retry_count_ += 1;
|
||||||
@@ -127,7 +166,9 @@ void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
|
|||||||
g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data);
|
g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data);
|
||||||
auto items = sn_watcher_dup_registered_items(host->watcher_);
|
auto items = sn_watcher_dup_registered_items(host->watcher_);
|
||||||
if (items != nullptr) {
|
if (items != nullptr) {
|
||||||
|
spdlog::info("Host: Found {} pre-registered SNI items", g_strv_length(items));
|
||||||
for (uint32_t i = 0; items[i] != nullptr; i += 1) {
|
for (uint32_t i = 0; items[i] != nullptr; i += 1) {
|
||||||
|
spdlog::info("Host: Processing pre-registered item: {}", items[i]);
|
||||||
host->addRegisteredItem(items[i]);
|
host->addRegisteredItem(items[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +177,10 @@ void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
|
|||||||
|
|
||||||
void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) {
|
void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) {
|
||||||
auto host = static_cast<SNI::Host*>(data);
|
auto host = static_cast<SNI::Host*>(data);
|
||||||
|
spdlog::info("Host::itemRegistered called with service: {}", service);
|
||||||
host->addRegisteredItem(service);
|
host->addRegisteredItem(service);
|
||||||
|
// host->checkIgnoreList(host->ignore_list_, std::bind(&Host::itemUnregistered, host,
|
||||||
|
// std::placeholders::_1, std::placeholders::_2, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) {
|
void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) {
|
||||||
@@ -197,17 +241,25 @@ std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::st
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Host::addRegisteredItem(const std::string& service) {
|
void Host::addRegisteredItem(const std::string& service) {
|
||||||
|
// Check service string directly before parsing
|
||||||
|
for (const auto& ignored : ignore_list_) {
|
||||||
|
if (service.find(ignored) != std::string::npos) {
|
||||||
|
spdlog::info("Host: Ignoring service '{}' - matched pattern '{}'", service, ignored);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
std::string bus_name, object_path;
|
std::string bus_name, object_path;
|
||||||
std::tie(bus_name, object_path) = getBusNameAndObjectPath(service);
|
std::tie(bus_name, object_path) = getBusNameAndObjectPath(service);
|
||||||
|
spdlog::debug("SNI item registered: bus_name={}, object_path={}, full_service={}", bus_name,
|
||||||
|
object_path, service);
|
||||||
auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) {
|
auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) {
|
||||||
return bus_name == item->bus_name && object_path == item->object_path;
|
return bus_name == item->bus_name && object_path == item->object_path;
|
||||||
});
|
});
|
||||||
if (it == items_.end()) {
|
if (it == items_.end()) {
|
||||||
|
spdlog::debug("Adding SNI item: {}", bus_name);
|
||||||
items_.emplace_back(std::make_unique<Item>(
|
items_.emplace_back(std::make_unique<Item>(
|
||||||
bus_name, object_path, config_, bar_,
|
bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); },
|
||||||
[this](Item& item) { itemReady(item); },
|
[this](Item& item) { itemInvalidated(item); }, on_update_));
|
||||||
[this](Item& item) { itemInvalidated(item); },
|
|
||||||
on_update_));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,29 @@
|
|||||||
|
|
||||||
namespace waybar::modules::SNI {
|
namespace waybar::modules::SNI {
|
||||||
|
|
||||||
|
std::vector<std::string> Tray::parseIgnoreList(const Json::Value& config) {
|
||||||
|
std::vector<std::string> ignore_list;
|
||||||
|
if (config["ignore-list"].isArray()) {
|
||||||
|
spdlog::info("Tray: Found ignore-list with {} items", config["ignore-list"].size());
|
||||||
|
for (const auto& item : config["ignore-list"]) {
|
||||||
|
if (item.isString()) {
|
||||||
|
ignore_list.push_back(item.asString());
|
||||||
|
spdlog::info("Tray: Adding to ignore list: {}", item.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spdlog::info("Tray: No ignore-list configured");
|
||||||
|
}
|
||||||
|
return ignore_list;
|
||||||
|
}
|
||||||
|
|
||||||
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
: AModule(config, "tray", id),
|
: AModule(config, "tray", id),
|
||||||
box_(bar.orientation, 0),
|
box_(bar.orientation, 0),
|
||||||
watcher_(SNI::Watcher::getInstance()),
|
watcher_(SNI::Watcher::getInstance()),
|
||||||
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
|
ignore_list_(parseIgnoreList(config)),
|
||||||
|
host_(nb_hosts_, config, bar, ignore_list_,
|
||||||
|
std::bind(&Tray::onAdd, this, std::placeholders::_1),
|
||||||
std::bind(&Tray::onRemove, this, std::placeholders::_1),
|
std::bind(&Tray::onRemove, this, std::placeholders::_1),
|
||||||
std::bind(&Tray::queueUpdate, this)) {
|
std::bind(&Tray::queueUpdate, this)) {
|
||||||
box_.set_name("tray");
|
box_.set_name("tray");
|
||||||
@@ -31,14 +49,26 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
|||||||
dp.emit();
|
dp.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tray::checkIgnoreList(std::unique_ptr<Item>* item_ptr) {
|
||||||
|
// Delegate to Host's checkIgnoreList method
|
||||||
|
host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
void Tray::queueUpdate() { dp.emit(); }
|
void Tray::queueUpdate() { dp.emit(); }
|
||||||
|
|
||||||
void Tray::onAdd(std::unique_ptr<Item>& item) {
|
void Tray::onAdd(std::unique_ptr<Item>& item) {
|
||||||
|
spdlog::info("Tray::onAdd - item bus_name='{}', category='{}', icon_name='{}', title='{}'",
|
||||||
|
item->bus_name, item->category, item->icon_name, item->title);
|
||||||
|
|
||||||
if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
|
if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
|
||||||
box_.pack_end(item->event_box);
|
box_.pack_end(item->event_box);
|
||||||
} else {
|
} else {
|
||||||
box_.pack_start(item->event_box);
|
box_.pack_start(item->event_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spdlog::debug("Tray::onAdd deferred check - checking ignore list");
|
||||||
|
host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1));
|
||||||
|
|
||||||
item->event_box.signal_show().connect([this] { dp.emit(); });
|
item->event_box.signal_show().connect([this] { dp.emit(); });
|
||||||
item->event_box.signal_hide().connect([this] { dp.emit(); });
|
item->event_box.signal_hide().connect([this] { dp.emit(); });
|
||||||
dp.emit();
|
dp.emit();
|
||||||
@@ -50,6 +80,12 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Tray::update() -> void {
|
auto Tray::update() -> void {
|
||||||
|
// Check if any items should be ignored now that properties have loaded
|
||||||
|
if (!ignore_list_.empty()) {
|
||||||
|
spdlog::debug("Tray::update() - checking ignore list");
|
||||||
|
host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Gtk::Widget*> children = box_.get_children();
|
std::vector<Gtk::Widget*> children = box_.get_children();
|
||||||
event_box_.set_visible(std::any_of(children.begin(), children.end(),
|
event_box_.set_visible(std::any_of(children.begin(), children.end(),
|
||||||
[](Gtk::Widget* child) { return child->get_visible(); }));
|
[](Gtk::Widget* child) { return child->get_visible(); }));
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
|
|||||||
m_windowRewriteRules = waybar::util::RegexCollection(
|
m_windowRewriteRules = waybar::util::RegexCollection(
|
||||||
windowRewrite, std::move(windowRewriteDefault), windowRewritePriorityFunction);
|
windowRewrite, std::move(windowRewriteDefault), windowRewritePriorityFunction);
|
||||||
}
|
}
|
||||||
|
populateIgnoreWorkspacesConfig(config);
|
||||||
ipc_.subscribe(R"(["workspace"])");
|
ipc_.subscribe(R"(["workspace"])");
|
||||||
ipc_.subscribe(R"(["window"])");
|
ipc_.subscribe(R"(["window"])");
|
||||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));
|
||||||
@@ -97,6 +98,36 @@ void Workspaces::onEvent(const struct Ipc::ipc_response& res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value& config) -> void {
|
||||||
|
auto ignoreWorkspaces = config["ignore-workspaces"];
|
||||||
|
if (ignoreWorkspaces.isArray()) {
|
||||||
|
for (const auto& workspaceRegex : ignoreWorkspaces) {
|
||||||
|
if (workspaceRegex.isString()) {
|
||||||
|
std::string ruleString = workspaceRegex.asString();
|
||||||
|
try {
|
||||||
|
const std::regex rule{ruleString, std::regex_constants::icase};
|
||||||
|
m_ignoreWorkspaces.emplace_back(rule);
|
||||||
|
} catch (const std::regex_error& e) {
|
||||||
|
spdlog::error("Invalid rule {}: {}", ruleString, e.what());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spdlog::error("Not a string: '{}'", workspaceRegex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspaces::isWorkspaceIgnored(std::string const& name) {
|
||||||
|
for (auto& rule : m_ignoreWorkspaces) {
|
||||||
|
if (std::regex_match(name, rule)) {
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Workspaces::onCmd(const struct Ipc::ipc_response& res) {
|
void Workspaces::onCmd(const struct Ipc::ipc_response& res) {
|
||||||
if (res.type == IPC_GET_TREE) {
|
if (res.type == IPC_GET_TREE) {
|
||||||
try {
|
try {
|
||||||
@@ -118,8 +149,9 @@ void Workspaces::onCmd(const struct Ipc::ipc_response& res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (auto& output : outputs) {
|
for (auto& output : outputs) {
|
||||||
std::copy(output["nodes"].begin(), output["nodes"].end(),
|
std::copy_if(
|
||||||
std::back_inserter(workspaces_));
|
output["nodes"].begin(), output["nodes"].end(), std::back_inserter(workspaces_),
|
||||||
|
[&](const auto& node) { return !(isWorkspaceIgnored(node["name"].asString())); });
|
||||||
std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(),
|
std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(),
|
||||||
std::back_inserter(workspaces_));
|
std::back_inserter(workspaces_));
|
||||||
}
|
}
|
||||||
@@ -143,7 +175,8 @@ void Workspaces::onCmd(const struct Ipc::ipc_response& res) {
|
|||||||
if (p_w.isArray() && !p_w.empty()) {
|
if (p_w.isArray() && !p_w.empty()) {
|
||||||
// Adding to target outputs
|
// Adding to target outputs
|
||||||
for (const Json::Value& output : p_w) {
|
for (const Json::Value& output : p_w) {
|
||||||
if (output.asString() == bar_.output->name) {
|
auto output_name = output.asString();
|
||||||
|
if (output_name == bar_.output->name || output_name == bar_.output->identifier) {
|
||||||
Json::Value v;
|
Json::Value v;
|
||||||
v["name"] = p_w_name;
|
v["name"] = p_w_name;
|
||||||
v["target_output"] = bar_.output->name;
|
v["target_output"] = bar_.output->name;
|
||||||
@@ -324,6 +357,18 @@ auto Workspaces::update() -> void {
|
|||||||
button.get_style_context()->remove_class("empty");
|
button.get_style_context()->remove_class("empty");
|
||||||
}
|
}
|
||||||
if ((*it)["output"].isString()) {
|
if ((*it)["output"].isString()) {
|
||||||
|
// Simply attempt to remove all output classes every time to reset output classes. This works
|
||||||
|
// even if a class has not been previously added to the style context.
|
||||||
|
for (const auto &oclass : config_["output-classes"]) {
|
||||||
|
button.get_style_context()->remove_class(oclass.asString());
|
||||||
|
}
|
||||||
|
// If output-classes contains a class for output associated with current workspace button, add
|
||||||
|
// the class to its style context.
|
||||||
|
std::string output_name = (*it)["output"].asString();
|
||||||
|
if (config_["output-classes"].isMember(output_name) &&
|
||||||
|
config_["output-classes"][output_name].isString()) {
|
||||||
|
button.get_style_context()->add_class(config_["output-classes"][output_name].asString());
|
||||||
|
}
|
||||||
if (((*it)["output"].asString()) == bar_.output->name) {
|
if (((*it)["output"].asString()) == bar_.output->name) {
|
||||||
button.get_style_context()->add_class("current_output");
|
button.get_style_context()->add_class("current_output");
|
||||||
} else {
|
} else {
|
||||||
@@ -332,29 +377,44 @@ auto Workspaces::update() -> void {
|
|||||||
} else {
|
} else {
|
||||||
button.get_style_context()->remove_class("current_output");
|
button.get_style_context()->remove_class("current_output");
|
||||||
}
|
}
|
||||||
std::string output;
|
std::string full_name;
|
||||||
if (!config_["disable-markup"].asBool()) {
|
if (!config_["disable-markup"].asBool()) {
|
||||||
output = g_markup_escape_text((*it)["name"].asString().c_str(), -1);
|
full_name = g_markup_escape_text((*it)["name"].asString().c_str(), -1);
|
||||||
} else {
|
} else {
|
||||||
output = (*it)["name"].asString();
|
full_name = (*it)["name"].asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string windows = "";
|
std::string windows = "";
|
||||||
if (config_["window-rewrite"].isObject()) {
|
if (config_["window-rewrite"].isObject()) {
|
||||||
updateWindows((*it), windows);
|
updateWindows((*it), windows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto index = (*it)["num"].asInt();
|
||||||
|
|
||||||
if (config_["format"].isString()) {
|
if (config_["format"].isString()) {
|
||||||
auto format = config_["format"].asString();
|
std::string format;
|
||||||
output = fmt::format(
|
if (config_["format-for-negative-index"].isString() && index < 0) {
|
||||||
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
|
format = config_["format-for-negative-index"].asString();
|
||||||
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
|
} else {
|
||||||
fmt::arg("windows",
|
format = config_["format"].asString();
|
||||||
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
|
}
|
||||||
fmt::arg("output", (*it)["output"].asString()));
|
|
||||||
|
auto name = trimWorkspaceName(full_name);
|
||||||
|
auto output = (*it)["output"].asString();
|
||||||
|
auto icon = getIcon(full_name, *it);
|
||||||
|
auto separated_windows =
|
||||||
|
windows.substr(0, windows.length() - m_formatWindowSeparator.length());
|
||||||
|
|
||||||
|
full_name =
|
||||||
|
fmt::format(fmt::runtime(format), fmt::arg("index", index), fmt::arg("name", name),
|
||||||
|
fmt::arg("value", full_name), fmt::arg("output", output),
|
||||||
|
fmt::arg("icon", icon), fmt::arg("windows", separated_windows));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config_["disable-markup"].asBool()) {
|
if (!config_["disable-markup"].asBool()) {
|
||||||
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(output);
|
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(full_name);
|
||||||
} else {
|
} else {
|
||||||
button.set_label(output);
|
button.set_label(full_name);
|
||||||
}
|
}
|
||||||
onButtonReady(*it, button);
|
onButtonReady(*it, button);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user