Merge pull request #3868 from pol-rivero/master
[hyprland/workspaces] Implement workspace taskbars
This commit is contained in:
@ -26,18 +26,31 @@ namespace waybar::modules::hyprland {
|
|||||||
|
|
||||||
class Workspaces;
|
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 {
|
class WindowCreationPayload {
|
||||||
public:
|
public:
|
||||||
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
||||||
std::string window_repr);
|
WindowRepr window_repr);
|
||||||
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
||||||
std::string window_class, std::string window_title);
|
std::string window_class, std::string window_title, bool is_active);
|
||||||
WindowCreationPayload(Json::Value const& client_data);
|
WindowCreationPayload(Json::Value const& client_data);
|
||||||
|
|
||||||
int incrementTimeSpentUncreated();
|
int incrementTimeSpentUncreated();
|
||||||
bool isEmpty(Workspaces& workspace_manager);
|
bool isEmpty(Workspaces& workspace_manager);
|
||||||
bool reprIsReady() const { return std::holds_alternative<Repr>(m_window); }
|
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; }
|
std::string getWorkspaceName() const { return m_workspaceName; }
|
||||||
WindowAddress getAddress() const { return m_windowAddress; }
|
WindowAddress getAddress() const { return m_windowAddress; }
|
||||||
@ -48,12 +61,13 @@ class WindowCreationPayload {
|
|||||||
void clearAddr();
|
void clearAddr();
|
||||||
void clearWorkspaceName();
|
void clearWorkspaceName();
|
||||||
|
|
||||||
using Repr = std::string;
|
using Repr = WindowRepr;
|
||||||
using ClassAndTitle = std::pair<std::string, std::string>;
|
using ClassAndTitle = std::pair<std::string, std::string>;
|
||||||
std::variant<Repr, ClassAndTitle> m_window;
|
std::variant<Repr, ClassAndTitle> m_window;
|
||||||
|
|
||||||
WindowAddress m_windowAddress;
|
WindowAddress m_windowAddress;
|
||||||
std::string m_workspaceName;
|
std::string m_workspaceName;
|
||||||
|
bool m_isActive = false;
|
||||||
|
|
||||||
int m_timeSpentUncreated = 0;
|
int m_timeSpentUncreated = 0;
|
||||||
};
|
};
|
||||||
|
@ -54,15 +54,18 @@ class Workspace {
|
|||||||
void setWindows(uint value) { m_windows = value; };
|
void setWindows(uint value) { m_windows = value; };
|
||||||
void setName(std::string const& value) { m_name = value; };
|
void setName(std::string const& value) { m_name = value; };
|
||||||
void setOutput(std::string const& value) { m_output = value; };
|
void setOutput(std::string const& value) { m_output = value; };
|
||||||
bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); }
|
bool containsWindow(WindowAddress const& addr) const {
|
||||||
|
return std::ranges::any_of(m_windowMap,
|
||||||
|
[&addr](const auto& window) { return window.address == addr; });
|
||||||
|
};
|
||||||
void insertWindow(WindowCreationPayload create_window_payload);
|
void insertWindow(WindowCreationPayload create_window_payload);
|
||||||
std::string removeWindow(WindowAddress const& addr);
|
|
||||||
void initializeWindowMap(const Json::Value& clients_data);
|
void initializeWindowMap(const Json::Value& clients_data);
|
||||||
|
void setActiveWindow(WindowAddress const& addr);
|
||||||
|
|
||||||
bool onWindowOpened(WindowCreationPayload const& create_window_payload);
|
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:
|
private:
|
||||||
Workspaces& m_workspaceManager;
|
Workspaces& m_workspaceManager;
|
||||||
@ -78,11 +81,16 @@ class Workspace {
|
|||||||
bool m_isUrgent = false;
|
bool m_isUrgent = false;
|
||||||
bool m_isVisible = false;
|
bool m_isVisible = false;
|
||||||
|
|
||||||
std::map<WindowAddress, std::string> m_windowMap;
|
std::vector<WindowRepr> m_windowMap;
|
||||||
|
|
||||||
Gtk::Button m_button;
|
Gtk::Button m_button;
|
||||||
Gtk::Box m_content;
|
Gtk::Box m_content;
|
||||||
Gtk::Label m_label;
|
Gtk::Label m_labelBefore;
|
||||||
|
Gtk::Label m_labelAfter;
|
||||||
|
|
||||||
|
void updateTaskbar(const std::string& workspace_icon);
|
||||||
|
bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const;
|
||||||
|
bool shouldSkipWindow(const WindowRepr& window_repr) const;
|
||||||
IPC& m_ipc;
|
IPC& m_ipc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <gtkmm/button.h>
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/enums.h>
|
||||||
#include <gtkmm/label.h>
|
#include <gtkmm/label.h>
|
||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
|
|
||||||
@ -18,6 +19,7 @@
|
|||||||
#include "modules/hyprland/windowcreationpayload.hpp"
|
#include "modules/hyprland/windowcreationpayload.hpp"
|
||||||
#include "modules/hyprland/workspace.hpp"
|
#include "modules/hyprland/workspace.hpp"
|
||||||
#include "util/enum.hpp"
|
#include "util/enum.hpp"
|
||||||
|
#include "util/icon_loader.hpp"
|
||||||
#include "util/regex_collection.hpp"
|
#include "util/regex_collection.hpp"
|
||||||
|
|
||||||
using WindowAddress = std::string;
|
using WindowAddress = std::string;
|
||||||
@ -39,14 +41,25 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
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; }
|
||||||
|
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 getBarOutput() const -> std::string { return m_bar.output->name; }
|
||||||
|
auto formatBefore() const -> std::string { return m_formatBefore; }
|
||||||
|
auto formatAfter() const -> std::string { return m_formatAfter; }
|
||||||
|
auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; }
|
||||||
|
auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; }
|
||||||
|
auto taskbarIconSize() const -> int { return m_taskbarIconSize; }
|
||||||
|
auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; }
|
||||||
|
auto onClickWindow() const -> std::string { return m_onClickWindow; }
|
||||||
|
auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }
|
||||||
|
|
||||||
std::string getRewrite(std::string window_class, std::string window_title);
|
std::string getRewrite(std::string window_class, std::string window_title);
|
||||||
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
||||||
bool isWorkspaceIgnored(std::string const& workspace_name);
|
bool isWorkspaceIgnored(std::string const& workspace_name);
|
||||||
|
|
||||||
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
||||||
|
const IconLoader& iconLoader() const { return m_iconLoader; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onEvent(const std::string& e) override;
|
void onEvent(const std::string& e) override;
|
||||||
@ -70,6 +83,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
|
auto 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 populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;
|
||||||
|
|
||||||
void registerIpc();
|
void registerIpc();
|
||||||
|
|
||||||
@ -92,6 +106,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
void onWindowMoved(std::string const& payload);
|
void onWindowMoved(std::string const& payload);
|
||||||
|
|
||||||
void onWindowTitleEvent(std::string const& payload);
|
void onWindowTitleEvent(std::string const& payload);
|
||||||
|
void onActiveWindowChanged(WindowAddress const& payload);
|
||||||
|
|
||||||
void onConfigReloaded();
|
void onConfigReloaded();
|
||||||
|
|
||||||
@ -131,7 +146,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
// Map for windows stored in workspaces not present in the current bar.
|
// Map for windows stored in workspaces not present in the current bar.
|
||||||
// This happens when the user has multiple monitors (hence, multiple bars)
|
// This happens when the user has multiple monitors (hence, multiple bars)
|
||||||
// and doesn't share windows across bars (a.k.a `all-outputs` = false)
|
// and doesn't share windows across bars (a.k.a `all-outputs` = false)
|
||||||
std::map<WindowAddress, std::string> m_orphanWindowMap;
|
std::map<WindowAddress, WindowRepr, std::less<>> m_orphanWindowMap;
|
||||||
|
|
||||||
enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };
|
enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };
|
||||||
util::EnumParser<SortMethod> m_enumParser;
|
util::EnumParser<SortMethod> m_enumParser;
|
||||||
@ -142,7 +157,8 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
{"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED},
|
{"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED},
|
||||||
{"DEFAULT", SortMethod::DEFAULT}};
|
{"DEFAULT", SortMethod::DEFAULT}};
|
||||||
|
|
||||||
std::string m_format;
|
std::string m_formatBefore;
|
||||||
|
std::string m_formatAfter;
|
||||||
|
|
||||||
std::map<std::string, std::string> m_iconsMap;
|
std::map<std::string, std::string> m_iconsMap;
|
||||||
util::RegexCollection m_windowRewriteRules;
|
util::RegexCollection m_windowRewriteRules;
|
||||||
@ -158,7 +174,20 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
std::vector<std::string> m_workspacesToRemove;
|
std::vector<std::string> m_workspacesToRemove;
|
||||||
std::vector<WindowCreationPayload> m_windowsToCreate;
|
std::vector<WindowCreationPayload> m_windowsToCreate;
|
||||||
|
|
||||||
|
IconLoader m_iconLoader;
|
||||||
|
bool m_enableTaskbar = false;
|
||||||
|
bool m_updateActiveWindow = false;
|
||||||
|
bool m_taskbarWithIcon = false;
|
||||||
|
bool m_taskbarWithTitle = false;
|
||||||
|
std::string m_taskbarFormatBefore;
|
||||||
|
std::string m_taskbarFormatAfter;
|
||||||
|
int m_taskbarIconSize = 16;
|
||||||
|
Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL;
|
||||||
|
std::string m_onClickWindow;
|
||||||
|
std::string m_currentActiveWindowAddress;
|
||||||
|
|
||||||
std::vector<std::regex> m_ignoreWorkspaces;
|
std::vector<std::regex> m_ignoreWorkspaces;
|
||||||
|
std::vector<std::regex> m_ignoreWindows;
|
||||||
|
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
const Bar& m_bar;
|
const Bar& m_bar;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
#include "giomm/desktopappinfo.h"
|
#include "giomm/desktopappinfo.h"
|
||||||
|
#include "util/icon_loader.hpp"
|
||||||
#include "util/json.hpp"
|
#include "util/json.hpp"
|
||||||
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
|
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
@ -89,9 +90,6 @@ class Task {
|
|||||||
std::string state_string(bool = false) const;
|
std::string state_string(bool = false) const;
|
||||||
void set_minimize_hint();
|
void set_minimize_hint();
|
||||||
void on_button_size_allocated(Gtk::Allocation &alloc);
|
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();
|
void hide_if_ignored();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -153,7 +151,7 @@ class Taskbar : public waybar::AModule {
|
|||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
std::vector<TaskPtr> tasks_;
|
std::vector<TaskPtr> tasks_;
|
||||||
|
|
||||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
|
IconLoader icon_loader_;
|
||||||
std::unordered_set<std::string> ignore_list_;
|
std::unordered_set<std::string> ignore_list_;
|
||||||
std::map<std::string, std::string> app_ids_replace_map_;
|
std::map<std::string, std::string> app_ids_replace_map_;
|
||||||
|
|
||||||
@ -178,7 +176,7 @@ class Taskbar : public waybar::AModule {
|
|||||||
bool show_output(struct wl_output *) const;
|
bool show_output(struct wl_output *) const;
|
||||||
bool all_outputs() 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::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;
|
||||||
};
|
};
|
||||||
|
34
include/util/icon_loader.hpp
Normal file
34
include/util/icon_loader.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gdkmm/general.h>
|
||||||
|
#include <gio/gdesktopappinfo.h>
|
||||||
|
#include <giomm/desktopappinfo.h>
|
||||||
|
#include <glibmm/fileutils.h>
|
||||||
|
#include <gtkmm/image.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util/gtk_icon.hpp"
|
||||||
|
|
||||||
|
class IconLoader {
|
||||||
|
private:
|
||||||
|
std::vector<Glib::RefPtr<Gtk::IconTheme>> custom_icon_themes_;
|
||||||
|
Glib::RefPtr<Gtk::IconTheme> default_icon_theme_ = Gtk::IconTheme::get_default();
|
||||||
|
static std::vector<std::string> search_prefix();
|
||||||
|
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string &app_id);
|
||||||
|
static Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id);
|
||||||
|
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string const &icon_path, int size);
|
||||||
|
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||||
|
const std::string &app_id);
|
||||||
|
static bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void add_custom_icon_theme(const std::string &theme_name);
|
||||||
|
bool image_load_icon(Gtk::Image &image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,
|
||||||
|
int size) const;
|
||||||
|
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_from_app_id_list(
|
||||||
|
const std::string &app_id_list);
|
||||||
|
};
|
@ -23,3 +23,26 @@ inline std::string capitalize(const std::string& str) {
|
|||||||
[](unsigned char c) { return std::toupper(c); });
|
[](unsigned char c) { return std::toupper(c); });
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
@ -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.
|
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.
|
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.
|
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 ++
|
typeof: string ++
|
||||||
default: "?" ++
|
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*: ++
|
*format-window-separator*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: " " ++
|
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*: ++
|
*show-special*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
@ -184,3 +216,4 @@ Additional to workspace name matching, the following *format-icons* can be set.
|
|||||||
- *#workspaces button.special*
|
- *#workspaces button.special*
|
||||||
- *#workspaces button.urgent*
|
- *#workspaces button.urgent*
|
||||||
- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)
|
- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)
|
||||||
|
- *#workspaces .taskbar-window* (each window in the taskbar)
|
||||||
|
@ -183,6 +183,7 @@ src_files = files(
|
|||||||
'src/util/sanitize_str.cpp',
|
'src/util/sanitize_str.cpp',
|
||||||
'src/util/rewrite_string.cpp',
|
'src/util/rewrite_string.cpp',
|
||||||
'src/util/gtk_icon.cpp',
|
'src/util/gtk_icon.cpp',
|
||||||
|
'src/util/icon_loader.cpp',
|
||||||
'src/util/regex_collection.cpp',
|
'src/util/regex_collection.cpp',
|
||||||
'src/util/css_reload_helper.cpp'
|
'src/util/css_reload_helper.cpp'
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
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_window(std::move(window_repr)),
|
||||||
m_windowAddress(std::move(window_address)),
|
m_windowAddress(std::move(window_address)),
|
||||||
m_workspaceName(std::move(workspace_name)) {
|
m_workspaceName(std::move(workspace_name)) {
|
||||||
@ -30,10 +30,11 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
|||||||
|
|
||||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||||
WindowAddress window_address, std::string window_class,
|
WindowAddress window_address, std::string window_class,
|
||||||
std::string window_title)
|
std::string window_title, bool is_active)
|
||||||
: m_window(std::make_pair(std::move(window_class), std::move(window_title))),
|
: m_window(std::make_pair(std::move(window_class), std::move(window_title))),
|
||||||
m_windowAddress(std::move(window_address)),
|
m_windowAddress(std::move(window_address)),
|
||||||
m_workspaceName(std::move(workspace_name)) {
|
m_workspaceName(std::move(workspace_name)),
|
||||||
|
m_isActive(is_active) {
|
||||||
clearAddr();
|
clearAddr();
|
||||||
clearWorkspaceName();
|
clearWorkspaceName();
|
||||||
}
|
}
|
||||||
@ -92,13 +93,14 @@ void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
|
|||||||
m_workspaceName = 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)) {
|
if (std::holds_alternative<Repr>(m_window)) {
|
||||||
return std::get<Repr>(m_window);
|
return std::get<Repr>(m_window);
|
||||||
}
|
}
|
||||||
if (std::holds_alternative<ClassAndTitle>(m_window)) {
|
if (std::holds_alternative<ClassAndTitle>(m_window)) {
|
||||||
auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);
|
auto const &[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), m_isActive};
|
||||||
}
|
}
|
||||||
// Unreachable
|
// Unreachable
|
||||||
spdlog::error("WorkspaceWindow::repr: Unreachable");
|
spdlog::error("WorkspaceWindow::repr: Unreachable");
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "modules/hyprland/workspaces.hpp"
|
#include "modules/hyprland/workspaces.hpp"
|
||||||
|
#include "util/command.hpp"
|
||||||
|
#include "util/icon_loader.hpp"
|
||||||
|
|
||||||
namespace waybar::modules::hyprland {
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
@ -32,7 +34,12 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
|
|||||||
false);
|
false);
|
||||||
|
|
||||||
m_button.set_relief(Gtk::RELIEF_NONE);
|
m_button.set_relief(Gtk::RELIEF_NONE);
|
||||||
m_content.set_center_widget(m_label);
|
if (m_workspaceManager.enableTaskbar()) {
|
||||||
|
m_content.set_orientation(m_workspaceManager.taskbarOrientation());
|
||||||
|
m_content.pack_start(m_labelBefore, false, false);
|
||||||
|
} else {
|
||||||
|
m_content.set_center_widget(m_labelBefore);
|
||||||
|
}
|
||||||
m_button.add(m_content);
|
m_button.add(m_content);
|
||||||
|
|
||||||
initializeWindowMap(clients_data);
|
initializeWindowMap(clients_data);
|
||||||
@ -47,9 +54,14 @@ void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext> &context, bool condi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> Workspace::closeWindow(WindowAddress const &addr) {
|
std::optional<WindowRepr> Workspace::closeWindow(WindowAddress const &addr) {
|
||||||
if (m_windowMap.contains(addr)) {
|
auto it = std::ranges::find_if(m_windowMap,
|
||||||
return removeWindow(addr);
|
[&addr](const auto &window) { return window.address == addr; });
|
||||||
|
// If the vector contains the address, remove it and return the window representation
|
||||||
|
if (it != m_windowMap.end()) {
|
||||||
|
WindowRepr windowRepr = *it;
|
||||||
|
m_windowMap.erase(it);
|
||||||
|
return windowRepr;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@ -91,12 +103,26 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Workspace::setActiveWindow(WindowAddress const &addr) {
|
||||||
|
for (auto &window : m_windowMap) {
|
||||||
|
window.setActive(window.address == addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
||||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||||
|
|
||||||
if (!repr.empty()) {
|
if (!repr.empty() || m_workspaceManager.enableTaskbar()) {
|
||||||
m_windowMap[create_window_payload.getAddress()] = repr;
|
auto addr = create_window_payload.getAddress();
|
||||||
|
auto it = std::ranges::find_if(
|
||||||
|
m_windowMap, [&addr](const auto &window) { return window.address == addr; });
|
||||||
|
// If the vector contains the address, update the window representation, otherwise insert it
|
||||||
|
if (it != m_windowMap.end()) {
|
||||||
|
*it = repr;
|
||||||
|
} else {
|
||||||
|
m_windowMap.emplace_back(repr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -109,12 +135,6 @@ bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payloa
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Workspace::removeWindow(WindowAddress const &addr) {
|
|
||||||
std::string windowRepr = m_windowMap[addr];
|
|
||||||
m_windowMap.erase(addr);
|
|
||||||
return windowRepr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map) {
|
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()) {
|
||||||
@ -172,7 +192,7 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
|
|||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::update(const std::string &format, const std::string &icon) {
|
void Workspace::update(const std::string &workspace_icon) {
|
||||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||||
m_button.hide();
|
m_button.hide();
|
||||||
return;
|
return;
|
||||||
@ -204,21 +224,122 @@ void Workspace::update(const std::string &format, const std::string &icon) {
|
|||||||
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
||||||
|
|
||||||
std::string windows;
|
std::string windows;
|
||||||
|
// Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no
|
||||||
|
// need to compute this if enableTaskbar() is true
|
||||||
|
if (!m_workspaceManager.enableTaskbar()) {
|
||||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||||
|
|
||||||
bool isNotFirst = false;
|
bool isNotFirst = false;
|
||||||
|
|
||||||
for (auto &[_pid, window_repr] : m_windowMap) {
|
for (const auto &window_repr : m_windowMap) {
|
||||||
if (isNotFirst) {
|
if (isNotFirst) {
|
||||||
windows.append(windowSeparator);
|
windows.append(windowSeparator);
|
||||||
}
|
}
|
||||||
isNotFirst = true;
|
isNotFirst = true;
|
||||||
windows.append(window_repr);
|
windows.append(window_repr.repr_rewrite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()),
|
auto formatBefore = m_workspaceManager.formatBefore();
|
||||||
fmt::arg("name", name()), fmt::arg("icon", icon),
|
m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()),
|
||||||
|
fmt::arg("name", name()), fmt::arg("icon", workspace_icon),
|
||||||
fmt::arg("windows", windows)));
|
fmt::arg("windows", windows)));
|
||||||
|
m_labelBefore.get_style_context()->add_class("workspace-label");
|
||||||
|
|
||||||
|
if (m_workspaceManager.enableTaskbar()) {
|
||||||
|
updateTaskbar(workspace_icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::updateTaskbar(const std::string &workspace_icon) {
|
||||||
|
for (auto child : m_content.get_children()) {
|
||||||
|
if (child != &m_labelBefore) {
|
||||||
|
m_content.remove(*child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFirst = true;
|
||||||
|
for (const auto &window_repr : m_windowMap) {
|
||||||
|
if (shouldSkipWindow(window_repr)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isFirst) {
|
||||||
|
isFirst = false;
|
||||||
|
} else if (m_workspaceManager.getWindowSeparator() != "") {
|
||||||
|
auto windowSeparator = Gtk::make_managed<Gtk::Label>(m_workspaceManager.getWindowSeparator());
|
||||||
|
m_content.pack_start(*windowSeparator, false, false);
|
||||||
|
windowSeparator->show();
|
||||||
|
}
|
||||||
|
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
||||||
|
window_box->set_tooltip_text(window_repr.window_title);
|
||||||
|
window_box->get_style_context()->add_class("taskbar-window");
|
||||||
|
if (window_repr.isActive) {
|
||||||
|
window_box->get_style_context()->add_class("active");
|
||||||
|
}
|
||||||
|
auto event_box = Gtk::manage(new Gtk::EventBox());
|
||||||
|
event_box->add(*window_box);
|
||||||
|
if (m_workspaceManager.onClickWindow() != "") {
|
||||||
|
event_box->signal_button_press_event().connect(
|
||||||
|
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||||
|
fmt::arg("title", window_repr.window_title));
|
||||||
|
if (!text_before.empty()) {
|
||||||
|
auto window_label_before = Gtk::make_managed<Gtk::Label>(text_before);
|
||||||
|
window_box->pack_start(*window_label_before, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_workspaceManager.taskbarWithIcon()) {
|
||||||
|
auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class);
|
||||||
|
int icon_size = m_workspaceManager.taskbarIconSize();
|
||||||
|
auto window_icon = Gtk::make_managed<Gtk::Image>();
|
||||||
|
m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, icon_size);
|
||||||
|
window_box->pack_start(*window_icon, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto text_after = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatAfter()),
|
||||||
|
fmt::arg("title", window_repr.window_title));
|
||||||
|
if (!text_after.empty()) {
|
||||||
|
auto window_label_after = Gtk::make_managed<Gtk::Label>(text_after);
|
||||||
|
window_box->pack_start(*window_label_after, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_content.pack_start(*event_box, true, false);
|
||||||
|
event_box->show_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto formatAfter = m_workspaceManager.formatAfter();
|
||||||
|
if (!formatAfter.empty()) {
|
||||||
|
m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()),
|
||||||
|
fmt::arg("name", name()),
|
||||||
|
fmt::arg("icon", workspace_icon)));
|
||||||
|
m_content.pack_end(m_labelAfter, false, false);
|
||||||
|
m_labelAfter.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress const &addr) const {
|
||||||
|
if (event_button->type == GDK_BUTTON_PRESS) {
|
||||||
|
std::string command = std::regex_replace(m_workspaceManager.onClickWindow(),
|
||||||
|
std::regex("\\{address\\}"), "0x" + addr);
|
||||||
|
command = std::regex_replace(command, std::regex("\\{button\\}"),
|
||||||
|
std::to_string(event_button->button));
|
||||||
|
auto res = util::command::execNoRead(command);
|
||||||
|
if (res.exit_code != 0) {
|
||||||
|
spdlog::error("Failed to execute {}: {}", command, res.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspace::shouldSkipWindow(const WindowRepr &window_repr) const {
|
||||||
|
auto ignore_list = m_workspaceManager.getIgnoredWindows();
|
||||||
|
auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto &ignoreItem) {
|
||||||
|
return std::regex_match(window_repr.window_class, ignoreItem) ||
|
||||||
|
std::regex_match(window_repr.window_title, ignoreItem);
|
||||||
|
});
|
||||||
|
return it != ignore_list.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::hyprland
|
} // namespace waybar::modules::hyprland
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "util/regex_collection.hpp"
|
#include "util/regex_collection.hpp"
|
||||||
|
#include "util/string.hpp"
|
||||||
|
|
||||||
namespace waybar::modules::hyprland {
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
@ -346,6 +347,8 @@ void Workspaces::onEvent(const std::string &ev) {
|
|||||||
onWorkspaceRenamed(payload);
|
onWorkspaceRenamed(payload);
|
||||||
} else if (eventName == "windowtitlev2") {
|
} else if (eventName == "windowtitlev2") {
|
||||||
onWindowTitleEvent(payload);
|
onWindowTitleEvent(payload);
|
||||||
|
} else if (eventName == "activewindowv2") {
|
||||||
|
onActiveWindowChanged(payload);
|
||||||
} else if (eventName == "configreloaded") {
|
} else if (eventName == "configreloaded") {
|
||||||
onConfigReloaded();
|
onConfigReloaded();
|
||||||
}
|
}
|
||||||
@ -495,12 +498,14 @@ void Workspaces::onWindowOpened(std::string const &payload) {
|
|||||||
|
|
||||||
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
|
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
|
||||||
|
|
||||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle);
|
bool isActive = m_currentActiveWindowAddress == windowAddress;
|
||||||
|
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::onWindowClosed(std::string const &addr) {
|
void Workspaces::onWindowClosed(std::string const &addr) {
|
||||||
spdlog::trace("Window closed: {}", addr);
|
spdlog::trace("Window closed: {}", addr);
|
||||||
updateWindowCount();
|
updateWindowCount();
|
||||||
|
m_orphanWindowMap.erase(addr);
|
||||||
for (auto &workspace : m_workspaces) {
|
for (auto &workspace : m_workspaces) {
|
||||||
if (workspace->closeWindow(addr)) {
|
if (workspace->closeWindow(addr)) {
|
||||||
break;
|
break;
|
||||||
@ -513,7 +518,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
|
|||||||
updateWindowCount();
|
updateWindowCount();
|
||||||
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
|
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
|
||||||
|
|
||||||
std::string windowRepr;
|
WindowRepr windowRepr;
|
||||||
|
|
||||||
// If the window was still queued to be created, just change its destination
|
// If the window was still queued to be created, just change its destination
|
||||||
// and exit
|
// and exit
|
||||||
@ -539,6 +544,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
|
|||||||
|
|
||||||
// ...and then add it to the new workspace
|
// ...and then add it to the new workspace
|
||||||
if (!windowRepr.empty()) {
|
if (!windowRepr.empty()) {
|
||||||
|
m_orphanWindowMap.erase(windowAddress);
|
||||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowRepr);
|
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowRepr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -564,8 +570,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
|||||||
(*windowWorkspace)->insertWindow(std::move(wcp));
|
(*windowWorkspace)->insertWindow(std::move(wcp));
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
|
auto queuedWindow =
|
||||||
return windowPayload.getAddress() == payload;
|
std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto &windowPayload) {
|
||||||
|
return windowPayload.getAddress() == windowAddress;
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the window was queued, rename it in the queue
|
// If the window was queued, rename it in the queue
|
||||||
@ -577,7 +584,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
|||||||
|
|
||||||
if (inserter.has_value()) {
|
if (inserter.has_value()) {
|
||||||
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
|
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
|
||||||
std::string jsonWindowAddress = fmt::format("0x{}", payload);
|
std::string jsonWindowAddress = fmt::format("0x{}", windowAddress);
|
||||||
|
|
||||||
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
|
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
|
||||||
return client["address"].asString() == jsonWindowAddress;
|
return client["address"].asString() == jsonWindowAddress;
|
||||||
@ -589,6 +596,21 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) {
|
||||||
|
spdlog::trace("Active window changed: {}", activeWindowAddress);
|
||||||
|
m_currentActiveWindowAddress = activeWindowAddress;
|
||||||
|
|
||||||
|
for (auto &[address, window] : m_orphanWindowMap) {
|
||||||
|
window.setActive(address == activeWindowAddress);
|
||||||
|
}
|
||||||
|
for (auto const &workspace : m_workspaces) {
|
||||||
|
workspace->setActiveWindow(activeWindowAddress);
|
||||||
|
}
|
||||||
|
for (auto &window : m_windowsToCreate) {
|
||||||
|
window.setActive(window.getAddress() == activeWindowAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Workspaces::onConfigReloaded() {
|
void Workspaces::onConfigReloaded() {
|
||||||
spdlog::info("Hyprland config reloaded, reinitializing hyprland/workspaces module...");
|
spdlog::info("Hyprland config reloaded, reinitializing hyprland/workspaces module...");
|
||||||
init();
|
init();
|
||||||
@ -596,8 +618,9 @@ void Workspaces::onConfigReloaded() {
|
|||||||
|
|
||||||
auto Workspaces::parseConfig(const Json::Value &config) -> void {
|
auto Workspaces::parseConfig(const Json::Value &config) -> void {
|
||||||
const auto &configFormat = config["format"];
|
const auto &configFormat = config["format"];
|
||||||
m_format = configFormat.isString() ? configFormat.asString() : "{name}";
|
m_formatBefore = configFormat.isString() ? configFormat.asString() : "{name}";
|
||||||
m_withIcon = m_format.find("{icon}") != std::string::npos;
|
m_withIcon = m_formatBefore.find("{icon}") != std::string::npos;
|
||||||
|
auto withWindows = m_formatBefore.find("{windows}") != std::string::npos;
|
||||||
|
|
||||||
if (m_withIcon && m_iconsMap.empty()) {
|
if (m_withIcon && m_iconsMap.empty()) {
|
||||||
populateIconsMap(config["format-icons"]);
|
populateIconsMap(config["format-icons"]);
|
||||||
@ -615,6 +638,15 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
|
|||||||
populateIgnoreWorkspacesConfig(config);
|
populateIgnoreWorkspacesConfig(config);
|
||||||
populateFormatWindowSeparatorConfig(config);
|
populateFormatWindowSeparatorConfig(config);
|
||||||
populateWindowRewriteConfig(config);
|
populateWindowRewriteConfig(config);
|
||||||
|
|
||||||
|
if (withWindows) {
|
||||||
|
populateWorkspaceTaskbarConfig(config);
|
||||||
|
}
|
||||||
|
if (m_enableTaskbar) {
|
||||||
|
auto parts = split(m_formatBefore, "{windows}", 1);
|
||||||
|
m_formatBefore = parts[0];
|
||||||
|
m_formatAfter = parts.size() > 1 ? parts[1] : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
|
auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
|
||||||
@ -687,6 +719,64 @@ 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::populateWorkspaceTaskbarConfig(const Json::Value &config) -> void {
|
||||||
|
const auto &workspaceTaskbar = config["workspace-taskbar"];
|
||||||
|
if (!workspaceTaskbar.isObject()) {
|
||||||
|
spdlog::debug("workspace-taskbar is not defined or is not an object, using default rules.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar);
|
||||||
|
populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow);
|
||||||
|
|
||||||
|
if (workspaceTaskbar["format"].isString()) {
|
||||||
|
/* The user defined a format string, use it */
|
||||||
|
std::string format = workspaceTaskbar["format"].asString();
|
||||||
|
m_taskbarWithTitle = format.find("{title") != std::string::npos; /* {title} or {title.length} */
|
||||||
|
auto parts = split(format, "{icon}", 1);
|
||||||
|
m_taskbarFormatBefore = parts[0];
|
||||||
|
if (parts.size() > 1) {
|
||||||
|
m_taskbarWithIcon = true;
|
||||||
|
m_taskbarFormatAfter = parts[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* The default is to only show the icon */
|
||||||
|
m_taskbarWithIcon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iconTheme = workspaceTaskbar["icon-theme"];
|
||||||
|
if (iconTheme.isArray()) {
|
||||||
|
for (auto &c : iconTheme) {
|
||||||
|
m_iconLoader.add_custom_icon_theme(c.asString());
|
||||||
|
}
|
||||||
|
} else if (iconTheme.isString()) {
|
||||||
|
m_iconLoader.add_custom_icon_theme(iconTheme.asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceTaskbar["icon-size"].isInt()) {
|
||||||
|
m_taskbarIconSize = workspaceTaskbar["icon-size"].asInt();
|
||||||
|
}
|
||||||
|
if (workspaceTaskbar["orientation"].isString() &&
|
||||||
|
toLower(workspaceTaskbar["orientation"].asString()) == "vertical") {
|
||||||
|
m_taskbarOrientation = Gtk::ORIENTATION_VERTICAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceTaskbar["on-click-window"].isString()) {
|
||||||
|
m_onClickWindow = workspaceTaskbar["on-click-window"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceTaskbar["ignore-list"].isArray()) {
|
||||||
|
for (auto &windowRegex : workspaceTaskbar["ignore-list"]) {
|
||||||
|
std::string ruleString = windowRegex.asString();
|
||||||
|
try {
|
||||||
|
m_ignoreWindows.emplace_back(ruleString, std::regex_constants::icase);
|
||||||
|
} catch (const std::regex_error &e) {
|
||||||
|
spdlog::error("Invalid rule {}: {}", ruleString, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
|
void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
|
||||||
if (!create_window_payload.isEmpty(*this)) {
|
if (!create_window_payload.isEmpty(*this)) {
|
||||||
m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);
|
m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);
|
||||||
@ -707,12 +797,18 @@ auto Workspaces::registerIpc() -> void {
|
|||||||
m_ipc.registerForIPC("urgent", this);
|
m_ipc.registerForIPC("urgent", this);
|
||||||
m_ipc.registerForIPC("configreloaded", this);
|
m_ipc.registerForIPC("configreloaded", this);
|
||||||
|
|
||||||
if (windowRewriteConfigUsesTitle()) {
|
if (windowRewriteConfigUsesTitle() || m_taskbarWithTitle) {
|
||||||
spdlog::info(
|
spdlog::info(
|
||||||
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
|
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
|
||||||
"rewrite rule uses the 'title' field.");
|
"rewrite rule uses the 'title' field.");
|
||||||
m_ipc.registerForIPC("windowtitlev2", this);
|
m_ipc.registerForIPC("windowtitlev2", this);
|
||||||
}
|
}
|
||||||
|
if (m_updateActiveWindow) {
|
||||||
|
spdlog::info(
|
||||||
|
"Registering for Hyprland's 'activewindowv2' events because 'update-active-window' is set "
|
||||||
|
"to true.");
|
||||||
|
m_ipc.registerForIPC("activewindowv2", this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::removeWorkspacesToRemove() {
|
void Workspaces::removeWorkspacesToRemove() {
|
||||||
@ -904,7 +1000,7 @@ auto Workspaces::update() -> void {
|
|||||||
|
|
||||||
void Workspaces::updateWindowCount() {
|
void Workspaces::updateWindowCount() {
|
||||||
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
||||||
for (auto &workspace : m_workspaces) {
|
for (auto const &workspace : m_workspaces) {
|
||||||
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
|
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
|
||||||
return x["name"].asString() == workspace->name() ||
|
return x["name"].asString() == workspace->name() ||
|
||||||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
|
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
|
||||||
@ -979,7 +1075,7 @@ void Workspaces::updateWorkspaceStates() {
|
|||||||
if (updatedWorkspace != updatedWorkspaces.end()) {
|
if (updatedWorkspace != updatedWorkspaces.end()) {
|
||||||
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
|
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
|
||||||
}
|
}
|
||||||
workspace->update(m_format, workspaceIcon);
|
workspace->update(workspaceIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,194 +26,6 @@
|
|||||||
|
|
||||||
namespace waybar::modules::wlr {
|
namespace waybar::modules::wlr {
|
||||||
|
|
||||||
/* Icon loading functions */
|
|
||||||
static std::vector<std::string> search_prefix() {
|
|
||||||
std::vector<std::string> prefixes = {""};
|
|
||||||
|
|
||||||
std::string home_dir = std::getenv("HOME");
|
|
||||||
prefixes.push_back(home_dir + "/.local/share/");
|
|
||||||
|
|
||||||
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
|
|
||||||
if (!xdg_data_dirs) {
|
|
||||||
prefixes.emplace_back("/usr/share/");
|
|
||||||
prefixes.emplace_back("/usr/local/share/");
|
|
||||||
} else {
|
|
||||||
std::string xdg_data_dirs_str(xdg_data_dirs);
|
|
||||||
size_t start = 0, end = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
end = xdg_data_dirs_str.find(':', start);
|
|
||||||
auto p = xdg_data_dirs_str.substr(start, end - start);
|
|
||||||
prefixes.push_back(trim(p) + "/");
|
|
||||||
|
|
||||||
start = end == std::string::npos ? end : end + 1;
|
|
||||||
} while (end != std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
|
|
||||||
|
|
||||||
return prefixes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int size) {
|
|
||||||
try {
|
|
||||||
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
|
|
||||||
return pb;
|
|
||||||
} catch (...) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string &app_id) {
|
|
||||||
static std::vector<std::string> prefixes = search_prefix();
|
|
||||||
|
|
||||||
std::vector<std::string> app_folders = {"", "applications/", "applications/kde/",
|
|
||||||
"applications/org.kde."};
|
|
||||||
|
|
||||||
std::vector<std::string> suffixes = {"", ".desktop"};
|
|
||||||
|
|
||||||
for (auto &prefix : prefixes) {
|
|
||||||
for (auto &folder : app_folders) {
|
|
||||||
for (auto &suffix : suffixes) {
|
|
||||||
auto app_info_ =
|
|
||||||
Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
|
||||||
if (!app_info_) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return app_info_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id) {
|
|
||||||
auto app_info = get_app_info_by_name(app_id);
|
|
||||||
if (app_info) {
|
|
||||||
return app_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string desktop_file = "";
|
|
||||||
|
|
||||||
gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str());
|
|
||||||
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
|
|
||||||
for (size_t i = 0; desktop_list[0][i]; i++) {
|
|
||||||
if (desktop_file == "") {
|
|
||||||
desktop_file = desktop_list[0][i];
|
|
||||||
} else {
|
|
||||||
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
|
|
||||||
if (!tmp_info)
|
|
||||||
// see https://github.com/Alexays/Waybar/issues/1446
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto startup_class = tmp_info->get_startup_wm_class();
|
|
||||||
if (startup_class == app_id) {
|
|
||||||
desktop_file = desktop_list[0][i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_strfreev(desktop_list[0]);
|
|
||||||
}
|
|
||||||
g_free(desktop_list);
|
|
||||||
|
|
||||||
return get_app_info_by_name(desktop_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Task::set_app_info_from_app_id_list(const std::string &app_id_list) {
|
|
||||||
std::string app_id;
|
|
||||||
std::istringstream stream(app_id_list);
|
|
||||||
|
|
||||||
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
|
||||||
* send a single app-id, but in any case this works fine */
|
|
||||||
while (stream >> app_id) {
|
|
||||||
app_info_ = get_desktop_app_info(app_id);
|
|
||||||
if (app_info_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lower_app_id = app_id;
|
|
||||||
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
|
||||||
[](char c) { return std::tolower(c); });
|
|
||||||
app_info_ = get_desktop_app_info(lower_app_id);
|
|
||||||
if (app_info_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t start = 0, end = app_id.size();
|
|
||||||
start = app_id.rfind(".", end);
|
|
||||||
std::string app_name = app_id.substr(start + 1, app_id.size());
|
|
||||||
app_info_ = get_desktop_app_info(app_name);
|
|
||||||
if (app_info_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
start = app_id.find("-");
|
|
||||||
app_name = app_id.substr(0, start);
|
|
||||||
app_info_ = get_desktop_app_info(app_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
|
||||||
const std::string &app_id) {
|
|
||||||
if (icon_theme->lookup_icon(app_id, 24)) return app_id;
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Task::image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
|
||||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {
|
|
||||||
std::string ret_icon_name = "unknown";
|
|
||||||
if (app_info) {
|
|
||||||
std::string icon_name =
|
|
||||||
get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
|
|
||||||
if (!icon_name.empty()) {
|
|
||||||
ret_icon_name = icon_name;
|
|
||||||
} else {
|
|
||||||
if (app_info->get_icon()) {
|
|
||||||
ret_icon_name = app_info->get_icon()->to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
|
||||||
auto scaled_icon_size = size * image.get_scale_factor();
|
|
||||||
|
|
||||||
try {
|
|
||||||
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
|
||||||
spdlog::debug("{} Loaded icon '{}'", repr(), ret_icon_name);
|
|
||||||
} catch (...) {
|
|
||||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
|
|
||||||
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
|
|
||||||
spdlog::debug("{} Loaded icon from file '{}'", repr(), ret_icon_name);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
|
|
||||||
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
|
||||||
spdlog::debug("{} Loaded icon from resource", repr());
|
|
||||||
} catch (...) {
|
|
||||||
pixbuf = {};
|
|
||||||
spdlog::debug("{} Unable to load icon.", repr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pixbuf) {
|
|
||||||
if (pixbuf->get_width() != scaled_icon_size) {
|
|
||||||
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
|
||||||
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
|
||||||
}
|
|
||||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),
|
|
||||||
image.get_window());
|
|
||||||
image.set(surface);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Task class implementation */
|
/* Task class implementation */
|
||||||
uint32_t Task::global_id = 0;
|
uint32_t Task::global_id = 0;
|
||||||
|
|
||||||
@ -299,16 +111,11 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
|||||||
with_name_ = true;
|
with_name_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto icon_pos = format.find("{icon}");
|
auto parts = split(format, "{icon}", 1);
|
||||||
if (icon_pos == 0) {
|
format_before_ = parts[0];
|
||||||
|
if (parts.size() > 1) {
|
||||||
with_icon_ = true;
|
with_icon_ = true;
|
||||||
format_after_ = format.substr(6);
|
format_after_ = parts[1];
|
||||||
} else if (icon_pos == std::string::npos) {
|
|
||||||
format_before_ = format;
|
|
||||||
} else {
|
|
||||||
with_icon_ = true;
|
|
||||||
format_before_ = format.substr(0, icon_pos);
|
|
||||||
format_after_ = format.substr(icon_pos + 6);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* The default is to only show the icon */
|
/* The default is to only show the icon */
|
||||||
@ -395,7 +202,7 @@ void Task::handle_title(const char *title) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_app_info_from_app_id_list(title_);
|
app_info_ = IconLoader::get_app_info_from_app_id_list(title_);
|
||||||
name_ = app_info_ ? app_info_->get_display_name() : title;
|
name_ = app_info_ ? app_info_->get_display_name() : title;
|
||||||
|
|
||||||
if (!with_icon_) {
|
if (!with_icon_) {
|
||||||
@ -403,15 +210,7 @@ void Task::handle_title(const char *title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||||
bool found = false;
|
if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))
|
||||||
for (auto &icon_theme : tbar_->icon_themes()) {
|
|
||||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found)
|
|
||||||
icon_.show();
|
icon_.show();
|
||||||
else
|
else
|
||||||
spdlog::debug("Couldn't find icon for {}", title_);
|
spdlog::debug("Couldn't find icon for {}", title_);
|
||||||
@ -460,7 +259,7 @@ void Task::handle_app_id(const char *app_id) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_app_info_from_app_id_list(app_id_);
|
app_info_ = IconLoader::get_app_info_from_app_id_list(app_id_);
|
||||||
name_ = app_info_ ? app_info_->get_display_name() : app_id;
|
name_ = app_info_ ? app_info_->get_display_name() : app_id;
|
||||||
|
|
||||||
if (!with_icon_) {
|
if (!with_icon_) {
|
||||||
@ -468,15 +267,7 @@ void Task::handle_app_id(const char *app_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||||
bool found = false;
|
if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))
|
||||||
for (auto &icon_theme : tbar_->icon_themes()) {
|
|
||||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found)
|
|
||||||
icon_.show();
|
icon_.show();
|
||||||
else
|
else
|
||||||
spdlog::debug("Couldn't find icon for {}", app_id_);
|
spdlog::debug("Couldn't find icon for {}", app_id_);
|
||||||
@ -802,22 +593,10 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
|||||||
/* Get the configured icon theme if specified */
|
/* Get the configured icon theme if specified */
|
||||||
if (config_["icon-theme"].isArray()) {
|
if (config_["icon-theme"].isArray()) {
|
||||||
for (auto &c : config_["icon-theme"]) {
|
for (auto &c : config_["icon-theme"]) {
|
||||||
auto it_name = c.asString();
|
icon_loader_.add_custom_icon_theme(c.asString());
|
||||||
|
|
||||||
auto it = Gtk::IconTheme::create();
|
|
||||||
it->set_custom_theme(it_name);
|
|
||||||
spdlog::debug("Use custom icon theme: {}", it_name);
|
|
||||||
|
|
||||||
icon_themes_.push_back(it);
|
|
||||||
}
|
}
|
||||||
} else if (config_["icon-theme"].isString()) {
|
} else if (config_["icon-theme"].isString()) {
|
||||||
auto it_name = config_["icon-theme"].asString();
|
icon_loader_.add_custom_icon_theme(config_["icon-theme"].asString());
|
||||||
|
|
||||||
auto it = Gtk::IconTheme::create();
|
|
||||||
it->set_custom_theme(it_name);
|
|
||||||
spdlog::debug("Use custom icon theme: {}", it_name);
|
|
||||||
|
|
||||||
icon_themes_.push_back(it);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load ignore-list
|
// Load ignore-list
|
||||||
@ -836,8 +615,6 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
icon_themes_.push_back(Gtk::IconTheme::get_default());
|
|
||||||
|
|
||||||
for (auto &t : tasks_) {
|
for (auto &t : tasks_) {
|
||||||
t->handle_app_id(t->app_id().c_str());
|
t->handle_app_id(t->app_id().c_str());
|
||||||
}
|
}
|
||||||
@ -972,9 +749,7 @@ bool Taskbar::all_outputs() const {
|
|||||||
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>> &Taskbar::icon_themes() const {
|
const IconLoader &Taskbar::icon_loader() const { return icon_loader_; }
|
||||||
return icon_themes_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
|
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
|
||||||
|
|
||||||
|
207
src/util/icon_loader.cpp
Normal file
207
src/util/icon_loader.cpp
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
#include "util/icon_loader.hpp"
|
||||||
|
|
||||||
|
#include "util/string.hpp"
|
||||||
|
|
||||||
|
std::vector<std::string> IconLoader::search_prefix() {
|
||||||
|
std::vector<std::string> prefixes = {""};
|
||||||
|
|
||||||
|
std::string home_dir = std::getenv("HOME");
|
||||||
|
prefixes.push_back(home_dir + "/.local/share/");
|
||||||
|
|
||||||
|
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
|
||||||
|
if (!xdg_data_dirs) {
|
||||||
|
prefixes.emplace_back("/usr/share/");
|
||||||
|
prefixes.emplace_back("/usr/local/share/");
|
||||||
|
} else {
|
||||||
|
std::string xdg_data_dirs_str(xdg_data_dirs);
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
end = xdg_data_dirs_str.find(':', start);
|
||||||
|
auto p = xdg_data_dirs_str.substr(start, end - start);
|
||||||
|
prefixes.push_back(trim(p) + "/");
|
||||||
|
|
||||||
|
start = end == std::string::npos ? end : end + 1;
|
||||||
|
} while (end != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
|
||||||
|
|
||||||
|
return prefixes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_by_name(const std::string &app_id) {
|
||||||
|
static std::vector<std::string> prefixes = search_prefix();
|
||||||
|
|
||||||
|
std::vector<std::string> app_folders = {"", "applications/", "applications/kde/",
|
||||||
|
"applications/org.kde."};
|
||||||
|
|
||||||
|
std::vector<std::string> suffixes = {"", ".desktop"};
|
||||||
|
|
||||||
|
for (auto const &prefix : prefixes) {
|
||||||
|
for (auto const &folder : app_folders) {
|
||||||
|
for (auto const &suffix : suffixes) {
|
||||||
|
auto app_info_ =
|
||||||
|
Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||||
|
if (!app_info_) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app_info_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_desktop_app_info(const std::string &app_id) {
|
||||||
|
auto app_info = get_app_info_by_name(app_id);
|
||||||
|
if (app_info) {
|
||||||
|
return app_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string desktop_file = "";
|
||||||
|
|
||||||
|
gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str());
|
||||||
|
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
|
||||||
|
for (size_t i = 0; desktop_list[0][i]; i++) {
|
||||||
|
if (desktop_file == "") {
|
||||||
|
desktop_file = desktop_list[0][i];
|
||||||
|
} else {
|
||||||
|
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
|
||||||
|
if (!tmp_info)
|
||||||
|
// see https://github.com/Alexays/Waybar/issues/1446
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto startup_class = tmp_info->get_startup_wm_class();
|
||||||
|
if (startup_class == app_id) {
|
||||||
|
desktop_file = desktop_list[0][i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_strfreev(desktop_list[0]);
|
||||||
|
}
|
||||||
|
g_free(desktop_list);
|
||||||
|
|
||||||
|
return get_app_info_by_name(desktop_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gdk::Pixbuf> IconLoader::load_icon_from_file(std::string const &icon_path, int size) {
|
||||||
|
try {
|
||||||
|
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
|
||||||
|
return pb;
|
||||||
|
} catch (...) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IconLoader::get_icon_name_from_icon_theme(
|
||||||
|
const Glib::RefPtr<Gtk::IconTheme> &icon_theme, const std::string &app_id) {
|
||||||
|
if (icon_theme->lookup_icon(app_id, 24)) return app_id;
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IconLoader::image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {
|
||||||
|
std::string ret_icon_name = "unknown";
|
||||||
|
if (app_info) {
|
||||||
|
std::string icon_name =
|
||||||
|
get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
|
||||||
|
if (!icon_name.empty()) {
|
||||||
|
ret_icon_name = icon_name;
|
||||||
|
} else {
|
||||||
|
if (app_info->get_icon()) {
|
||||||
|
ret_icon_name = app_info->get_icon()->to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||||
|
auto scaled_icon_size = size * image.get_scale_factor();
|
||||||
|
|
||||||
|
try {
|
||||||
|
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||||
|
} catch (...) {
|
||||||
|
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
|
||||||
|
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
|
||||||
|
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||||
|
} catch (...) {
|
||||||
|
pixbuf = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pixbuf) {
|
||||||
|
if (pixbuf->get_width() != scaled_icon_size) {
|
||||||
|
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||||
|
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||||
|
}
|
||||||
|
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),
|
||||||
|
image.get_window());
|
||||||
|
image.set(surface);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IconLoader::add_custom_icon_theme(const std::string &theme_name) {
|
||||||
|
auto icon_theme = Gtk::IconTheme::create();
|
||||||
|
icon_theme->set_custom_theme(theme_name);
|
||||||
|
custom_icon_themes_.push_back(icon_theme);
|
||||||
|
spdlog::debug("Use custom icon theme: {}", theme_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IconLoader::image_load_icon(Gtk::Image &image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,
|
||||||
|
int size) const {
|
||||||
|
for (auto &icon_theme : custom_icon_themes_) {
|
||||||
|
if (image_load_icon(image, icon_theme, app_info, size)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return image_load_icon(image, default_icon_theme_, app_info, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_from_app_id_list(
|
||||||
|
const std::string &app_id_list) {
|
||||||
|
std::string app_id;
|
||||||
|
std::istringstream stream(app_id_list);
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
|
||||||
|
|
||||||
|
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
||||||
|
* send a single app-id, but in any case this works fine */
|
||||||
|
while (stream >> app_id) {
|
||||||
|
app_info_ = get_desktop_app_info(app_id);
|
||||||
|
if (app_info_) {
|
||||||
|
return app_info_;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lower_app_id = app_id;
|
||||||
|
std::ranges::transform(lower_app_id, lower_app_id.begin(),
|
||||||
|
[](char c) { return std::tolower(c); });
|
||||||
|
app_info_ = get_desktop_app_info(lower_app_id);
|
||||||
|
if (app_info_) {
|
||||||
|
return app_info_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t start = 0, end = app_id.size();
|
||||||
|
start = app_id.rfind(".", end);
|
||||||
|
std::string app_name = app_id.substr(start + 1, app_id.size());
|
||||||
|
app_info_ = get_desktop_app_info(app_name);
|
||||||
|
if (app_info_) {
|
||||||
|
return app_info_;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = app_id.find("-");
|
||||||
|
app_name = app_id.substr(0, start);
|
||||||
|
app_info_ = get_desktop_app_info(app_name);
|
||||||
|
}
|
||||||
|
return app_info_;
|
||||||
|
}
|
Reference in New Issue
Block a user