Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hyprland/workspaces] Implement workspace taskbars #3868

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions include/modules/hyprland/windowcreationpayload.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,28 @@ namespace waybar::modules::hyprland {

class Workspaces;

struct WindowRepr {
std::string address;
std::string window_class;
std::string window_title;
std::string repr_rewrite;

public:
bool empty() const { return address.empty(); }
};

class WindowCreationPayload {
public:
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
std::string window_repr);
WindowRepr window_repr);
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
std::string window_class, std::string window_title);
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);

std::string getWorkspaceName() const { return m_workspaceName; }
WindowAddress getAddress() const { return m_windowAddress; }
Expand All @@ -48,7 +58,7 @@ 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;

Expand Down
18 changes: 12 additions & 6 deletions include/modules/hyprland/workspace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,17 @@ 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_paylod);
std::string removeWindow(WindowAddress const& addr);
void initializeWindowMap(const Json::Value& clients_data);

bool onWindowOpened(WindowCreationPayload const& create_window_paylod);
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;
Expand All @@ -78,11 +80,15 @@ class Workspace {
bool m_isUrgent = false;
bool m_isVisible = false;

std::map<WindowAddress, std::string> m_windowMap;
std::vector<WindowRepr> m_windowMap;

Gtk::Button m_button;
Gtk::Box m_content;
Gtk::Label m_label;
Gtk::Label m_labelBefore;
Gtk::Label m_labelAfter;

void updateTaskbar(const std::string& workspace_icon);
bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const;
};

} // namespace waybar::modules::hyprland
28 changes: 26 additions & 2 deletions include/modules/hyprland/workspaces.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <gtkmm/button.h>
#include <gtkmm/enums.h>
#include <gtkmm/label.h>
#include <json/value.h>

Expand All @@ -17,6 +18,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;
Expand All @@ -37,14 +39,24 @@ class Workspaces : public AModule, public EventHandler {
auto activeOnly() const -> bool { return m_activeOnly; }
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
auto enableTaskbar() const -> bool { return m_enableTaskbar; }
auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }

auto getBarOutput() const -> std::string { return m_bar.output->name; }
auto formatBefore() const -> std::string { return m_formatBefore; }
auto formatAfter() const -> std::string { return m_formatAfter; }
auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; }
auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; }
auto taskbarIconSize() const -> int { return m_taskbarIconSize; }
auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; }
auto onClickWindow() const -> std::string { return m_onClickWindow; }

std::string getRewrite(std::string window_class, std::string window_title);
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
bool isWorkspaceIgnored(std::string const& workspace_name);

bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
const IconLoader& iconLoader() const { return m_iconLoader; }

private:
void onEvent(const std::string& e) override;
Expand All @@ -67,6 +79,7 @@ class Workspaces : public AModule, public EventHandler {
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;
auto populateWindowRewriteConfig(const Json::Value& config) -> void;
auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;

void registerIpc();

Expand Down Expand Up @@ -119,7 +132,7 @@ class Workspaces : public AModule, public EventHandler {
// Map for windows stored in workspaces not present in the current bar.
// This happens when the user has multiple monitors (hence, multiple bars)
// and doesn't share windows accross 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, DEFAULT };
util::EnumParser<SortMethod> m_enumParser;
Expand All @@ -129,7 +142,8 @@ class Workspaces : public AModule, public EventHandler {
{"NUMBER", SortMethod::NUMBER},
{"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;
Expand All @@ -145,6 +159,16 @@ 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_taskbarWithIcon = false;
bool m_taskbarWithTitle = false;
std::string m_taskbarFormatBefore;
std::string m_taskbarFormatAfter;
int m_taskbarIconSize = 16;
Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL;
std::string m_onClickWindow;

std::vector<std::regex> m_ignoreWorkspaces;

std::mutex m_mutex;
Expand Down
8 changes: 3 additions & 5 deletions include/modules/wlr/taskbar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "bar.hpp"
#include "client.hpp"
#include "giomm/desktopappinfo.h"
#include "util/icon_loader.hpp"
#include "util/json.hpp"
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"

Expand Down Expand Up @@ -89,9 +90,6 @@ class Task {
std::string state_string(bool = false) const;
void set_minimize_hint();
void on_button_size_allocated(Gtk::Allocation &alloc);
void set_app_info_from_app_id_list(const std::string &app_id_list);
bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
void hide_if_ignored();

public:
Expand Down Expand Up @@ -153,7 +151,7 @@ class Taskbar : public waybar::AModule {
Gtk::Box box_;
std::vector<TaskPtr> tasks_;

std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
IconLoader icon_loader_;
std::unordered_set<std::string> ignore_list_;
std::map<std::string, std::string> app_ids_replace_map_;

Expand All @@ -178,7 +176,7 @@ class Taskbar : public waybar::AModule {
bool show_output(struct wl_output *) const;
bool all_outputs() const;

const std::vector<Glib::RefPtr<Gtk::IconTheme>> &icon_themes() const;
const IconLoader &icon_loader() const;
const std::unordered_set<std::string> &ignore_list() const;
const std::map<std::string, std::string> &app_ids_replace_map() const;
};
Expand Down
34 changes: 34 additions & 0 deletions include/util/icon_loader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include <gdkmm/general.h>
#include <gio/gdesktopappinfo.h>
#include <giomm/desktopappinfo.h>
#include <glibmm/fileutils.h>
#include <gtkmm/image.h>
#include <spdlog/spdlog.h>

#include <string>
#include <vector>

#include "util/gtk_icon.hpp"

class IconLoader {
private:
std::vector<Glib::RefPtr<Gtk::IconTheme>> custom_icon_themes_;
Glib::RefPtr<Gtk::IconTheme> default_icon_theme_ = Gtk::IconTheme::get_default();
static std::vector<std::string> search_prefix();
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string &app_id);
static Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id);
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string const &icon_path, int size);
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
const std::string &app_id);
static bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);

public:
void add_custom_icon_theme(const std::string &theme_name);
bool image_load_icon(Gtk::Image &image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,
int size) const;
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_from_app_id_list(
const std::string &app_id_list);
};
23 changes: 23 additions & 0 deletions include/util/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,26 @@ inline std::string capitalize(const std::string& str) {
[](unsigned char c) { return std::toupper(c); });
return result;
}

inline std::string toLower(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}

inline std::vector<std::string> split(std::string_view s, std::string_view delimiter,
int max_splits = -1) {
std::vector<std::string> result;
size_t pos = 0;
size_t next_pos = 0;
while ((next_pos = s.find(delimiter, pos)) != std::string::npos) {
result.push_back(std::string(s.substr(pos, next_pos - pos)));
pos = next_pos + delimiter.size();
if (max_splits > 0 && result.size() == static_cast<size_t>(max_splits)) {
break;
}
}
result.push_back(std::string(s.substr(pos)));
return result;
}
41 changes: 37 additions & 4 deletions man/waybar-hyprland-workspaces.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,49 @@ Addressed by *hyprland/workspaces*
Regex rules to map window class to an icon or preferred method of representation for a workspace's window.
Keys are the rules, while the values are the methods of representation. Values may use the placeholders {class} and {title} to use the window's original class and/or title respectively.
Rules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching.
You may assign an empty value to a rule to have it ignored from generating any representation in workspaces.
You may assign an empty value to a rule to have it ignored from generating any representation in workspaces. ++
This setting is ignored if *workspace-taskbar.enable* is set to true.

*window-rewrite-default*:
*window-rewrite-default*: ++
typeof: string ++
default: "?" ++
The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*.
The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*. ++
This setting is ignored if *workspace-taskbar.enable* is set to true.

*format-window-separator*: ++
typeof: string ++
default: " " ++
The separator to be used between windows in a workspace.
The separator to be used between windows in a workspace. ++
This setting is ignored if *workspace-taskbar.enable* is set to true.

*workspace-taskbar*: ++
typeof: object ++
Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.

*enable*: ++
typeof: bool ++
default: false ++
Enables the workspace taskbar mode.

*format*: ++
typeof: string ++
default: {icon} ++
Format to use for each window in the workspace taskbar. Available placeholders are {icon} and {title}.

*icon-size*: ++
typeof: int ++
default: 16 ++
Size of the icons in the workspace taskbar.

*icon-theme*: ++
typeof: string | array ++
default: [] ++
Icon theme to use for the workspace taskbar. If an array is provided, the first theme that is found for a given icon will be used. If no theme is found (or the array is empty), the default icon theme is used.

*orientation*: ++
typeof: "horizontal" | "vertical" ++
default: horizontal ++
Direction in which the workspace taskbar is displayed.

*show-special*: ++
typeof: bool ++
Expand Down Expand Up @@ -178,3 +210,4 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *#workspaces button.special*
- *#workspaces button.urgent*
- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)
- *#workspaces .taskbar-window* (each window in the taskbar)
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ src_files = files(
'src/util/sanitize_str.cpp',
'src/util/rewrite_string.cpp',
'src/util/gtk_icon.cpp',
'src/util/icon_loader.cpp',
'src/util/regex_collection.cpp',
'src/util/css_reload_helper.cpp'
)
Expand Down
7 changes: 4 additions & 3 deletions src/modules/hyprland/windowcreationpayload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
}

WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowAddress window_address, std::string window_repr)
WindowAddress window_address, WindowRepr window_repr)
: m_window(std::move(window_repr)),
m_windowAddress(std::move(window_address)),
m_workspaceName(std::move(workspace_name)) {
Expand Down Expand Up @@ -92,13 +92,14 @@ void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
m_workspaceName = new_workspace_name;
}

std::string WindowCreationPayload::repr(Workspaces &workspace_manager) {
WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) {
if (std::holds_alternative<Repr>(m_window)) {
return std::get<Repr>(m_window);
}
if (std::holds_alternative<ClassAndTitle>(m_window)) {
auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);
return workspace_manager.getRewrite(window_class, window_title);
return {m_windowAddress, window_class, window_title,
workspace_manager.getRewrite(window_class, window_title)};
}
// Unreachable
spdlog::error("WorkspaceWindow::repr: Unreachable");
Expand Down
Loading