Merge remote-tracking branch 'origin/master' into pr-4320

# Conflicts:
#	include/modules/hyprland/workspaces.hpp
#	man/waybar-hyprland-workspaces.5.scd
#	src/modules/hyprland/workspace.cpp
#	src/modules/hyprland/workspaces.cpp
This commit is contained in:
Alex
2026-07-04 00:51:21 +02:00
302 changed files with 14350 additions and 5052 deletions
+24 -6
View File
@@ -1,8 +1,10 @@
#pragma once
#include <atomic>
#include <filesystem>
#include <list>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <utility>
@@ -17,9 +19,13 @@ class EventHandler {
virtual ~EventHandler() = default;
};
/// If you want to use the Hyprland IPC, simply use IPC::inst() to get the singleton instance.
/// Do not create multiple instances.
class IPC {
protected:
IPC(); // use IPC::inst() instead.
public:
IPC();
~IPC();
static IPC& inst();
@@ -30,21 +36,33 @@ class IPC {
Json::Value getSocket1JsonReply(const std::string& rq);
static std::filesystem::path getSocketFolder(const char* instanceSig);
/// Dispatch a Hyprland command. Automatically uses the correct protocol
/// (legacy text or Lua-based) depending on the running Hyprland version.
static std::string dispatch(const std::string& dispatcher, const std::string& arg);
/// Build a Lua-format dispatch command string.
static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg);
protected:
static std::filesystem::path socketFolder_;
/// Detect whether the running Hyprland uses the Lua-based IPC protocol.
/// Returns true for Hyprland >= 0.54 (Lua config), false for older versions.
static bool isLuaProtocol();
static std::optional<bool> s_luaProtocolDetected_; // cached detection result
private:
void socketListener();
void parseIPC(const std::string&);
std::thread ipcThread_;
std::mutex callbackMutex_;
std::mutex socketMutex_;
util::JsonParser parser_;
std::list<std::pair<std::string, EventHandler*>> callbacks_;
int socketfd_; // the hyprland socket file descriptor
bool running_ = true;
int socketfd_ = -1; // the hyprland socket file descriptor
pid_t socketOwnerPid_ = -1;
std::atomic<bool> running_ = true; // the ipcThread will stop running when this is false
};
inline bool modulesReady = false;
inline std::unique_ptr<IPC> gIPC;
}; // namespace waybar::modules::hyprland
+2
View File
@@ -30,6 +30,8 @@ class Language : public waybar::ALabel, public EventHandler {
std::string short_description;
};
auto removeXkbLayoutCssClass() -> void;
auto addXkbLayoutCssClass() -> void;
static auto getLayout(const std::string&) -> Layout;
std::mutex mutex_;
+3
View File
@@ -26,8 +26,11 @@ class Submap : public waybar::ALabel, public EventHandler {
const Bar& bar_;
util::JsonParser parser_;
std::string submap_;
std::string icon_;
std::string prev_submap_;
bool always_on_ = false;
std::string default_submap_ = "Default";
std::unordered_map<std::string, std::string> icons_;
IPC& m_ipc;
};
+11 -11
View File
@@ -20,8 +20,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
private:
struct Workspace {
int id;
int windows;
int id = 0;
int windows = 0;
std::string last_window;
std::string last_window_title;
@@ -29,14 +29,14 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
};
struct WindowData {
bool floating;
bool floating = false;
int monitor = -1;
std::string class_name;
std::string initial_class_name;
std::string title;
std::string initial_title;
bool fullscreen;
bool grouped;
bool fullscreen = false;
bool grouped = false;
static auto parse(const Json::Value&) -> WindowData;
};
@@ -47,7 +47,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
void queryActiveWorkspace();
void setClass(const std::string&, bool enable);
bool separateOutputs_;
bool separateOutputs_ = false;
std::mutex mutex_;
const Bar& bar_;
util::JsonParser parser_;
@@ -55,11 +55,11 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
Workspace workspace_;
std::string soloClass_;
std::string lastSoloClass_;
bool solo_;
bool allFloating_;
bool swallowing_;
bool fullscreen_;
bool focused_;
bool solo_ = false;
bool allFloating_ = false;
bool swallowing_ = false;
bool fullscreen_ = false;
bool focused_ = false;
IPC& m_ipc;
};
+3 -3
View File
@@ -7,7 +7,6 @@
#include "AAppIconLabel.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland {
@@ -26,8 +25,8 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler {
static auto parse(const Json::Value& value) -> Workspace;
};
static auto getActiveWorkspace(const std::string&) -> Workspace;
static auto getActiveWorkspace() -> Workspace;
auto getActiveWorkspace(const std::string&) -> Workspace;
auto getActiveWorkspace() -> Workspace;
void onEvent(const std::string& ev) override;
void queryActiveWorkspace();
void setClass(const std::string&, bool enable);
@@ -36,6 +35,7 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler {
std::mutex mutex_;
const Bar& bar_;
Workspace workspace_;
IPC& m_ipc;
};
} // namespace waybar::modules::hyprland
@@ -26,18 +26,32 @@ namespace waybar::modules::hyprland {
class Workspaces;
struct WindowRepr {
std::string address;
std::string window_class;
std::string window_title;
std::string repr_rewrite;
bool isActive = false;
public:
bool empty() const { return address.empty(); }
void setActive(bool value) { isActive = value; }
};
class WindowCreationPayload {
public:
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
std::string window_repr);
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
std::string window_class, std::string window_title);
WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
WindowRepr window_repr);
WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
const std::string& window_class, const std::string& window_title,
bool is_active);
WindowCreationPayload(Json::Value const& client_data);
int incrementTimeSpentUncreated();
bool isEmpty(Workspaces& workspace_manager);
bool reprIsReady() const { return std::holds_alternative<Repr>(m_window); }
std::string repr(Workspaces& workspace_manager);
WindowRepr repr(Workspaces& workspace_manager);
void setActive(bool value) { m_isActive = value; }
std::string getWorkspaceName() const { return m_workspaceName; }
WindowAddress getAddress() const { return m_windowAddress; }
@@ -48,12 +62,13 @@ class WindowCreationPayload {
void clearAddr();
void clearWorkspaceName();
using Repr = std::string;
using Repr = WindowRepr;
using ClassAndTitle = std::pair<std::string, std::string>;
std::variant<Repr, ClassAndTitle> m_window;
WindowAddress m_windowAddress;
std::string m_workspaceName;
bool m_isActive = false;
int m_timeSpentUncreated = 0;
};
+28 -7
View File
@@ -30,6 +30,7 @@ class Workspace {
public:
explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
const Json::Value& clients_data = Json::Value::nullRef);
~Workspace();
std::string& selectIcon(std::map<std::string, std::string>& icons_map);
Gtk::Button& button() { return m_button; };
@@ -42,10 +43,18 @@ class Workspace {
bool isPersistentConfig() const { return m_isPersistentConfig; };
bool isPersistentRule() const { return m_isPersistentRule; };
bool isVisible() const { return m_isVisible; };
bool isEmpty() const { return m_windows == 0; };
bool isUrgent() const { return m_isUrgent; };
bool handleClicked(GdkEventButton* bt) const;
bool handleEnter(GdkEventCrossing* event);
bool handleLeave(GdkEventCrossing* event);
void startHoverCheck();
void stopHoverCheck();
bool syncHoverClass();
bool pointerInsideButton();
void setActive(bool value = true) { m_isActive = value; };
void setPersistentRule(bool value = true) { m_isPersistentRule = value; };
void setPersistentConfig(bool value = true) { m_isPersistentConfig = value; };
@@ -54,21 +63,25 @@ class Workspace {
void setWindows(uint value) { m_windows = value; };
void setName(std::string const& value) { m_name = value; };
void setOutput(std::string const& value) { m_output = value; };
bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); }
bool containsWindow(WindowAddress const& addr) const {
return std::ranges::any_of(m_windowMap,
[&addr](const auto& window) { return window.address == addr; });
};
void insertWindow(WindowCreationPayload create_window_payload);
std::string removeWindow(WindowAddress const& addr);
void initializeWindowMap(const Json::Value& clients_data);
void setActiveWindow(WindowAddress const& addr);
bool onWindowOpened(WindowCreationPayload const& create_window_payload);
std::optional<std::string> closeWindow(WindowAddress const& addr);
std::optional<WindowRepr> closeWindow(WindowAddress const& addr);
void update(const std::string& format, const std::string& icon);
void update(const std::string& workspace_icon);
private:
Workspaces& m_workspaceManager;
int m_id;
std::string m_name;
std::string m_prevNameClass;
std::string m_output;
uint m_windows;
bool m_isActive = false;
@@ -78,11 +91,19 @@ class Workspace {
bool m_isUrgent = false;
bool m_isVisible = false;
std::map<WindowAddress, std::string> m_windowMap;
sigc::connection m_hoverCheckConnection;
std::vector<WindowRepr> m_windowMap;
Gtk::Button m_button;
Gtk::Box m_content;
Gtk::Label m_label;
Gtk::Label m_labelBefore;
Gtk::Label m_labelAfter;
bool isEmpty() const;
void updateTaskbar(const std::string& workspace_icon);
bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const;
bool shouldSkipWindow(const WindowRepr& window_repr) const;
IPC& m_ipc;
};
+62 -3
View File
@@ -1,8 +1,10 @@
#pragma once
#include <gtkmm/button.h>
#include <gtkmm/enums.h>
#include <gtkmm/label.h>
#include <json/value.h>
#include <sigc++/connection.h>
#include <cstdint>
#include <map>
@@ -18,6 +20,7 @@
#include "modules/hyprland/windowcreationpayload.hpp"
#include "modules/hyprland/workspace.hpp"
#include "util/enum.hpp"
#include "util/icon_loader.hpp"
#include "util/regex_collection.hpp"
using WindowAddress = std::string;
@@ -36,18 +39,38 @@ class Workspaces : public AModule, public EventHandler {
auto allOutputs() const -> bool { return m_allOutputs; }
auto showSpecial() const -> bool { return m_showSpecial; }
auto activeOnly() const -> bool { return m_activeOnly; }
auto hideActive() const -> bool { return m_hideActive; }
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
auto persistentOnly() const -> bool { return m_persistentOnly; }
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
auto uniqueIcons() const -> bool { return m_uniqueIcons; }
auto enableTaskbar() const -> bool { return m_enableTaskbar; }
auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }
auto barScroll() const -> bool { return m_barScroll; }
auto getBarOutput() const -> std::string { return m_bar.output->name; }
auto formatBefore() const -> std::string { return m_formatBefore; }
auto formatAfter() const -> std::string { return m_formatAfter; }
auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; }
auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; }
auto taskbarIconSize() const -> int { return m_taskbarIconSize; }
auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; }
auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; }
auto onClickWindow() const -> std::string { return m_onClickWindow; }
auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }
auto maxWindows() const -> int { return m_maxWindows; }
std::string getRewrite(std::string window_class, std::string window_title);
enum class ActiveWindowPosition { NONE, FIRST, LAST };
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
std::string getRewrite(const std::string& window_class, const std::string& window_title);
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
auto windowRewriteGroupThreshold() const -> int { return m_windowRewriteGroupThreshold; }
auto const& getWindowRewriteGroupFormat() const { return m_windowRewriteGroupFormat; }
bool isWorkspaceIgnored(std::string const& workspace_name);
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
const IconLoader& iconLoader() const { return m_iconLoader; }
private:
void onEvent(const std::string& e) override;
@@ -71,6 +94,8 @@ class Workspaces : public AModule, public EventHandler {
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;
auto populateWindowRewriteConfig(const Json::Value& config) -> void;
auto populateMaxWindowsConfig(const Json::Value& config) -> void;
auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;
void registerIpc();
@@ -93,6 +118,7 @@ class Workspaces : public AModule, public EventHandler {
void onWindowMoved(std::string const& payload);
void onWindowTitleEvent(std::string const& payload);
void onActiveWindowChanged(WindowAddress const& payload);
void onConfigReloaded();
@@ -104,6 +130,8 @@ class Workspaces : public AModule, public EventHandler {
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload);
// scroll events
bool handleScroll(GdkEventScroll* e) override;
// Update methods
void doUpdate();
@@ -124,16 +152,18 @@ class Workspaces : public AModule, public EventHandler {
bool m_allOutputs = false;
bool m_showSpecial = false;
bool m_activeOnly = false;
bool m_hideActive = false;
bool m_specialVisibleOnly = false;
bool m_persistentOnly = false;
bool m_moveToMonitor = false;
bool m_uniqueIcons = false;
bool m_barScroll = false;
Json::Value m_persistentWorkspaceConfig;
// Map for windows stored in workspaces not present in the current bar.
// This happens when the user has multiple monitors (hence, multiple bars)
// and doesn't share windows across bars (a.k.a `all-outputs` = false)
std::map<WindowAddress, std::string> m_orphanWindowMap;
std::map<WindowAddress, WindowRepr, std::less<>> m_orphanWindowMap;
enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };
util::EnumParser<SortMethod> m_enumParser;
@@ -144,12 +174,15 @@ class Workspaces : public AModule, public EventHandler {
{"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED},
{"DEFAULT", SortMethod::DEFAULT}};
std::string m_format;
std::string m_formatBefore;
std::string m_formatAfter;
std::map<std::string, std::string> m_iconsMap;
util::RegexCollection m_windowRewriteRules;
bool m_anyWindowRewriteRuleUsesTitle = false;
std::string m_formatWindowSeparator;
int m_windowRewriteGroupThreshold = 0;
std::string m_windowRewriteGroupFormat = "{icon}×{count}";
bool m_withIcon;
uint64_t m_monitorId;
@@ -160,12 +193,38 @@ class Workspaces : public AModule, public EventHandler {
std::vector<std::string> m_workspacesToRemove;
std::vector<WindowCreationPayload> m_windowsToCreate;
IconLoader m_iconLoader;
bool m_enableTaskbar = false;
bool m_updateActiveWindow = false;
bool m_taskbarWithIcon = false;
bool m_taskbarWithTitle = false;
std::string m_taskbarFormatBefore;
std::string m_taskbarFormatAfter;
int m_taskbarIconSize = 16;
Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL;
bool m_taskbarReverseDirection = false;
util::EnumParser<ActiveWindowPosition> m_activeWindowEnumParser;
ActiveWindowPosition m_activeWindowPosition = ActiveWindowPosition::NONE;
std::map<std::string, ActiveWindowPosition> m_activeWindowPositionMap = {
{"NONE", ActiveWindowPosition::NONE},
{"FIRST", ActiveWindowPosition::FIRST},
{"LAST", ActiveWindowPosition::LAST},
};
std::string m_onClickWindow;
std::string m_currentActiveWindowAddress;
int m_maxWindows = 0;
std::vector<std::regex> m_ignoreWorkspaces;
std::vector<std::regex> m_ignoreWindows;
std::mutex m_mutex;
const Bar& m_bar;
Gtk::Box m_box;
sigc::connection m_scrollEventConnection_;
IPC& m_ipc;
sigc::connection m_debounceTimer;
bool m_updatePending = false;
};
} // namespace waybar::modules::hyprland