// 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 "waylandshm.hpp"
#include "dimensions.hpp"
#include "shm.hpp"
#include "util.hpp"
#include
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(data);
const auto window = tmp->ptr.lock();
if (!window) {
return;
}
auto *shm_window = dynamic_cast(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(data);
const auto window = tmp->ptr.lock();
if (!window) {
return;
}
auto *shm_window = dynamic_cast(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 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(image->width(), image->height(), output_scale, canvas->wl_shm);
}
void WaylandShmWindow::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 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);
}