// 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 "wayland.hpp" #include "application.hpp" #include "image.hpp" #include "os.hpp" #include "util.hpp" #ifdef ENABLE_OPENGL # include "window/waylandegl.hpp" # include #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(data); if (interface_str == wl_compositor_interface.name) { canvas->compositor = static_cast( wl_registry_bind(registry, name, &wl_compositor_interface, compositor_ver)); } else if (interface_str == wl_shm_interface.name) { canvas->wl_shm = static_cast(wl_registry_bind(registry, name, &wl_shm_interface, shm_ver)); } else if (interface_str == xdg_wm_base_interface.name) { canvas->xdg_base = static_cast(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(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(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(data); canvas->output_pair.second = scale; } void WaylandCanvas::output_done(void *data, [[maybe_unused]] struct wl_output *output) { auto *canvas = static_cast(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, ®istry_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>(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 new_image) { std::shared_ptr window; #ifdef ENABLE_OPENGL if (egl_available) { try { window = std::make_shared(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(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); }