summaryrefslogtreecommitdiff
path: root/src/canvas/wayland
diff options
context:
space:
mode:
Diffstat (limited to 'src/canvas/wayland')
-rw-r--r--src/canvas/wayland/config.cpp43
-rw-r--r--src/canvas/wayland/config.hpp45
-rw-r--r--src/canvas/wayland/config/dummy.cpp29
-rw-r--r--src/canvas/wayland/config/dummy.hpp35
-rw-r--r--src/canvas/wayland/config/hyprland.cpp151
-rw-r--r--src/canvas/wayland/config/hyprland.hpp53
-rw-r--r--src/canvas/wayland/config/sway.cpp183
-rw-r--r--src/canvas/wayland/config/sway.hpp62
-rw-r--r--src/canvas/wayland/config/wayfire.cpp73
-rw-r--r--src/canvas/wayland/config/wayfire.hpp44
-rw-r--r--src/canvas/wayland/wayland.cpp222
-rw-r--r--src/canvas/wayland/wayland.hpp84
-rw-r--r--src/canvas/wayland/window/shm.cpp75
-rw-r--r--src/canvas/wayland/window/shm.hpp47
-rw-r--r--src/canvas/wayland/window/waylandegl.cpp235
-rw-r--r--src/canvas/wayland/window/waylandegl.hpp88
-rw-r--r--src/canvas/wayland/window/waylandshm.cpp186
-rw-r--r--src/canvas/wayland/window/waylandshm.hpp77
-rw-r--r--src/canvas/wayland/window/waylandwindow.hpp46
19 files changed, 1778 insertions, 0 deletions
diff --git a/src/canvas/wayland/config.cpp b/src/canvas/wayland/config.cpp
new file mode 100644
index 0000000..4cd19e9
--- /dev/null
+++ b/src/canvas/wayland/config.cpp
@@ -0,0 +1,43 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "config.hpp"
+#include "os.hpp"
+#include "config/sway.hpp"
+#include "config/hyprland.hpp"
+#include "config/wayfire.hpp"
+#include "config/dummy.hpp"
+
+auto WaylandConfig::get() -> std::unique_ptr<WaylandConfig>
+{
+ const auto sway_sock = os::getenv("SWAYSOCK");
+ if (sway_sock.has_value()) {
+ return std::make_unique<SwaySocket>(sway_sock.value());
+ }
+
+ const auto hypr_sig = os::getenv("HYPRLAND_INSTANCE_SIGNATURE");
+ if (hypr_sig.has_value()) {
+ return std::make_unique<HyprlandSocket>(hypr_sig.value());
+ }
+
+ const auto wayfire_sock = os::getenv("WAYFIRE_SOCKET");
+ if (wayfire_sock.has_value()) {
+ return std::make_unique<WayfireSocket>(wayfire_sock.value());
+ }
+
+ return std::make_unique<DummyWaylandConfig>();
+}
+
diff --git a/src/canvas/wayland/config.hpp b/src/canvas/wayland/config.hpp
new file mode 100644
index 0000000..c84e669
--- /dev/null
+++ b/src/canvas/wayland/config.hpp
@@ -0,0 +1,45 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYLAND_CONFIG_H
+#define WAYLAND_CONFIG_H
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+struct WaylandWindowGeometry {
+ int width;
+ int height;
+ int x;
+ int y;
+};
+
+class WaylandConfig
+{
+ public:
+ static auto get() -> std::unique_ptr<WaylandConfig>;
+
+ virtual ~WaylandConfig() = default;
+
+ virtual auto get_focused_output_name() -> std::string = 0;
+ virtual auto get_window_info() -> struct WaylandWindowGeometry = 0;
+ virtual auto is_dummy() -> bool { return false; }
+ virtual void initial_setup(std::string_view appid) = 0;
+ virtual void move_window(std::string_view appid, int xcoord, int ycoord) = 0;
+};
+
+#endif
diff --git a/src/canvas/wayland/config/dummy.cpp b/src/canvas/wayland/config/dummy.cpp
new file mode 100644
index 0000000..d4b4297
--- /dev/null
+++ b/src/canvas/wayland/config/dummy.cpp
@@ -0,0 +1,29 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "dummy.hpp"
+
+auto DummyWaylandConfig::get_window_info() -> struct WaylandWindowGeometry
+{
+ return {};
+}
+
+void DummyWaylandConfig::initial_setup([[maybe_unused]] std::string_view appid)
+{}
+
+void DummyWaylandConfig::move_window([[maybe_unused]] std::string_view appid,
+ [[maybe_unused]] int xcoord, [[maybe_unused]] int ycoord)
+{}
diff --git a/src/canvas/wayland/config/dummy.hpp b/src/canvas/wayland/config/dummy.hpp
new file mode 100644
index 0000000..60b35c1
--- /dev/null
+++ b/src/canvas/wayland/config/dummy.hpp
@@ -0,0 +1,35 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef DUMMY_WAYLAND_CONFIG_H
+#define DUMMY_WAYLAND_CONFIG_H
+
+#include "../config.hpp"
+
+class DummyWaylandConfig : public WaylandConfig
+{
+ public:
+ DummyWaylandConfig() = default;
+ ~DummyWaylandConfig() override = default;
+
+ auto get_focused_output_name() -> std::string override { return {}; };
+ auto get_window_info() -> struct WaylandWindowGeometry override;
+ auto is_dummy() -> bool override { return true; }
+ void initial_setup(std::string_view appid) override;
+ void move_window(std::string_view appid, int xcoord, int ycoord) override;
+};
+
+#endif
diff --git a/src/canvas/wayland/config/hyprland.cpp b/src/canvas/wayland/config/hyprland.cpp
new file mode 100644
index 0000000..42387fb
--- /dev/null
+++ b/src/canvas/wayland/config/hyprland.cpp
@@ -0,0 +1,151 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "hyprland.hpp"
+#include "os.hpp"
+#include "tmux.hpp"
+#include "util/socket.hpp"
+
+#include <filesystem>
+#include <fmt/format.h>
+#include <nlohmann/json.hpp>
+#include <range/v3/all.hpp>
+
+using njson = nlohmann::json;
+namespace fs = std::filesystem;
+
+HyprlandSocket::HyprlandSocket(const std::string_view signature)
+ : logger(spdlog::get("wayland"))
+{
+ const auto socket_base_dir = os::getenv("XDG_RUNTIME_DIR").value_or("/tmp");
+ const auto socket_rel_path = fmt::format("hypr/{}/.socket.sock", signature);
+ socket_path = fmt::format("{}/{}", socket_base_dir, socket_rel_path);
+ // XDG_RUNTIME_DIR set but hyprland < 0.40
+ if (!fs::exists(socket_path)) {
+ socket_path = fmt::format("/tmp/{}", socket_rel_path);
+ }
+
+ logger->info("Using hyprland socket {}", socket_path);
+ const auto active = request_result("j/activewindow");
+ address = active.at("address");
+ set_active_monitor();
+}
+
+void HyprlandSocket::set_active_monitor()
+{
+ const auto monitors = request_result("j/monitors");
+ for (const auto &monitor : monitors) {
+ bool focused = monitor.at("focused");
+ if (focused) {
+ output_name = monitor.at("name");
+ output_scale = monitor.at("scale");
+ break;
+ }
+ }
+}
+
+auto HyprlandSocket::get_focused_output_name() -> std::string
+{
+ return output_name;
+}
+
+auto HyprlandSocket::request_result(const std::string_view payload) -> nlohmann::json
+{
+ const UnixSocket socket{socket_path};
+ socket.write(payload.data(), payload.size());
+ const std::string result = socket.read_until_empty();
+ return njson::parse(result);
+}
+
+void HyprlandSocket::request(const std::string_view payload)
+{
+ const UnixSocket socket{socket_path};
+ logger->debug("Running socket command {}", payload);
+ socket.write(payload.data(), payload.length());
+}
+
+auto HyprlandSocket::get_active_window() -> nlohmann::json
+{
+ // recalculate address in case it changed
+ if (tmux::is_used()) {
+ const auto active = request_result("j/activewindow");
+ address = active.at("address");
+ }
+ const auto clients = request_result("j/clients");
+ const auto client = ranges::find_if(clients, [this](const njson &json) { return json.at("address") == address; });
+ if (client == clients.end()) {
+ throw std::runtime_error("Active window not found");
+ }
+ return *client;
+}
+
+auto HyprlandSocket::get_window_info() -> struct WaylandWindowGeometry {
+ const auto terminal = get_active_window();
+ const auto &sizes = terminal.at("size");
+ const auto &coords = terminal.at("at");
+
+ return {
+ .width = sizes.at(0),
+ .height = sizes.at(1),
+ .x = coords.at(0),
+ .y = coords.at(1),
+ };
+}
+
+void HyprlandSocket::initial_setup(const std::string_view appid)
+{
+ disable_focus(appid);
+ enable_floating(appid);
+ remove_borders(appid);
+ remove_rounding(appid);
+}
+
+void HyprlandSocket::remove_rounding(const std::string_view appid)
+{
+ const auto payload = fmt::format("/keyword windowrulev2 rounding 0,title:{}", appid);
+ request(payload);
+}
+
+void HyprlandSocket::disable_focus(const std::string_view appid)
+{
+ const auto payload = fmt::format("/keyword windowrulev2 nofocus,title:{}", appid);
+ request(payload);
+}
+
+void HyprlandSocket::enable_floating(const std::string_view appid)
+{
+ const auto payload = fmt::format("/keyword windowrulev2 float,title:{}", appid);
+ request(payload);
+}
+
+void HyprlandSocket::remove_borders(const std::string_view appid)
+{
+ const auto payload = fmt::format("/keyword windowrulev2 noborder,title:{}", appid);
+ request(payload);
+}
+
+void HyprlandSocket::move_window(const std::string_view appid, int xcoord, int ycoord)
+{
+ int res_x = xcoord;
+ int res_y = ycoord;
+ if (output_scale > 1.0F) {
+ const int offset = 10;
+ res_x = res_x / 2 + offset;
+ res_y = res_y / 2 + offset;
+ }
+ const auto payload = fmt::format("/dispatch movewindowpixel exact {} {},title:{}", res_x, res_y, appid);
+ request(payload);
+}
diff --git a/src/canvas/wayland/config/hyprland.hpp b/src/canvas/wayland/config/hyprland.hpp
new file mode 100644
index 0000000..4353d98
--- /dev/null
+++ b/src/canvas/wayland/config/hyprland.hpp
@@ -0,0 +1,53 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef HYPRLAND_SOCKET_H
+#define HYPRLAND_SOCKET_H
+
+#include "../config.hpp"
+
+#include <nlohmann/json.hpp>
+#include <spdlog/spdlog.h>
+
+class HyprlandSocket : public WaylandConfig
+{
+ public:
+ explicit HyprlandSocket(std::string_view signature);
+ ~HyprlandSocket() override = default;
+
+ auto get_window_info() -> struct WaylandWindowGeometry override;
+ auto get_focused_output_name() -> std::string override;
+ void initial_setup(std::string_view appid) override;
+ void move_window(std::string_view appid, int xcoord, int ycoord) override;
+
+ private:
+ void disable_focus(std::string_view appid);
+ void enable_floating(std::string_view appid);
+ void remove_borders(std::string_view appid);
+ void remove_rounding(std::string_view appid);
+ void request(std::string_view payload);
+ auto request_result(std::string_view payload) -> nlohmann::json;
+ auto get_active_window() -> nlohmann::json;
+ void set_active_monitor();
+
+ std::shared_ptr<spdlog::logger> logger;
+ std::string socket_path;
+ std::string address;
+ std::string output_name;
+ float output_scale = 1.0F;
+};
+
+#endif
diff --git a/src/canvas/wayland/config/sway.cpp b/src/canvas/wayland/config/sway.cpp
new file mode 100644
index 0000000..85bff0c
--- /dev/null
+++ b/src/canvas/wayland/config/sway.cpp
@@ -0,0 +1,183 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "sway.hpp"
+#include "application.hpp"
+#include "os.hpp"
+#include "tmux.hpp"
+#include "util.hpp"
+
+#include <algorithm>
+#include <array>
+#include <stack>
+#include <string>
+
+#include <fmt/format.h>
+#include <range/v3/all.hpp>
+
+using njson = nlohmann::json;
+
+constexpr auto ipc_magic = std::string_view{"i3-ipc"};
+constexpr auto ipc_header_size = ipc_magic.size() + 8;
+
+SwaySocket::SwaySocket(const std::string_view endpoint)
+ : socket(endpoint),
+ logger(spdlog::get("wayland"))
+{
+ logger->info("Using sway socket {}", endpoint);
+ set_active_output_info();
+}
+
+struct __attribute__((packed)) ipc_header {
+ std::array<char, ipc_magic.size()> magic;
+ uint32_t len;
+ uint32_t type;
+};
+
+auto SwaySocket::get_window_info() -> struct WaylandWindowGeometry {
+ const auto nodes = get_nodes();
+ const auto window = get_active_window(nodes);
+ const auto &rect = window.at("rect");
+ return {.width = rect.at("width"),
+ .height = rect.at("height"),
+ .x = rect.at("x").get<int>() - output_info.x,
+ .y = rect.at("y").get<int>() - output_info.y};
+}
+
+auto SwaySocket::get_active_window(const std::vector<nlohmann::json>& nodes) -> nlohmann::json
+{
+ const auto pids = tmux::get_client_pids().value_or(std::vector<int>{Application::parent_pid});
+
+ for (const auto pid : pids) {
+ const auto tree = util::get_process_tree(pid);
+ const auto found = ranges::find_if(nodes, [&tree](const njson &json) -> bool {
+ try {
+ return ranges::find(tree, json.at("pid").get<int>()) != tree.end();
+ } catch (const njson::out_of_range &err) {
+ return false;
+ }
+ });
+ if (found != nodes.end()) {
+ return *found;
+ }
+ }
+ return nullptr;
+}
+
+auto SwaySocket::get_focused_output_name() -> std::string
+{
+ return output_info.name;
+};
+
+void SwaySocket::set_active_output_info()
+{
+ const auto outputs = ipc_message(IPC_GET_OUTPUTS);
+ for (const auto &node : outputs) {
+ bool focused = node.at("focused");
+ if (focused) {
+ const auto &rect = node.at("rect");
+ output_info = {
+ .x = rect.at("x"),
+ .y = rect.at("y"),
+ .scale = node.at("scale"),
+ .name = node.at("name"),
+ };
+ break;
+ }
+ }
+}
+
+void SwaySocket::initial_setup(const std::string_view appid)
+{
+ disable_focus(appid);
+ enable_floating(appid);
+}
+
+void SwaySocket::disable_focus(const std::string_view appid)
+{
+ const auto payload = fmt::format(R"(no_focus [app_id="{}"])", appid);
+ ipc_command(payload);
+}
+
+void SwaySocket::enable_floating(const std::string_view appid)
+{
+ ipc_command(appid, "floating enable");
+}
+
+void SwaySocket::move_window(const std::string_view appid, int xcoord, int ycoord)
+{
+ int res_x = xcoord;
+ int res_y = ycoord;
+ if (output_info.scale > 1.0F) {
+ res_x = res_x / 2;
+ res_y = res_y / 2;
+ }
+ const auto payload = fmt::format(R"([app_id="{}"] move position {} {})", appid, res_x, res_y);
+ ipc_command(payload);
+}
+
+auto SwaySocket::ipc_message(ipc_message_type type, const std::string_view payload) const -> nlohmann::json
+{
+ struct ipc_header header;
+ header.len = payload.size();
+ header.type = type;
+ ipc_magic.copy(header.magic.data(), ipc_magic.size());
+
+ if (!payload.empty()) {
+ logger->debug("Running socket command {}", payload);
+ }
+ socket.write(&header, ipc_header_size);
+ socket.write(payload.data(), payload.size());
+
+ socket.read(&header, ipc_header_size);
+ std::string buff(header.len, 0);
+ socket.read(buff.data(), buff.size());
+ return njson::parse(buff);
+}
+
+auto SwaySocket::get_nodes() const -> std::vector<nlohmann::json>
+{
+ logger->debug("Obtaining sway tree");
+ const auto tree = ipc_message(IPC_GET_TREE);
+ std::stack<njson> nodes_st;
+ std::vector<njson> nodes_vec;
+
+ nodes_st.push(tree);
+
+ while (!nodes_st.empty()) {
+ const auto top = nodes_st.top();
+ nodes_st.pop();
+ nodes_vec.push_back(top);
+ for (const auto &node : top.at("nodes")) {
+ nodes_st.push(node);
+ }
+ for (const auto &node : top.at("floating_nodes")) {
+ nodes_st.push(node);
+ }
+ }
+ return nodes_vec;
+}
+
+void SwaySocket::ipc_command(const std::string_view appid, const std::string_view command) const
+{
+ const auto payload = fmt::format(R"(for_window [app_id="{}"] {})", appid, command);
+ ipc_command(payload);
+}
+
+void SwaySocket::ipc_command(const std::string_view payload) const
+{
+ std::ignore = ipc_message(IPC_COMMAND, payload);
+}
diff --git a/src/canvas/wayland/config/sway.hpp b/src/canvas/wayland/config/sway.hpp
new file mode 100644
index 0000000..9ac4d69
--- /dev/null
+++ b/src/canvas/wayland/config/sway.hpp
@@ -0,0 +1,62 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef SWAY_SOCKET_H
+#define SWAY_SOCKET_H
+
+#include "../config.hpp"
+#include "util/socket.hpp"
+
+#include <nlohmann/json.hpp>
+#include <spdlog/spdlog.h>
+
+enum ipc_message_type { IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4 };
+
+struct SwayOutputInfo {
+ int x;
+ int y;
+ float scale;
+ std::string name;
+};
+
+class SwaySocket : public WaylandConfig
+{
+ public:
+ explicit SwaySocket(std::string_view endpoint);
+ ~SwaySocket() override = default;
+
+ auto get_focused_output_name() -> std::string override;
+ auto get_window_info() -> struct WaylandWindowGeometry override;
+ void initial_setup(std::string_view appid) override;
+ void move_window(std::string_view appid, int xcoord, int ycoord) override;
+ static auto get_active_window(const std::vector<nlohmann::json> &nodes) -> nlohmann::json;
+
+ private:
+ void disable_focus(std::string_view appid);
+ void enable_floating(std::string_view appid);
+ void ipc_command(std::string_view appid, std::string_view command) const;
+ void ipc_command(std::string_view payload) const;
+ void set_active_output_info();
+
+ [[nodiscard]] auto get_nodes() const -> std::vector<nlohmann::json>;
+ [[nodiscard]] auto ipc_message(ipc_message_type type, std::string_view payload = "") const -> nlohmann::json;
+
+ UnixSocket socket;
+ std::shared_ptr<spdlog::logger> logger;
+ struct SwayOutputInfo output_info;
+};
+
+#endif
diff --git a/src/canvas/wayland/config/wayfire.cpp b/src/canvas/wayland/config/wayfire.cpp
new file mode 100644
index 0000000..efde546
--- /dev/null
+++ b/src/canvas/wayland/config/wayfire.cpp
@@ -0,0 +1,73 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "wayfire.hpp"
+
+using njson = nlohmann::json;
+
+WayfireSocket::WayfireSocket(const std::string_view endpoint):
+socket(endpoint),
+logger(spdlog::get("wayland"))
+{
+ logger->info("Using wayfire socket {}", endpoint);
+}
+
+auto WayfireSocket::get_window_info() -> struct WaylandWindowGeometry
+{
+ const auto response = request("window-rules/get-focused-view");
+ const auto& info = response.at("info");
+ const auto& geometry = info.at("geometry");
+ const int decoration_height = 25;
+ return {
+ .width = geometry.at("width"),
+ .height = geometry.at("height").get<int>() - decoration_height,
+ .x = 0,
+ .y = 0
+ };
+}
+
+void WayfireSocket::initial_setup([[maybe_unused]] const std::string_view appid)
+{
+ // all is handled by the ueberzug plugin
+}
+
+void WayfireSocket::move_window(const std::string_view appid, int xcoord, int ycoord)
+{
+ const njson payload_data = {
+ {"app-id", appid},
+ {"x", xcoord},
+ {"y", ycoord}
+ };
+ std::ignore = request("ueberzugpp/set_offset", payload_data);
+}
+
+auto WayfireSocket::request(const std::string_view method, const njson& data) const -> njson
+{
+ const njson json = {
+ {"method", method},
+ {"data", data}
+ };
+ const auto payload = json.dump();
+ const uint32_t payload_size = payload.length();
+ socket.write(&payload_size, sizeof(uint32_t));
+ socket.write(payload.c_str(), payload_size);
+
+ uint32_t response_size = 0;
+ socket.read(&response_size, sizeof(uint32_t));
+ std::string buffer (response_size, 0);
+ socket.read(buffer.data(), response_size);
+ return njson::parse(buffer);
+}
diff --git a/src/canvas/wayland/config/wayfire.hpp b/src/canvas/wayland/config/wayfire.hpp
new file mode 100644
index 0000000..e1e83eb
--- /dev/null
+++ b/src/canvas/wayland/config/wayfire.hpp
@@ -0,0 +1,44 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYFIRE_SOCKET_H
+#define WAYFIRE_SOCKET_H
+
+#include "../config.hpp"
+#include "util/socket.hpp"
+
+#include <nlohmann/json.hpp>
+#include <spdlog/spdlog.h>
+
+class WayfireSocket : public WaylandConfig
+{
+ public:
+ explicit WayfireSocket(std::string_view endpoint);
+ ~WayfireSocket() override = default;
+
+ auto get_window_info() -> struct WaylandWindowGeometry override;
+ auto get_focused_output_name() -> std::string override { return {}; };
+ void initial_setup(std::string_view appid) override;
+ void move_window(std::string_view appid, int xcoord, int ycoord) override;
+
+ private:
+ [[nodiscard]] auto request(std::string_view method, const nlohmann::json &data = {}) const -> nlohmann::json;
+
+ UnixSocket socket;
+ std::shared_ptr<spdlog::logger> logger;
+};
+
+#endif
diff --git a/src/canvas/wayland/wayland.cpp b/src/canvas/wayland/wayland.cpp
new file mode 100644
index 0000000..9e2a642
--- /dev/null
+++ b/src/canvas/wayland/wayland.cpp
@@ -0,0 +1,222 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "wayland.hpp"
+#include "application.hpp"
+#include "image.hpp"
+#include "os.hpp"
+#include "util.hpp"
+
+#ifdef ENABLE_OPENGL
+# include "window/waylandegl.hpp"
+# include <EGL/eglext.h>
+#endif
+#include "window/waylandshm.hpp"
+
+constexpr struct wl_registry_listener registry_listener = {.global = WaylandCanvas::registry_handle_global,
+ .global_remove = [](auto...) { /*unused*/ }};
+
+constexpr struct xdg_wm_base_listener xdg_wm_base_listener = {.ping = WaylandCanvas::xdg_wm_base_ping};
+
+constexpr struct wl_output_listener wl_output_listener = {.geometry = [](auto...) { /*unused*/ },
+ .mode = [](auto...) { /*unused*/ },
+ .done = WaylandCanvas::output_done,
+ .scale = WaylandCanvas::output_scale,
+ .name = WaylandCanvas::output_name,
+ .description = [](auto...) { /*unused*/ }};
+
+void WaylandCanvas::registry_handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
+ [[maybe_unused]] uint32_t version)
+{
+ const std::string_view interface_str{interface};
+#ifdef WSL
+ const uint32_t compositor_ver = 4;
+ const uint32_t shm_ver = 1;
+ const uint32_t xdg_base_ver = 1;
+ const uint32_t output_ver = 3;
+#else
+ const uint32_t compositor_ver = 5;
+ const uint32_t shm_ver = 1;
+ const uint32_t xdg_base_ver = 2;
+ const uint32_t output_ver = 4;
+#endif
+
+ auto *canvas = static_cast<WaylandCanvas *>(data);
+ if (interface_str == wl_compositor_interface.name) {
+ canvas->compositor = static_cast<struct wl_compositor *>(
+ wl_registry_bind(registry, name, &wl_compositor_interface, compositor_ver));
+ } else if (interface_str == wl_shm_interface.name) {
+ canvas->wl_shm = static_cast<struct wl_shm *>(wl_registry_bind(registry, name, &wl_shm_interface, shm_ver));
+ } else if (interface_str == xdg_wm_base_interface.name) {
+ canvas->xdg_base =
+ static_cast<struct xdg_wm_base *>(wl_registry_bind(registry, name, &xdg_wm_base_interface, xdg_base_ver));
+ xdg_wm_base_add_listener(canvas->xdg_base, &xdg_wm_base_listener, canvas);
+ } else if (interface_str == wl_output_interface.name) {
+ auto *output =
+ static_cast<struct wl_output *>(wl_registry_bind(registry, name, &wl_output_interface, output_ver));
+ wl_output_add_listener(output, &wl_output_listener, canvas);
+ }
+}
+
+void WaylandCanvas::xdg_wm_base_ping([[maybe_unused]] void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+{
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+void WaylandCanvas::output_name(void *data, [[maybe_unused]] struct wl_output *output, const char *name)
+{
+ auto *canvas = static_cast<WaylandCanvas *>(data);
+ canvas->output_pair.first = name;
+}
+
+void WaylandCanvas::output_scale(void *data, [[maybe_unused]] struct wl_output *output, int32_t scale)
+{
+ auto *canvas = static_cast<WaylandCanvas *>(data);
+ canvas->output_pair.second = scale;
+}
+
+void WaylandCanvas::output_done(void *data, [[maybe_unused]] struct wl_output *output)
+{
+ auto *canvas = static_cast<WaylandCanvas *>(data);
+ const auto active_output = canvas->config->get_focused_output_name();
+ if (active_output == canvas->output_pair.first) {
+ canvas->flags->scale_factor = canvas->output_pair.second;
+ canvas->flags->needs_scaling = canvas->output_pair.second > 1;
+ }
+ canvas->output_info.insert(canvas->output_pair);
+}
+
+WaylandCanvas::WaylandCanvas()
+ : display(wl_display_connect(nullptr))
+{
+ if (display == nullptr) {
+ throw std::runtime_error("Failed to connect to wayland display.");
+ }
+ logger = spdlog::get("wayland");
+ flags = Flags::instance();
+ config = WaylandConfig::get();
+ registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, this);
+ wl_display_roundtrip(display);
+ event_handler = std::thread(&WaylandCanvas::handle_events, this);
+
+#ifdef ENABLE_OPENGL
+ if (flags->use_opengl) {
+ try {
+ egl = std::make_unique<EGLUtil<struct wl_display, struct wl_egl_window>>(EGL_PLATFORM_WAYLAND_EXT, display);
+ } catch (const std::runtime_error &) {
+ egl_available = false;
+ }
+ } else {
+ egl_available = false;
+ }
+#endif
+
+ logger->info("Canvas created");
+}
+
+void WaylandCanvas::show()
+{
+ for (const auto &[key, value] : windows) {
+ value->show();
+ }
+}
+
+void WaylandCanvas::hide()
+{
+ for (const auto &[key, value] : windows) {
+ value->hide();
+ }
+}
+
+WaylandCanvas::~WaylandCanvas()
+{
+ windows.clear();
+ if (event_handler.joinable()) {
+ event_handler.join();
+ }
+
+#ifdef ENABLE_OPENGL
+ egl.reset();
+#endif
+
+ if (wl_shm != nullptr) {
+ wl_shm_destroy(wl_shm);
+ }
+ if (compositor != nullptr) {
+ wl_compositor_destroy(compositor);
+ }
+ if (registry != nullptr) {
+ wl_registry_destroy(registry);
+ }
+ wl_display_disconnect(display);
+}
+
+void WaylandCanvas::add_image(const std::string &identifier, std::unique_ptr<Image> new_image)
+{
+ std::shared_ptr<WaylandWindow> window;
+#ifdef ENABLE_OPENGL
+ if (egl_available) {
+ try {
+ window = std::make_shared<WaylandEglWindow>(compositor, xdg_base, egl.get(), std::move(new_image),
+ config.get(), &xdg_agg);
+ } catch (const std::runtime_error &err) {
+ return;
+ }
+ }
+#endif
+ if (window == nullptr) {
+ window = std::make_shared<WaylandShmWindow>(this, std::move(new_image), &xdg_agg, config.get());
+ }
+
+ window->finish_init();
+ windows.insert_or_assign(identifier, std::move(window));
+}
+
+void WaylandCanvas::handle_events()
+{
+ const auto wl_fd = wl_display_get_fd(display);
+ bool in_event = false;
+
+ while (!Application::stop_flag) {
+ // prepare to read wayland events
+ while (wl_display_prepare_read(display) != 0) {
+ wl_display_dispatch_pending(display);
+ }
+ wl_display_flush(display);
+
+ try {
+ constexpr int waitms = 100;
+ in_event = os::wait_for_data_on_fd(wl_fd, waitms);
+ } catch (const std::system_error &err) {
+ Application::stop_flag = true;
+ break;
+ }
+
+ if (in_event) {
+ wl_display_read_events(display);
+ wl_display_dispatch_pending(display);
+ } else {
+ wl_display_cancel_read(display);
+ }
+ }
+}
+
+void WaylandCanvas::remove_image(const std::string &identifier)
+{
+ windows.erase(identifier);
+ wl_display_flush(display);
+}
diff --git a/src/canvas/wayland/wayland.hpp b/src/canvas/wayland/wayland.hpp
new file mode 100644
index 0000000..e2016df
--- /dev/null
+++ b/src/canvas/wayland/wayland.hpp
@@ -0,0 +1,84 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYLAND_CANVAS_H
+#define WAYLAND_CANVAS_H
+
+#include "canvas.hpp"
+#include "config.hpp"
+#include "flags.hpp"
+#include "wayland-xdg-shell-client-protocol.h"
+#include "window/waylandwindow.hpp"
+
+#include <memory>
+#include <thread>
+#include <unordered_map>
+
+#include <spdlog/spdlog.h>
+#include <wayland-client.h>
+
+#ifdef ENABLE_OPENGL
+# include "util/egl.hpp"
+# include <wayland-egl.h>
+#endif
+
+class WaylandCanvas : public Canvas
+{
+ public:
+ explicit WaylandCanvas();
+ ~WaylandCanvas() override;
+
+ static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface,
+ uint32_t version);
+ static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial);
+
+ static void output_scale(void *data, struct wl_output *output, int32_t scale);
+ static void output_name(void *data, struct wl_output *output, const char *name);
+ static void output_done(void *data, struct wl_output *output);
+
+ void add_image(const std::string &identifier, std::unique_ptr<Image> new_image) override;
+ void remove_image(const std::string &identifier) override;
+ void show() override;
+ void hide() override;
+
+ struct wl_compositor *compositor = nullptr;
+ struct wl_shm *wl_shm = nullptr;
+ struct xdg_wm_base *xdg_base = nullptr;
+
+ std::pair<std::string, int32_t> output_pair;
+ std::unordered_map<std::string, int32_t> output_info;
+
+ private:
+ struct wl_display *display = nullptr;
+ struct wl_registry *registry = nullptr;
+ std::thread event_handler;
+
+ std::shared_ptr<spdlog::logger> logger;
+ std::unique_ptr<WaylandConfig> config;
+ std::shared_ptr<Flags> flags;
+ std::unordered_map<std::string, std::shared_ptr<WaylandWindow>> windows;
+
+#ifdef ENABLE_OPENGL
+ std::unique_ptr<EGLUtil<struct wl_display, struct wl_egl_window>> egl;
+ bool egl_available = true;
+#endif
+
+ struct XdgStructAgg xdg_agg;
+
+ void handle_events();
+};
+
+#endif
diff --git a/src/canvas/wayland/window/shm.cpp b/src/canvas/wayland/window/shm.cpp
new file mode 100644
index 0000000..461267d
--- /dev/null
+++ b/src/canvas/wayland/window/shm.cpp
@@ -0,0 +1,75 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "shm.hpp"
+#include "util.hpp"
+#include "util/ptr.hpp"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <fmt/format.h>
+
+#include <cerrno>
+#include <system_error>
+
+WaylandShm::WaylandShm(int width, int height, int scale_factor, struct wl_shm *shm)
+ : shm(shm),
+ width(width),
+ height(height),
+ stride(width * 4),
+ pool_size(height * stride * scale_factor)
+{
+ const int path_size = 32;
+ shm_path = fmt::format("/{}", util::generate_random_string(path_size));
+ create_shm_file();
+ allocate_pool_buffers();
+}
+
+void WaylandShm::create_shm_file()
+{
+ fd = memfd_create("ueberzugpp-shm", 0);
+ if (fd == -1) {
+ throw std::system_error(errno, std::system_category());
+ }
+ int res = ftruncate(fd, pool_size);
+ if (res == -1) {
+ throw std::system_error(errno, std::system_category());
+ }
+ auto *pool_ptr = mmap(nullptr, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (pool_ptr == MAP_FAILED) {
+ throw std::system_error(errno, std::system_category());
+ }
+ pool_data = static_cast<uint8_t *>(pool_ptr);
+}
+
+void WaylandShm::allocate_pool_buffers()
+{
+ const auto pool = c_unique_ptr<struct wl_shm_pool, wl_shm_pool_destroy>{wl_shm_create_pool(shm, fd, pool_size)};
+ buffer = wl_shm_pool_create_buffer(pool.get(), 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
+}
+
+WaylandShm::~WaylandShm()
+{
+ shm_unlink(shm_path.c_str());
+ close(fd);
+ munmap(pool_data, pool_size);
+ if (buffer != nullptr) {
+ wl_buffer_destroy(buffer);
+ }
+}
diff --git a/src/canvas/wayland/window/shm.hpp b/src/canvas/wayland/window/shm.hpp
new file mode 100644
index 0000000..fca1f88
--- /dev/null
+++ b/src/canvas/wayland/window/shm.hpp
@@ -0,0 +1,47 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYLAND_SHM_H
+#define WAYLAND_SHM_H
+
+#include <string>
+#include <wayland-client.h>
+
+class WaylandShm
+{
+ public:
+ WaylandShm(int width, int height, int scale_factor, struct wl_shm *shm);
+ ~WaylandShm();
+
+ struct wl_buffer *buffer = nullptr;
+ uint8_t *pool_data;
+
+ private:
+ void create_shm_file();
+ void allocate_pool_buffers();
+
+ struct wl_shm *shm = nullptr;
+
+ int fd = 0;
+ std::string shm_path;
+
+ int width = 0;
+ int height = 0;
+ int stride = 0;
+ int pool_size = 0;
+};
+
+#endif
diff --git a/src/canvas/wayland/window/waylandegl.cpp b/src/canvas/wayland/window/waylandegl.cpp
new file mode 100644
index 0000000..3545d20
--- /dev/null
+++ b/src/canvas/wayland/window/waylandegl.cpp
@@ -0,0 +1,235 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "waylandegl.hpp"
+#include "dimensions.hpp"
+#include "util.hpp"
+
+#include <fmt/format.h>
+#include <thread>
+
+constexpr int id_len = 10;
+
+constexpr struct xdg_surface_listener xdg_surface_listener_egl = {
+ .configure = WaylandEglWindow::xdg_surface_configure,
+};
+
+constexpr struct wl_callback_listener frame_listener_egl = {.done = WaylandEglWindow::wl_surface_frame_done};
+
+WaylandEglWindow::WaylandEglWindow(struct wl_compositor *compositor, struct xdg_wm_base *xdg_base,
+ const EGLUtil<struct wl_display, struct wl_egl_window> *egl,
+ std::unique_ptr<Image> new_image, WaylandConfig *new_config,
+ struct XdgStructAgg *xdg_agg)
+ : compositor(compositor),
+ xdg_base(xdg_base),
+ surface(wl_compositor_create_surface(compositor)),
+ xdg_surface(xdg_wm_base_get_xdg_surface(xdg_base, surface)),
+ xdg_toplevel(xdg_surface_get_toplevel(xdg_surface)),
+ image(std::move(new_image)),
+ config(new_config),
+ egl_window(wl_egl_window_create(surface, image->width(), image->height())),
+ egl(egl),
+ appid(fmt::format("ueberzugpp_{}", util::generate_random_string(id_len))),
+ xdg_agg(xdg_agg)
+{
+ config->initial_setup(appid);
+ opengl_setup();
+ xdg_setup();
+}
+
+WaylandEglWindow::~WaylandEglWindow()
+{
+ opengl_cleanup();
+ delete_xdg_structs();
+ delete_wayland_structs();
+}
+
+void WaylandEglWindow::opengl_cleanup()
+{
+ egl->run_contained(egl_context, egl_surface, [this] {
+ glDeleteTextures(1, &texture);
+ glDeleteFramebuffers(1, &fbo);
+ });
+ eglDestroySurface(egl->display, egl_surface);
+ eglDestroyContext(egl->display, egl_context);
+}
+
+void WaylandEglWindow::finish_init()
+{
+ auto xdg = std::make_unique<XdgStruct>();
+ xdg->ptr = weak_from_this();
+ this_ptr = xdg.get();
+ xdg_agg->ptrs.push_back(std::move(xdg));
+ setup_listeners();
+ visible = true;
+}
+
+void WaylandEglWindow::opengl_setup()
+{
+ egl_surface = egl->create_surface(egl_window);
+ if (egl_surface == EGL_NO_SURFACE) {
+ throw std::runtime_error("");
+ }
+
+ egl_context = egl->create_context(egl_surface);
+ if (egl_context == EGL_NO_CONTEXT) {
+ throw std::runtime_error("");
+ }
+
+ egl->run_contained(egl_surface, egl_context, [this] {
+ eglSwapInterval(egl->display, 0);
+ glGenFramebuffers(1, &fbo);
+ glGenTextures(1, &texture);
+ });
+}
+
+void WaylandEglWindow::setup_listeners()
+{
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener_egl, this_ptr);
+ wl_surface_commit(surface);
+
+ if (image->is_animated()) {
+ callback = wl_surface_frame(surface);
+ wl_callback_add_listener(callback, &frame_listener_egl, this_ptr);
+ }
+}
+
+void WaylandEglWindow::xdg_setup()
+{
+ xdg_toplevel_set_app_id(xdg_toplevel, appid.c_str());
+ xdg_toplevel_set_title(xdg_toplevel, appid.c_str());
+}
+
+void WaylandEglWindow::delete_xdg_structs()
+{
+ if (xdg_toplevel != nullptr) {
+ xdg_toplevel_destroy(xdg_toplevel);
+ xdg_toplevel = nullptr;
+ }
+ if (xdg_surface != nullptr) {
+ xdg_surface_destroy(xdg_surface);
+ xdg_surface = nullptr;
+ }
+}
+
+void WaylandEglWindow::delete_wayland_structs()
+{
+ if (egl_window != nullptr) {
+ wl_egl_window_destroy(egl_window);
+ egl_window = nullptr;
+ }
+ if (surface != nullptr) {
+ wl_surface_destroy(surface);
+ surface = nullptr;
+ }
+}
+
+void WaylandEglWindow::draw()
+{
+ load_framebuffer();
+
+ wl_surface_commit(surface);
+ move_window();
+}
+
+void WaylandEglWindow::load_framebuffer()
+{
+ std::scoped_lock lock{egl_mutex};
+ egl->run_contained(egl_surface, egl_context, [this] {
+ egl->get_texture_from_image(*image, texture);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+ glBlitFramebuffer(0, 0, image->width(), image->height(), 0, 0, image->width(), image->height(),
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ eglSwapBuffers(egl->display, egl_surface);
+ });
+}
+
+void WaylandEglWindow::move_window()
+{
+ const auto dims = image->dimensions();
+ const auto cur_window = config->get_window_info();
+ const int wayland_x = dims.xpixels() + dims.padding_horizontal;
+ const int wayland_y = dims.ypixels() + dims.padding_vertical;
+ const int xcoord = cur_window.x + wayland_x;
+ const int ycoord = cur_window.y + wayland_y;
+ config->move_window(appid, xcoord, ycoord);
+}
+
+void WaylandEglWindow::generate_frame()
+{
+ std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
+ callback = wl_surface_frame(surface);
+ wl_callback_add_listener(callback, &frame_listener_egl, this_ptr);
+
+ image->next_frame();
+ load_framebuffer();
+
+ wl_surface_commit(surface);
+}
+
+void WaylandEglWindow::show()
+{
+ if (visible) {
+ return;
+ }
+ visible = true;
+ xdg_surface = xdg_wm_base_get_xdg_surface(xdg_base, surface);
+ xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+ xdg_setup();
+ setup_listeners();
+}
+
+void WaylandEglWindow::hide()
+{
+ if (!visible) {
+ return;
+ }
+ visible = false;
+ const std::scoped_lock lock{draw_mutex};
+ delete_xdg_structs();
+ wl_surface_attach(surface, nullptr, 0, 0);
+ wl_surface_commit(surface);
+}
+
+void WaylandEglWindow::xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
+{
+ xdg_surface_ack_configure(xdg_surface, serial);
+ const auto *tmp = static_cast<struct XdgStruct *>(data);
+ const auto window = tmp->ptr.lock();
+ if (!window) {
+ return;
+ }
+ auto *egl_window = dynamic_cast<WaylandEglWindow *>(window.get());
+ egl_window->draw();
+}
+
+void WaylandEglWindow::wl_surface_frame_done(void *data, struct wl_callback *callback, [[maybe_unused]] uint32_t time)
+{
+ wl_callback_destroy(callback);
+ const auto *tmp = static_cast<struct XdgStruct *>(data);
+ const auto window = tmp->ptr.lock();
+ if (!window) {
+ return;
+ }
+ auto *egl_window = dynamic_cast<WaylandEglWindow *>(window.get());
+ const std::scoped_lock lock{egl_window->draw_mutex};
+ if (!egl_window->visible) {
+ return;
+ }
+ egl_window->generate_frame();
+}
diff --git a/src/canvas/wayland/window/waylandegl.hpp b/src/canvas/wayland/window/waylandegl.hpp
new file mode 100644
index 0000000..768e57f
--- /dev/null
+++ b/src/canvas/wayland/window/waylandegl.hpp
@@ -0,0 +1,88 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYLAND_EGL_WINDOW_H
+#define WAYLAND_EGL_WINDOW_H
+
+#include "../config.hpp"
+#include "image.hpp"
+#include "util/egl.hpp"
+#include "wayland-xdg-shell-client-protocol.h"
+#include "waylandwindow.hpp"
+
+#include <wayland-client.h>
+#include <wayland-egl.h>
+
+#include <memory>
+#include <mutex>
+
+class WaylandEglWindow : public WaylandWindow
+{
+ public:
+ WaylandEglWindow(struct wl_compositor *compositor, struct xdg_wm_base *xdg_base,
+ const EGLUtil<struct wl_display, struct wl_egl_window> *egl, std::unique_ptr<Image> new_image,
+ WaylandConfig *new_config, struct XdgStructAgg *xdg_agg);
+ ~WaylandEglWindow() override;
+ static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial);
+ static void wl_surface_frame_done(void *data, struct wl_callback *callback, uint32_t time);
+
+ void draw() override;
+ void generate_frame() override;
+ void show() override;
+ void hide() override;
+
+ void finish_init() override;
+
+ private:
+ struct wl_compositor *compositor;
+ struct xdg_wm_base *xdg_base;
+
+ struct wl_surface *surface = nullptr;
+ struct xdg_surface *xdg_surface = nullptr;
+ struct xdg_toplevel *xdg_toplevel = nullptr;
+ struct wl_callback *callback;
+
+ std::unique_ptr<Image> image;
+ WaylandConfig *config;
+
+ EGLSurface egl_surface;
+ EGLContext egl_context;
+
+ struct wl_egl_window *egl_window = nullptr;
+ const EGLUtil<struct wl_display, struct wl_egl_window> *egl;
+
+ GLuint texture;
+ GLuint fbo;
+
+ std::mutex draw_mutex;
+ std::mutex egl_mutex;
+
+ std::string appid;
+ void *this_ptr;
+ struct XdgStructAgg *xdg_agg;
+ bool visible = false;
+
+ void move_window();
+ void delete_wayland_structs();
+ void delete_xdg_structs();
+
+ void opengl_cleanup();
+ void xdg_setup();
+ void setup_listeners();
+ void opengl_setup();
+ void load_framebuffer();
+};
+#endif
diff --git a/src/canvas/wayland/window/waylandshm.cpp b/src/canvas/wayland/window/waylandshm.cpp
new file mode 100644
index 0000000..1ee5994
--- /dev/null
+++ b/src/canvas/wayland/window/waylandshm.cpp
@@ -0,0 +1,186 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#include "waylandshm.hpp"
+#include "dimensions.hpp"
+#include "shm.hpp"
+#include "util.hpp"
+
+#include <fmt/format.h>
+
+constexpr int id_len = 10;
+
+constexpr struct xdg_surface_listener xdg_surface_listener = {
+ .configure = WaylandShmWindow::xdg_surface_configure,
+};
+
+constexpr struct wl_callback_listener frame_listener = {.done = WaylandShmWindow::wl_surface_frame_done};
+
+void WaylandShmWindow::xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
+{
+ xdg_surface_ack_configure(xdg_surface, serial);
+ const auto *tmp = static_cast<struct XdgStruct *>(data);
+ const auto window = tmp->ptr.lock();
+ if (!window) {
+ return;
+ }
+ auto *shm_window = dynamic_cast<WaylandShmWindow *>(window.get());
+ shm_window->wl_draw(shm_window->output_scale);
+}
+
+void WaylandShmWindow::wl_surface_frame_done(void *data, struct wl_callback *callback, [[maybe_unused]] uint32_t time)
+{
+ wl_callback_destroy(callback);
+ const auto *tmp = static_cast<struct XdgStruct *>(data);
+ const auto window = tmp->ptr.lock();
+ if (!window) {
+ return;
+ }
+ auto *shm_window = dynamic_cast<WaylandShmWindow *>(window.get());
+ const std::scoped_lock lock{shm_window->draw_mutex};
+ if (!shm_window->visible) {
+ return;
+ }
+ shm_window->generate_frame();
+}
+
+WaylandShmWindow::WaylandShmWindow(WaylandCanvas *canvas, std::unique_ptr<Image> new_image,
+ struct XdgStructAgg *xdg_agg, WaylandConfig *config)
+ : config(config),
+ xdg_base(canvas->xdg_base),
+ surface(wl_compositor_create_surface(canvas->compositor)),
+ xdg_surface(xdg_wm_base_get_xdg_surface(xdg_base, surface)),
+ xdg_toplevel(xdg_surface_get_toplevel(xdg_surface)),
+ image(std::move(new_image)),
+ appid(fmt::format("ueberzugpp_{}", util::generate_random_string(id_len))),
+ xdg_agg(xdg_agg)
+{
+ config->initial_setup(appid);
+ xdg_setup();
+ output_scale = canvas->output_info.at(config->get_focused_output_name());
+ shm = std::make_unique<WaylandShm>(image->width(), image->height(), output_scale, canvas->wl_shm);
+}
+
+void WaylandShmWindow::finish_init()
+{
+ auto xdg = std::make_unique<XdgStruct>();
+ xdg->ptr = weak_from_this();
+ this_ptr = xdg.get();
+ xdg_agg->ptrs.push_back(std::move(xdg));
+
+ setup_listeners();
+ visible = true;
+}
+
+void WaylandShmWindow::setup_listeners()
+{
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, this_ptr);
+ wl_surface_commit(surface);
+
+ if (image->is_animated()) {
+ callback = wl_surface_frame(surface);
+ wl_callback_add_listener(callback, &frame_listener, this_ptr);
+ }
+}
+
+void WaylandShmWindow::xdg_setup()
+{
+ xdg_toplevel_set_app_id(xdg_toplevel, appid.c_str());
+ xdg_toplevel_set_title(xdg_toplevel, appid.c_str());
+}
+
+WaylandShmWindow::~WaylandShmWindow()
+{
+ delete_xdg_structs();
+ delete_wayland_structs();
+}
+
+void WaylandShmWindow::wl_draw(int32_t scale_factor)
+{
+ std::memcpy(shm->pool_data, image->data(), image->size());
+ wl_surface_attach(surface, shm->buffer, 0, 0);
+ wl_surface_set_buffer_scale(surface, scale_factor);
+ wl_surface_commit(surface);
+ move_window();
+}
+
+void WaylandShmWindow::show()
+{
+ if (visible) {
+ return;
+ }
+ visible = true;
+ xdg_surface = xdg_wm_base_get_xdg_surface(xdg_base, surface);
+ xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+ xdg_setup();
+ setup_listeners();
+}
+
+void WaylandShmWindow::hide()
+{
+ if (!visible) {
+ return;
+ }
+ visible = false;
+ const std::scoped_lock lock{draw_mutex};
+ delete_xdg_structs();
+ wl_surface_attach(surface, nullptr, 0, 0);
+ wl_surface_commit(surface);
+}
+
+void WaylandShmWindow::delete_xdg_structs()
+{
+ if (xdg_toplevel != nullptr) {
+ xdg_toplevel_destroy(xdg_toplevel);
+ xdg_toplevel = nullptr;
+ }
+ if (xdg_surface != nullptr) {
+ xdg_surface_destroy(xdg_surface);
+ xdg_surface = nullptr;
+ }
+}
+
+void WaylandShmWindow::delete_wayland_structs()
+{
+ if (surface != nullptr) {
+ wl_surface_destroy(surface);
+ surface = nullptr;
+ }
+}
+
+void WaylandShmWindow::move_window()
+{
+ const auto dims = image->dimensions();
+ const auto cur_window = config->get_window_info();
+ const int wayland_x = dims.xpixels() + dims.padding_horizontal;
+ const int wayland_y = dims.ypixels() + dims.padding_vertical;
+ const int xcoord = cur_window.x + wayland_x;
+ const int ycoord = cur_window.y + wayland_y;
+ config->move_window(appid, xcoord, ycoord);
+}
+
+void WaylandShmWindow::generate_frame()
+{
+ std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
+ callback = wl_surface_frame(surface);
+ wl_callback_add_listener(callback, &frame_listener, this_ptr);
+
+ image->next_frame();
+ std::memcpy(shm->pool_data, image->data(), image->size());
+ wl_surface_attach(surface, shm->buffer, 0, 0);
+ wl_surface_damage_buffer(surface, 0, 0, image->width(), image->height());
+ wl_surface_commit(surface);
+}
diff --git a/src/canvas/wayland/window/waylandshm.hpp b/src/canvas/wayland/window/waylandshm.hpp
new file mode 100644
index 0000000..f275c4c
--- /dev/null
+++ b/src/canvas/wayland/window/waylandshm.hpp
@@ -0,0 +1,77 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYLAND_SHM_WINDOW_H
+#define WAYLAND_SHM_WINDOW_H
+
+#include "../wayland.hpp"
+#include "image.hpp"
+#include "shm.hpp"
+#include "wayland-xdg-shell-client-protocol.h"
+#include "waylandwindow.hpp"
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <wayland-client.h>
+
+class WaylandShmWindow : public WaylandWindow
+{
+ public:
+ WaylandShmWindow(WaylandCanvas *canvas, std::unique_ptr<Image> new_image, struct XdgStructAgg *xdg_agg,
+ WaylandConfig *config);
+ ~WaylandShmWindow() override;
+ static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial);
+ static void wl_surface_frame_done(void *data, struct wl_callback *callback, uint32_t time);
+
+ void draw() override {}
+ void wl_draw(int32_t scale_factor) override;
+ void generate_frame() override;
+ void show() override;
+ void hide() override;
+
+ void finish_init() override;
+
+ std::mutex draw_mutex;
+ std::atomic<bool> visible{false};
+ std::unique_ptr<WaylandShm> shm;
+ int32_t output_scale;
+
+ private:
+ WaylandConfig *config;
+
+ struct xdg_wm_base *xdg_base = nullptr;
+ struct wl_surface *surface = nullptr;
+ struct xdg_surface *xdg_surface = nullptr;
+ struct xdg_toplevel *xdg_toplevel = nullptr;
+ struct wl_callback *callback;
+
+ std::unique_ptr<Image> image;
+ std::string appid;
+
+ struct XdgStructAgg *xdg_agg;
+ void *this_ptr;
+
+ void move_window();
+ void xdg_setup();
+
+ void setup_listeners();
+ void delete_wayland_structs();
+ void delete_xdg_structs();
+};
+
+#endif
diff --git a/src/canvas/wayland/window/waylandwindow.hpp b/src/canvas/wayland/window/waylandwindow.hpp
new file mode 100644
index 0000000..0a1a1e8
--- /dev/null
+++ b/src/canvas/wayland/window/waylandwindow.hpp
@@ -0,0 +1,46 @@
+// Display images inside a terminal
+// Copyright (C) 2023 JustKidding
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#ifndef WAYLAND_WINDOW_H
+#define WAYLAND_WINDOW_H
+
+#include "window.hpp"
+
+#include <memory>
+#include <vector>
+
+class WaylandWindow:
+ public Window,
+ public std::enable_shared_from_this<WaylandWindow>
+{
+public:
+ ~WaylandWindow() override = default;
+
+ virtual void wl_draw([[maybe_unused]] int32_t scale_factor) {};
+ virtual void finish_init() = 0;
+};
+
+struct XdgStruct
+{
+ std::weak_ptr<WaylandWindow> ptr;
+};
+
+struct XdgStructAgg
+{
+ std::vector<std::unique_ptr<XdgStruct>> ptrs;
+};
+
+#endif