// 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 .
#include "waylandegl.hpp"
#include "dimensions.hpp"
#include "util.hpp"
#include
#include
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 *egl,
std::unique_ptr 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();
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(data);
const auto window = tmp->ptr.lock();
if (!window) {
return;
}
auto *egl_window = dynamic_cast(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(data);
const auto window = tmp->ptr.lock();
if (!window) {
return;
}
auto *egl_window = dynamic_cast(window.get());
const std::scoped_lock lock{egl_window->draw_mutex};
if (!egl_window->visible) {
return;
}
egl_window->generate_frame();
}