Merge pull request #3863 from yamader/wayfire
add module wayfire/window, wayfire/workspaces
This commit is contained in:
122
include/modules/wayfire/backend.hpp
Normal file
122
include/modules/wayfire/backend.hpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace waybar::modules::wayfire {
|
||||||
|
|
||||||
|
using EventHandler = std::function<void(const std::string& event)>;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
/*
|
||||||
|
┌───────────┐ ┌───────────┐
|
||||||
|
│ output #1 │ │ output #2 │
|
||||||
|
└─────┬─────┘ └─────┬─────┘
|
||||||
|
└─┐ └─────┐─ ─ ─ ─ ─ ─ ─ ─ ┐
|
||||||
|
┌───────┴───────┐ ┌───────┴──────┐ ┌───────┴───────┐
|
||||||
|
│ wset #1 │ │ wset #2 │ │ wset #3 │
|
||||||
|
│┌────────────┐ │ │┌────────────┐│ │┌────────────┐ │
|
||||||
|
││ workspaces │ │ ││ workspaces ││ ││ workspaces │ │
|
||||||
|
│└─┬──────────┘ │ │└────────────┘│ │└─┬──────────┘ │
|
||||||
|
│ │ ┌─────────┐│ └──────────────┘ │ │ ┌─────────┐│
|
||||||
|
│ ├─┤ view #1 ││ │ └─┤ view #3 ││
|
||||||
|
│ │ └─────────┘│ │ └─────────┘│
|
||||||
|
│ │ ┌─────────┐│ └───────────────┘
|
||||||
|
│ └─┤ view #2 ││
|
||||||
|
│ └─────────┘│
|
||||||
|
└───────────────┘
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
size_t id;
|
||||||
|
size_t w, h;
|
||||||
|
size_t wset_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Workspace {
|
||||||
|
size_t num_views;
|
||||||
|
size_t num_sticky_views;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Wset {
|
||||||
|
std::optional<std::reference_wrapper<Output>> output;
|
||||||
|
std::vector<Workspace> wss;
|
||||||
|
size_t ws_w, ws_h, ws_x, ws_y;
|
||||||
|
size_t focused_view_id;
|
||||||
|
|
||||||
|
auto ws_idx() const { return ws_w * ws_y + ws_x; }
|
||||||
|
auto count_ws(const Json::Value& pos) -> Workspace&;
|
||||||
|
auto locate_ws(const Json::Value& geo) -> Workspace&;
|
||||||
|
auto locate_ws(const Json::Value& geo) const -> const Workspace&;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Output> outputs;
|
||||||
|
std::unordered_map<size_t, Wset> wsets;
|
||||||
|
std::unordered_map<size_t, Json::Value> views;
|
||||||
|
std::string focused_output_name;
|
||||||
|
size_t maybe_empty_focus_wset_idx = {};
|
||||||
|
size_t vswitch_sticky_view_id = {};
|
||||||
|
bool new_output_detected = {};
|
||||||
|
bool vswitching = {};
|
||||||
|
|
||||||
|
auto update_view(const Json::Value& view) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sock {
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
Sock(int fd) : fd{fd} {}
|
||||||
|
~Sock() { close(fd); }
|
||||||
|
Sock(const Sock&) = delete;
|
||||||
|
auto operator=(const Sock&) = delete;
|
||||||
|
Sock(Sock&& rhs) noexcept {
|
||||||
|
fd = rhs.fd;
|
||||||
|
rhs.fd = -1;
|
||||||
|
}
|
||||||
|
auto& operator=(Sock&& rhs) noexcept {
|
||||||
|
fd = rhs.fd;
|
||||||
|
rhs.fd = -1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class IPC {
|
||||||
|
static std::weak_ptr<IPC> instance;
|
||||||
|
Json::CharReaderBuilder reader_builder;
|
||||||
|
Json::StreamWriterBuilder writer_builder;
|
||||||
|
std::list<std::pair<std::string, std::reference_wrapper<const EventHandler>>> handlers;
|
||||||
|
std::mutex handlers_mutex;
|
||||||
|
State state;
|
||||||
|
std::mutex state_mutex;
|
||||||
|
|
||||||
|
IPC() { start(); }
|
||||||
|
|
||||||
|
static auto connect() -> Sock;
|
||||||
|
auto receive(Sock& sock) -> Json::Value;
|
||||||
|
auto start() -> void;
|
||||||
|
auto root_event_handler(const std::string& event, const Json::Value& data) -> void;
|
||||||
|
auto update_state_handler(const std::string& event, const Json::Value& data) -> void;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static auto get_instance() -> std::shared_ptr<IPC>;
|
||||||
|
auto send(const std::string& method, Json::Value&& data) -> Json::Value;
|
||||||
|
auto register_handler(const std::string& event, const EventHandler& handler) -> void;
|
||||||
|
auto unregister_handler(EventHandler& handler) -> void;
|
||||||
|
|
||||||
|
auto lock_state() -> std::lock_guard<std::mutex> { return std::lock_guard{state_mutex}; }
|
||||||
|
auto& get_outputs() const { return state.outputs; }
|
||||||
|
auto& get_wsets() const { return state.wsets; }
|
||||||
|
auto& get_views() const { return state.views; }
|
||||||
|
auto& get_focused_output_name() const { return state.focused_output_name; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::wayfire
|
24
include/modules/wayfire/window.hpp
Normal file
24
include/modules/wayfire/window.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AAppIconLabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/wayfire/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::wayfire {
|
||||||
|
|
||||||
|
class Window : public AAppIconLabel {
|
||||||
|
std::shared_ptr<IPC> ipc;
|
||||||
|
EventHandler handler;
|
||||||
|
|
||||||
|
const Bar& bar_;
|
||||||
|
std::string old_app_id_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Window(const std::string& id, const Bar& bar, const Json::Value& config);
|
||||||
|
~Window() override;
|
||||||
|
|
||||||
|
auto update() -> void override;
|
||||||
|
auto update_icon_label() -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::wayfire
|
32
include/modules/wayfire/workspaces.hpp
Normal file
32
include/modules/wayfire/workspaces.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/wayfire/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::wayfire {
|
||||||
|
|
||||||
|
class Workspaces : public AModule {
|
||||||
|
std::shared_ptr<IPC> ipc;
|
||||||
|
EventHandler handler;
|
||||||
|
|
||||||
|
const Bar& bar_;
|
||||||
|
Gtk::Box box_;
|
||||||
|
std::vector<Gtk::Button> buttons_;
|
||||||
|
|
||||||
|
auto handleScroll(GdkEventScroll* e) -> bool override;
|
||||||
|
auto update() -> void override;
|
||||||
|
auto update_box() -> void;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Workspaces(const std::string& id, const Bar& bar, const Json::Value& config);
|
||||||
|
~Workspaces() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::wayfire
|
82
man/waybar-wayfire-window.5.scd
Normal file
82
man/waybar-wayfire-window.5.scd
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
waybar-wayfire-window(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - wayfire window module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *window* module displays the title of the currently focused window in wayfire.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *wayfire/window*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {title} ++
|
||||||
|
The format, how information should be displayed. On {} the current window title is displayed.
|
||||||
|
|
||||||
|
*rewrite*: ++
|
||||||
|
typeof: object ++
|
||||||
|
Rules to rewrite window title. See *rewrite rules*.
|
||||||
|
|
||||||
|
*icon*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Option to hide the application icon.
|
||||||
|
|
||||||
|
*icon-size*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
default: 24 ++
|
||||||
|
Option to change the size of the application icon.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
See the output of "wayfire msg windows" for examples
|
||||||
|
|
||||||
|
*{title}*: The current title of the focused window.
|
||||||
|
|
||||||
|
*{app_id}*: The current app ID of the focused window.
|
||||||
|
|
||||||
|
# REWRITE RULES
|
||||||
|
|
||||||
|
*rewrite* is an object where keys are regular expressions and values are
|
||||||
|
rewrite rules if the expression matches. Rules may contain references to
|
||||||
|
captures of the expression.
|
||||||
|
|
||||||
|
Regular expression and replacement follow ECMA-script rules.
|
||||||
|
|
||||||
|
If no expression matches, the title is left unchanged.
|
||||||
|
|
||||||
|
Invalid expressions (e.g., mismatched parentheses) are skipped.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"wayfire/window": {
|
||||||
|
"format": "{}",
|
||||||
|
"rewrite": {
|
||||||
|
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||||
|
"(.*) - zsh": "> [$1]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#window*
|
||||||
|
- *window#waybar.empty #window* When no windows are on the workspace
|
||||||
|
|
||||||
|
The following classes are applied to the entire Waybar rather than just the
|
||||||
|
window widget:
|
||||||
|
|
||||||
|
- *window#waybar.empty* When no windows are in the workspace
|
||||||
|
- *window#waybar.solo* When only one window is on the workspace
|
||||||
|
- *window#waybar.<app-id>* Where *app-id* is the app ID of the only window on
|
||||||
|
the workspace
|
86
man/waybar-wayfire-workspaces.5.scd
Normal file
86
man/waybar-wayfire-workspaces.5.scd
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
waybar-wayfire-workspaces(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - wayfire workspaces module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *workspaces* module displays the currently used workspaces in wayfire.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *wayfire/workspaces*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {value} ++
|
||||||
|
The format, how information should be displayed.
|
||||||
|
|
||||||
|
*format-icons*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Based on the workspace name, index and state, the corresponding icon gets selected. See *icons*.
|
||||||
|
|
||||||
|
*disable-click*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
|
||||||
|
|
||||||
|
*disable-markup*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, button label will escape pango markup.
|
||||||
|
|
||||||
|
*current-only*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, only the active or focused workspace will be shown.
|
||||||
|
|
||||||
|
*on-update*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{icon}*: Icon, as defined in *format-icons*.
|
||||||
|
|
||||||
|
*{index}*: Index of the workspace on its output.
|
||||||
|
|
||||||
|
*{output}*: Output where the workspace is located.
|
||||||
|
|
||||||
|
# ICONS
|
||||||
|
|
||||||
|
Additional to workspace name matching, the following *format-icons* can be set.
|
||||||
|
|
||||||
|
- *default*: Will be shown, when no string matches are found.
|
||||||
|
- *focused*: Will be shown, when workspace is focused.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"wayfire/workspaces": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"1": "",
|
||||||
|
"2": "",
|
||||||
|
"3": "",
|
||||||
|
"4": "",
|
||||||
|
"5": "",
|
||||||
|
"focused": "",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Style
|
||||||
|
|
||||||
|
- *#workspaces button*
|
||||||
|
- *#workspaces button.focused*: The single focused workspace.
|
||||||
|
- *#workspaces button.empty*: The workspace is empty.
|
||||||
|
- *#workspaces button.current_output*: The workspace is from the same output as
|
||||||
|
the bar that it is displayed on.
|
@ -333,6 +333,15 @@ if get_option('niri')
|
|||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if true
|
||||||
|
add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')
|
||||||
|
src_files += files(
|
||||||
|
'src/modules/wayfire/backend.cpp',
|
||||||
|
'src/modules/wayfire/window.cpp',
|
||||||
|
'src/modules/wayfire/workspaces.cpp',
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
if get_option('login-proxy')
|
if get_option('login-proxy')
|
||||||
add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')
|
add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')
|
||||||
endif
|
endif
|
||||||
|
@ -41,6 +41,10 @@
|
|||||||
#include "modules/niri/window.hpp"
|
#include "modules/niri/window.hpp"
|
||||||
#include "modules/niri/workspaces.hpp"
|
#include "modules/niri/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_WAYFIRE
|
||||||
|
#include "modules/wayfire/window.hpp"
|
||||||
|
#include "modules/wayfire/workspaces.hpp"
|
||||||
|
#endif
|
||||||
#if defined(__FreeBSD__) || defined(__linux__)
|
#if defined(__FreeBSD__) || defined(__linux__)
|
||||||
#include "modules/battery.hpp"
|
#include "modules/battery.hpp"
|
||||||
#endif
|
#endif
|
||||||
@ -221,6 +225,14 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
|||||||
if (ref == "niri/workspaces") {
|
if (ref == "niri/workspaces") {
|
||||||
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
|
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_WAYFIRE
|
||||||
|
if (ref == "wayfire/window") {
|
||||||
|
return new waybar::modules::wayfire::Window(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
if (ref == "wayfire/workspaces") {
|
||||||
|
return new waybar::modules::wayfire::Workspaces(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ref == "idle_inhibitor") {
|
if (ref == "idle_inhibitor") {
|
||||||
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
||||||
|
445
src/modules/wayfire/backend.cpp
Normal file
445
src/modules/wayfire/backend.cpp
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
#include "modules/wayfire/backend.hpp"
|
||||||
|
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <exception>
|
||||||
|
#include <ranges>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace waybar::modules::wayfire {
|
||||||
|
|
||||||
|
std::weak_ptr<IPC> IPC::instance;
|
||||||
|
|
||||||
|
// C++23: std::byteswap
|
||||||
|
inline auto byteswap(uint32_t x) -> uint32_t {
|
||||||
|
return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 |
|
||||||
|
(x & 0x000000ff) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pack_and_write(Sock& sock, std::string&& buf) -> void {
|
||||||
|
uint32_t len = buf.size();
|
||||||
|
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
|
||||||
|
(void)write(sock.fd, &len, 4);
|
||||||
|
(void)write(sock.fd, buf.data(), buf.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto read_exact(Sock& sock, size_t n) -> std::string {
|
||||||
|
auto buf = std::string(n, 0);
|
||||||
|
for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438
|
||||||
|
inline auto is_mapped_toplevel_view(const Json::Value& view) -> bool {
|
||||||
|
return view["mapped"].asBool() && view["role"] != "desktop-environment" &&
|
||||||
|
view["pid"].asInt() != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto State::Wset::count_ws(const Json::Value& pos) -> Workspace& {
|
||||||
|
auto x = pos["x"].asInt();
|
||||||
|
auto y = pos["y"].asInt();
|
||||||
|
return wss.at(ws_w * y + x);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
|
||||||
|
return const_cast<Workspace&>(std::as_const(*this).locate_ws(geo));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
|
||||||
|
const auto& out = output.value().get();
|
||||||
|
auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
|
||||||
|
auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
|
||||||
|
auto x = std::max(0, (int)ws_x + qx - int{rx < 0});
|
||||||
|
auto y = std::max(0, (int)ws_y + qy - int{ry < 0});
|
||||||
|
return wss.at(ws_w * y + x);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto State::update_view(const Json::Value& view) -> void {
|
||||||
|
auto id = view["id"].asUInt();
|
||||||
|
|
||||||
|
// erase old view information
|
||||||
|
if (views.contains(id)) {
|
||||||
|
auto& old_view = views.at(id);
|
||||||
|
auto& ws = wsets.at(old_view["wset-index"].asUInt()).locate_ws(old_view["geometry"]);
|
||||||
|
ws.num_views--;
|
||||||
|
if (old_view["sticky"].asBool()) ws.num_sticky_views--;
|
||||||
|
views.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert or assign new view information
|
||||||
|
if (is_mapped_toplevel_view(view)) {
|
||||||
|
try {
|
||||||
|
// view["wset-index"] could be messed up
|
||||||
|
auto& ws = wsets.at(view["wset-index"].asUInt()).locate_ws(view["geometry"]);
|
||||||
|
ws.num_views++;
|
||||||
|
if (view["sticky"].asBool()) ws.num_sticky_views++;
|
||||||
|
views.emplace(id, view);
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::get_instance() -> std::shared_ptr<IPC> {
|
||||||
|
auto p = instance.lock();
|
||||||
|
if (!p) instance = p = std::shared_ptr<IPC>(new IPC);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::connect() -> Sock {
|
||||||
|
auto* path = std::getenv("WAYFIRE_SOCKET");
|
||||||
|
if (path == nullptr) {
|
||||||
|
throw std::runtime_error{"Wayfire IPC: ipc not available"};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (sock == -1) {
|
||||||
|
throw std::runtime_error{"Wayfire IPC: socket() failed"};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto addr = sockaddr_un{.sun_family = AF_UNIX};
|
||||||
|
std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||||
|
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||||
|
|
||||||
|
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
close(sock);
|
||||||
|
throw std::runtime_error{"Wayfire IPC: connect() failed"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {sock};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::receive(Sock& sock) -> Json::Value {
|
||||||
|
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data());
|
||||||
|
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
|
||||||
|
auto buf = read_exact(sock, len);
|
||||||
|
|
||||||
|
Json::Value json;
|
||||||
|
std::string err;
|
||||||
|
auto* reader = reader_builder.newCharReader();
|
||||||
|
if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) {
|
||||||
|
throw std::runtime_error{"Wayfire IPC: parse json failed: " + err};
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value {
|
||||||
|
spdlog::debug("Wayfire IPC: send method \"{}\"", method);
|
||||||
|
auto sock = connect();
|
||||||
|
|
||||||
|
Json::Value json;
|
||||||
|
json["method"] = method;
|
||||||
|
json["data"] = std::move(data);
|
||||||
|
|
||||||
|
pack_and_write(sock, Json::writeString(writer_builder, json));
|
||||||
|
auto res = receive(sock);
|
||||||
|
root_event_handler(method, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::start() -> void {
|
||||||
|
spdlog::info("Wayfire IPC: starting");
|
||||||
|
|
||||||
|
// init state
|
||||||
|
send("window-rules/list-outputs", {});
|
||||||
|
send("window-rules/list-wsets", {});
|
||||||
|
send("window-rules/list-views", {});
|
||||||
|
send("window-rules/get-focused-view", {});
|
||||||
|
send("window-rules/get-focused-output", {});
|
||||||
|
|
||||||
|
std::thread([&] {
|
||||||
|
auto sock = connect();
|
||||||
|
|
||||||
|
{
|
||||||
|
Json::Value json;
|
||||||
|
json["method"] = "window-rules/events/watch";
|
||||||
|
|
||||||
|
pack_and_write(sock, Json::writeString(writer_builder, json));
|
||||||
|
if (receive(sock)["result"] != "ok") {
|
||||||
|
spdlog::error(
|
||||||
|
"Wayfire IPC: method \"window-rules/events/watch\""
|
||||||
|
" have failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (auto json = receive(sock)) {
|
||||||
|
auto ev = json["event"].asString();
|
||||||
|
spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
|
||||||
|
root_event_handler(ev, json);
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void {
|
||||||
|
auto _ = std::lock_guard{handlers_mutex};
|
||||||
|
handlers.emplace_back(event, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::unregister_handler(EventHandler& handler) -> void {
|
||||||
|
auto _ = std::lock_guard{handlers_mutex};
|
||||||
|
handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void {
|
||||||
|
bool new_output_detected;
|
||||||
|
{
|
||||||
|
auto _ = lock_state();
|
||||||
|
update_state_handler(event, data);
|
||||||
|
new_output_detected = state.new_output_detected;
|
||||||
|
state.new_output_detected = false;
|
||||||
|
}
|
||||||
|
if (new_output_detected) {
|
||||||
|
send("window-rules/list-outputs", {});
|
||||||
|
send("window-rules/list-wsets", {});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto _ = std::lock_guard{handlers_mutex};
|
||||||
|
for (const auto& [_event, handler] : handlers)
|
||||||
|
if (_event == event) handler(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void {
|
||||||
|
// IPC events
|
||||||
|
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125
|
||||||
|
/*
|
||||||
|
[x] view-mapped
|
||||||
|
[x] view-unmapped
|
||||||
|
[-] view-set-output // for detect new output
|
||||||
|
[ ] view-geometry-changed // -> view-workspace-changed
|
||||||
|
[x] view-wset-changed
|
||||||
|
[x] view-focused
|
||||||
|
[x] view-title-changed
|
||||||
|
[x] view-app-id-changed
|
||||||
|
[x] plugin-activation-state-changed
|
||||||
|
[x] output-gain-focus
|
||||||
|
|
||||||
|
[ ] view-tiled
|
||||||
|
[ ] view-minimized
|
||||||
|
[ ] view-fullscreened
|
||||||
|
[x] view-sticky
|
||||||
|
[x] view-workspace-changed
|
||||||
|
[x] output-wset-changed
|
||||||
|
[x] wset-workspace-changed
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (event == "view-mapped") {
|
||||||
|
// data: { event, view }
|
||||||
|
state.update_view(data["view"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "view-unmapped") {
|
||||||
|
// data: { event, view }
|
||||||
|
try {
|
||||||
|
// data["view"]["wset-index"] could be messed up
|
||||||
|
state.update_view(data["view"]);
|
||||||
|
state.maybe_empty_focus_wset_idx = data["view"]["wset-index"].asUInt();
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "view-set-output") {
|
||||||
|
// data: { event, output?, view }
|
||||||
|
// new output event
|
||||||
|
if (!state.outputs.contains(data["view"]["output-name"].asString())) {
|
||||||
|
state.new_output_detected = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "view-wset-changed") {
|
||||||
|
// data: { event, old-wset: wset, new-wset: wset, view }
|
||||||
|
state.maybe_empty_focus_wset_idx = data["old-wset"]["index"].asUInt();
|
||||||
|
state.update_view(data["view"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "view-focused") {
|
||||||
|
// data: { event, view? }
|
||||||
|
if (const auto& view = data["view"]) {
|
||||||
|
try {
|
||||||
|
// view["wset-index"] could be messed up
|
||||||
|
auto& wset = state.wsets.at(view["wset-index"].asUInt());
|
||||||
|
wset.focused_view_id = view["id"].asUInt();
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// focused to null
|
||||||
|
if (state.wsets.contains(state.maybe_empty_focus_wset_idx))
|
||||||
|
state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "view-title-changed" || event == "view-app-id-changed" || event == "view-sticky") {
|
||||||
|
// data: { event, view }
|
||||||
|
state.update_view(data["view"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "plugin-activation-state-changed") {
|
||||||
|
// data: { event, plugin: name, state: bool, output: id, output-data: output }
|
||||||
|
auto plugin = data["plugin"].asString();
|
||||||
|
auto plugin_state = data["state"].asBool();
|
||||||
|
|
||||||
|
if (plugin == "vswitch") {
|
||||||
|
state.vswitching = plugin_state;
|
||||||
|
if (plugin_state) {
|
||||||
|
state.maybe_empty_focus_wset_idx = data["output-data"]["wset-index"].asUInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "output-gain-focus") {
|
||||||
|
// data: { event, output }
|
||||||
|
state.focused_output_name = data["output"]["name"].asString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "view-workspace-changed") {
|
||||||
|
// data: { event, from: point, to: point, view }
|
||||||
|
if (state.vswitching) {
|
||||||
|
if (state.vswitch_sticky_view_id == 0) {
|
||||||
|
auto& wset = state.wsets.at(data["view"]["wset-index"].asUInt());
|
||||||
|
auto& old_ws = wset.locate_ws(state.views.at(data["view"]["id"].asUInt())["geometry"]);
|
||||||
|
auto& new_ws = wset.count_ws(data["to"]);
|
||||||
|
old_ws.num_views--;
|
||||||
|
new_ws.num_views++;
|
||||||
|
if (data["view"]["sticky"].asBool()) {
|
||||||
|
old_ws.num_sticky_views--;
|
||||||
|
new_ws.num_sticky_views++;
|
||||||
|
}
|
||||||
|
state.update_view(data["view"]);
|
||||||
|
state.vswitch_sticky_view_id = data["view"]["id"].asUInt();
|
||||||
|
} else {
|
||||||
|
state.vswitch_sticky_view_id = {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.update_view(data["view"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "output-wset-changed") {
|
||||||
|
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
|
||||||
|
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
||||||
|
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
||||||
|
state.wsets.at(wset_idx).output = output;
|
||||||
|
output.wset_idx = wset_idx;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "wset-workspace-changed") {
|
||||||
|
// data: { event, previous-workspace: point, new-workspace: point,
|
||||||
|
// output: id, wset: wset.name, output-data: output, wset-data: wset }
|
||||||
|
auto wset_idx = data["wset-data"]["index"].asUInt();
|
||||||
|
auto& wset = state.wsets.at(wset_idx);
|
||||||
|
wset.ws_x = data["new-workspace"]["x"].asUInt();
|
||||||
|
wset.ws_y = data["new-workspace"]["y"].asUInt();
|
||||||
|
|
||||||
|
// correct existing views geometry
|
||||||
|
auto& out = wset.output.value().get();
|
||||||
|
auto dx = (int)out.w * ((int)wset.ws_x - data["previous-workspace"]["x"].asInt());
|
||||||
|
auto dy = (int)out.h * ((int)wset.ws_y - data["previous-workspace"]["y"].asInt());
|
||||||
|
for (auto& [_, view] : state.views) {
|
||||||
|
if (view["wset-index"].asUInt() == wset_idx &&
|
||||||
|
view["id"].asUInt() != state.vswitch_sticky_view_id) {
|
||||||
|
view["geometry"]["x"] = view["geometry"]["x"].asInt() - dx;
|
||||||
|
view["geometry"]["y"] = view["geometry"]["y"].asInt() - dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC responses
|
||||||
|
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37
|
||||||
|
|
||||||
|
if (event == "window-rules/list-views") {
|
||||||
|
// data: [ view ]
|
||||||
|
state.views.clear();
|
||||||
|
for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{});
|
||||||
|
for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) {
|
||||||
|
state.update_view(view);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "window-rules/list-outputs") {
|
||||||
|
// data: [ output ]
|
||||||
|
state.outputs.clear();
|
||||||
|
for (const auto& output_data : data) {
|
||||||
|
state.outputs.emplace(output_data["name"].asString(),
|
||||||
|
State::Output{
|
||||||
|
.id = output_data["id"].asUInt(),
|
||||||
|
.w = output_data["geometry"]["width"].asUInt(),
|
||||||
|
.h = output_data["geometry"]["height"].asUInt(),
|
||||||
|
.wset_idx = output_data["wset-index"].asUInt(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "window-rules/list-wsets") {
|
||||||
|
// data: [ wset ]
|
||||||
|
std::unordered_map<size_t, State::Wset> wsets;
|
||||||
|
for (const auto& wset_data : data) {
|
||||||
|
auto wset_idx = wset_data["index"].asUInt();
|
||||||
|
|
||||||
|
auto output_name = wset_data["output-name"].asString();
|
||||||
|
auto output = state.outputs.contains(output_name)
|
||||||
|
? std::optional{std::ref(state.outputs.at(output_name))}
|
||||||
|
: std::nullopt;
|
||||||
|
|
||||||
|
const auto& ws_data = wset_data["workspace"];
|
||||||
|
auto ws_w = ws_data["grid_width"].asUInt();
|
||||||
|
auto ws_h = ws_data["grid_height"].asUInt();
|
||||||
|
|
||||||
|
wsets.emplace(wset_idx, State::Wset{
|
||||||
|
.output = output,
|
||||||
|
.wss = std::vector<State::Workspace>(ws_w * ws_h),
|
||||||
|
.ws_w = ws_w,
|
||||||
|
.ws_h = ws_h,
|
||||||
|
.ws_x = ws_data["x"].asUInt(),
|
||||||
|
.ws_y = ws_data["y"].asUInt(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (state.wsets.contains(wset_idx)) {
|
||||||
|
auto& old_wset = state.wsets.at(wset_idx);
|
||||||
|
auto& new_wset = wsets.at(wset_idx);
|
||||||
|
new_wset.wss = std::move(old_wset.wss);
|
||||||
|
new_wset.focused_view_id = old_wset.focused_view_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.wsets = std::move(wsets);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "window-rules/get-focused-view") {
|
||||||
|
// data: { ok, info: view? }
|
||||||
|
if (const auto& view = data["info"]) {
|
||||||
|
auto& wset = state.wsets.at(view["wset-index"].asUInt());
|
||||||
|
wset.focused_view_id = view["id"].asUInt();
|
||||||
|
state.update_view(view);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == "window-rules/get-focused-output") {
|
||||||
|
// data: { ok, info: output }
|
||||||
|
state.focused_output_name = data["info"]["name"].asString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::wayfire
|
77
src/modules/wayfire/window.cpp
Normal file
77
src/modules/wayfire/window.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include "modules/wayfire/window.hpp"
|
||||||
|
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include "util/rewrite_string.hpp"
|
||||||
|
#include "util/sanitize_str.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::wayfire {
|
||||||
|
|
||||||
|
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: AAppIconLabel(config, "window", id, "{title}", 0, true),
|
||||||
|
ipc{IPC::get_instance()},
|
||||||
|
handler{[this](const auto&) { dp.emit(); }},
|
||||||
|
bar_{bar} {
|
||||||
|
ipc->register_handler("view-unmapped", handler);
|
||||||
|
ipc->register_handler("view-focused", handler);
|
||||||
|
ipc->register_handler("view-title-changed", handler);
|
||||||
|
ipc->register_handler("view-app-id-changed", handler);
|
||||||
|
|
||||||
|
ipc->register_handler("window-rules/get-focused-view", handler);
|
||||||
|
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::~Window() { ipc->unregister_handler(handler); }
|
||||||
|
|
||||||
|
auto Window::update() -> void {
|
||||||
|
update_icon_label();
|
||||||
|
AAppIconLabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Window::update_icon_label() -> void {
|
||||||
|
auto _ = ipc->lock_state();
|
||||||
|
|
||||||
|
const auto& output = ipc->get_outputs().at(bar_.output->name);
|
||||||
|
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||||
|
const auto& views = ipc->get_views();
|
||||||
|
auto ctx = bar_.window.get_style_context();
|
||||||
|
|
||||||
|
if (views.contains(wset.focused_view_id)) {
|
||||||
|
const auto& view = views.at(wset.focused_view_id);
|
||||||
|
auto title = view["title"].asString();
|
||||||
|
auto app_id = view["app-id"].asString();
|
||||||
|
|
||||||
|
// update label
|
||||||
|
label_.set_markup(waybar::util::rewriteString(
|
||||||
|
fmt::format(fmt::runtime(format_), fmt::arg("title", waybar::util::sanitize_string(title)),
|
||||||
|
fmt::arg("app_id", waybar::util::sanitize_string(app_id))),
|
||||||
|
config_["rewrite"]));
|
||||||
|
|
||||||
|
// update window#waybar.solo
|
||||||
|
if (wset.locate_ws(view["geometry"]).num_views > 1)
|
||||||
|
ctx->remove_class("solo");
|
||||||
|
else
|
||||||
|
ctx->add_class("solo");
|
||||||
|
|
||||||
|
// update window#waybar.<app_id>
|
||||||
|
ctx->remove_class(old_app_id_);
|
||||||
|
ctx->add_class(old_app_id_ = app_id);
|
||||||
|
|
||||||
|
// update window#waybar.empty
|
||||||
|
ctx->remove_class("empty");
|
||||||
|
|
||||||
|
//
|
||||||
|
updateAppIconName(app_id, "");
|
||||||
|
label_.show();
|
||||||
|
} else {
|
||||||
|
ctx->add_class("empty");
|
||||||
|
|
||||||
|
updateAppIconName("", "");
|
||||||
|
label_.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::wayfire
|
183
src/modules/wayfire/workspaces.cpp
Normal file
183
src/modules/wayfire/workspaces.cpp
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#include "modules/wayfire/workspaces.hpp"
|
||||||
|
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "modules/wayfire/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::wayfire {
|
||||||
|
|
||||||
|
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||||
|
: AModule{config, "workspaces", id, false, !config["disable-scroll"].asBool()},
|
||||||
|
ipc{IPC::get_instance()},
|
||||||
|
handler{[this](const auto&) { dp.emit(); }},
|
||||||
|
bar_{bar} {
|
||||||
|
// init box_
|
||||||
|
box_.set_name("workspaces");
|
||||||
|
if (!id.empty()) box_.get_style_context()->add_class(id);
|
||||||
|
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||||
|
event_box_.add(box_);
|
||||||
|
|
||||||
|
// scroll events
|
||||||
|
if (!config_["disable-scroll"].asBool()) {
|
||||||
|
auto& target = config_["enable-bar-scroll"].asBool() ? const_cast<Bar&>(bar_).window
|
||||||
|
: dynamic_cast<Gtk::Widget&>(box_);
|
||||||
|
target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
|
target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen events
|
||||||
|
ipc->register_handler("view-mapped", handler);
|
||||||
|
ipc->register_handler("view-unmapped", handler);
|
||||||
|
ipc->register_handler("view-wset-changed", handler);
|
||||||
|
ipc->register_handler("output-gain-focus", handler);
|
||||||
|
ipc->register_handler("view-sticky", handler);
|
||||||
|
ipc->register_handler("view-workspace-changed", handler);
|
||||||
|
ipc->register_handler("output-wset-changed", handler);
|
||||||
|
ipc->register_handler("wset-workspace-changed", handler);
|
||||||
|
|
||||||
|
ipc->register_handler("window-rules/list-views", handler);
|
||||||
|
ipc->register_handler("window-rules/list-outputs", handler);
|
||||||
|
ipc->register_handler("window-rules/list-wsets", handler);
|
||||||
|
ipc->register_handler("window-rules/get-focused-output", handler);
|
||||||
|
|
||||||
|
// initial render
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspaces::~Workspaces() { ipc->unregister_handler(handler); }
|
||||||
|
|
||||||
|
auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
|
||||||
|
// Ignore emulated scroll events on window
|
||||||
|
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false;
|
||||||
|
|
||||||
|
auto dir = AModule::getScrollDir(e);
|
||||||
|
if (dir == SCROLL_DIR::NONE) return true;
|
||||||
|
|
||||||
|
int delta;
|
||||||
|
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT)
|
||||||
|
delta = 1;
|
||||||
|
else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT)
|
||||||
|
delta = -1;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// cycle workspace
|
||||||
|
Json::Value data;
|
||||||
|
{
|
||||||
|
auto _ = ipc->lock_state();
|
||||||
|
const auto& output = ipc->get_outputs().at(bar_.output->name);
|
||||||
|
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||||
|
auto n = wset.ws_w * wset.ws_h;
|
||||||
|
auto i = (wset.ws_idx() + delta + n) % n;
|
||||||
|
data["x"] = i % wset.ws_w;
|
||||||
|
data["y"] = i / wset.ws_h;
|
||||||
|
data["output-id"] = output.id;
|
||||||
|
}
|
||||||
|
ipc->send("vswitch/set-workspace", std::move(data));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Workspaces::update() -> void {
|
||||||
|
update_box();
|
||||||
|
AModule::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Workspaces::update_box() -> void {
|
||||||
|
auto _ = ipc->lock_state();
|
||||||
|
|
||||||
|
const auto& output_name = bar_.output->name;
|
||||||
|
const auto& output = ipc->get_outputs().at(output_name);
|
||||||
|
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||||
|
|
||||||
|
auto output_focused = ipc->get_focused_output_name() == output_name;
|
||||||
|
auto ws_w = wset.ws_w;
|
||||||
|
auto ws_h = wset.ws_h;
|
||||||
|
auto num_wss = ws_w * ws_h;
|
||||||
|
|
||||||
|
// add buttons for new workspaces
|
||||||
|
for (auto i = buttons_.size(); i < num_wss; i++) {
|
||||||
|
auto& btn = buttons_.emplace_back("");
|
||||||
|
box_.pack_start(btn, false, false, 0);
|
||||||
|
btn.set_relief(Gtk::RELIEF_NONE);
|
||||||
|
if (!config_["disable-click"].asBool()) {
|
||||||
|
btn.signal_pressed().connect([=, this] {
|
||||||
|
Json::Value data;
|
||||||
|
data["x"] = i % ws_w;
|
||||||
|
data["y"] = i / ws_h;
|
||||||
|
data["output-id"] = output.id;
|
||||||
|
ipc->send("vswitch/set-workspace", std::move(data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove buttons for removed workspaces
|
||||||
|
buttons_.resize(num_wss);
|
||||||
|
|
||||||
|
// update buttons
|
||||||
|
for (size_t i = 0; i < num_wss; i++) {
|
||||||
|
const auto& ws = wset.wss[i];
|
||||||
|
auto& btn = buttons_[i];
|
||||||
|
auto ctx = btn.get_style_context();
|
||||||
|
auto ws_focused = i == wset.ws_idx();
|
||||||
|
auto ws_empty = ws.num_views == 0;
|
||||||
|
|
||||||
|
// update #workspaces button.focused
|
||||||
|
if (ws_focused)
|
||||||
|
ctx->add_class("focused");
|
||||||
|
else
|
||||||
|
ctx->remove_class("focused");
|
||||||
|
|
||||||
|
// update #workspaces button.empty
|
||||||
|
if (ws_empty)
|
||||||
|
ctx->add_class("empty");
|
||||||
|
else
|
||||||
|
ctx->remove_class("empty");
|
||||||
|
|
||||||
|
// update #workspaces button.current_output
|
||||||
|
if (output_focused)
|
||||||
|
ctx->add_class("current_output");
|
||||||
|
else
|
||||||
|
ctx->remove_class("current_output");
|
||||||
|
|
||||||
|
// update label
|
||||||
|
auto label = std::to_string(i + 1);
|
||||||
|
if (config_["format"].isString()) {
|
||||||
|
auto format = config_["format"].asString();
|
||||||
|
auto ws_idx = std::to_string(i + 1);
|
||||||
|
|
||||||
|
const auto& icons = config_["format-icons"];
|
||||||
|
std::string icon;
|
||||||
|
if (!icons)
|
||||||
|
icon = ws_idx;
|
||||||
|
else if (ws_focused && icons["focused"])
|
||||||
|
icon = icons["focused"].asString();
|
||||||
|
else if (icons[ws_idx])
|
||||||
|
icon = icons[ws_idx].asString();
|
||||||
|
else if (icons["default"])
|
||||||
|
icon = icons["default"].asString();
|
||||||
|
else
|
||||||
|
icon = ws_idx;
|
||||||
|
|
||||||
|
label = fmt::format(fmt::runtime(format), fmt::arg("icon", icon), fmt::arg("index", ws_idx),
|
||||||
|
fmt::arg("output", output_name));
|
||||||
|
}
|
||||||
|
if (!config_["disable-markup"].asBool())
|
||||||
|
static_cast<Gtk::Label*>(btn.get_children()[0])->set_markup(label);
|
||||||
|
else
|
||||||
|
btn.set_label(label);
|
||||||
|
|
||||||
|
//
|
||||||
|
if (config_["current-only"].asBool() && i != wset.ws_idx())
|
||||||
|
btn.hide();
|
||||||
|
else
|
||||||
|
btn.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::wayfire
|
Reference in New Issue
Block a user