diff options
| author | pants <niklasjo2004@gmail.com> | 2026-09-21 14:18:06 -0700 |
|---|---|---|
| committer | pants <niklasjo2004@gmail.com> | 2026-09-21 14:18:06 -0700 |
| commit | 83d5ba42f8a98fc0df177efef4333f74a0481f61 (patch) | |
| tree | 6f1fcff64ec9adb2fc51a7cfcc451eb22a103fb3 /src/canvas/wayland/wayland.cpp | |
Diffstat (limited to 'src/canvas/wayland/wayland.cpp')
| -rw-r--r-- | src/canvas/wayland/wayland.cpp | 222 |
1 files changed, 222 insertions, 0 deletions
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, ®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<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); +} |
