summaryrefslogtreecommitdiff
path: root/src/canvas/wayland/window
diff options
context:
space:
mode:
Diffstat (limited to 'src/canvas/wayland/window')
-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
7 files changed, 754 insertions, 0 deletions
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