diff options
Diffstat (limited to 'src/canvas/wayland')
| -rw-r--r-- | src/canvas/wayland/config.cpp | 43 | ||||
| -rw-r--r-- | src/canvas/wayland/config.hpp | 45 | ||||
| -rw-r--r-- | src/canvas/wayland/config/dummy.cpp | 29 | ||||
| -rw-r--r-- | src/canvas/wayland/config/dummy.hpp | 35 | ||||
| -rw-r--r-- | src/canvas/wayland/config/hyprland.cpp | 151 | ||||
| -rw-r--r-- | src/canvas/wayland/config/hyprland.hpp | 53 | ||||
| -rw-r--r-- | src/canvas/wayland/config/sway.cpp | 183 | ||||
| -rw-r--r-- | src/canvas/wayland/config/sway.hpp | 62 | ||||
| -rw-r--r-- | src/canvas/wayland/config/wayfire.cpp | 73 | ||||
| -rw-r--r-- | src/canvas/wayland/config/wayfire.hpp | 44 | ||||
| -rw-r--r-- | src/canvas/wayland/wayland.cpp | 222 | ||||
| -rw-r--r-- | src/canvas/wayland/wayland.hpp | 84 | ||||
| -rw-r--r-- | src/canvas/wayland/window/shm.cpp | 75 | ||||
| -rw-r--r-- | src/canvas/wayland/window/shm.hpp | 47 | ||||
| -rw-r--r-- | src/canvas/wayland/window/waylandegl.cpp | 235 | ||||
| -rw-r--r-- | src/canvas/wayland/window/waylandegl.hpp | 88 | ||||
| -rw-r--r-- | src/canvas/wayland/window/waylandshm.cpp | 186 | ||||
| -rw-r--r-- | src/canvas/wayland/window/waylandshm.hpp | 77 | ||||
| -rw-r--r-- | src/canvas/wayland/window/waylandwindow.hpp | 46 |
19 files changed, 1778 insertions, 0 deletions
diff --git a/src/canvas/wayland/config.cpp b/src/canvas/wayland/config.cpp new file mode 100644 index 0000000..4cd19e9 --- /dev/null +++ b/src/canvas/wayland/config.cpp @@ -0,0 +1,43 @@ +// 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 "config.hpp" +#include "os.hpp" +#include "config/sway.hpp" +#include "config/hyprland.hpp" +#include "config/wayfire.hpp" +#include "config/dummy.hpp" + +auto WaylandConfig::get() -> std::unique_ptr<WaylandConfig> +{ + const auto sway_sock = os::getenv("SWAYSOCK"); + if (sway_sock.has_value()) { + return std::make_unique<SwaySocket>(sway_sock.value()); + } + + const auto hypr_sig = os::getenv("HYPRLAND_INSTANCE_SIGNATURE"); + if (hypr_sig.has_value()) { + return std::make_unique<HyprlandSocket>(hypr_sig.value()); + } + + const auto wayfire_sock = os::getenv("WAYFIRE_SOCKET"); + if (wayfire_sock.has_value()) { + return std::make_unique<WayfireSocket>(wayfire_sock.value()); + } + + return std::make_unique<DummyWaylandConfig>(); +} + diff --git a/src/canvas/wayland/config.hpp b/src/canvas/wayland/config.hpp new file mode 100644 index 0000000..c84e669 --- /dev/null +++ b/src/canvas/wayland/config.hpp @@ -0,0 +1,45 @@ +// 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_CONFIG_H +#define WAYLAND_CONFIG_H + +#include <memory> +#include <string> +#include <string_view> + +struct WaylandWindowGeometry { + int width; + int height; + int x; + int y; +}; + +class WaylandConfig +{ + public: + static auto get() -> std::unique_ptr<WaylandConfig>; + + virtual ~WaylandConfig() = default; + + virtual auto get_focused_output_name() -> std::string = 0; + virtual auto get_window_info() -> struct WaylandWindowGeometry = 0; + virtual auto is_dummy() -> bool { return false; } + virtual void initial_setup(std::string_view appid) = 0; + virtual void move_window(std::string_view appid, int xcoord, int ycoord) = 0; +}; + +#endif diff --git a/src/canvas/wayland/config/dummy.cpp b/src/canvas/wayland/config/dummy.cpp new file mode 100644 index 0000000..d4b4297 --- /dev/null +++ b/src/canvas/wayland/config/dummy.cpp @@ -0,0 +1,29 @@ +// 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 "dummy.hpp" + +auto DummyWaylandConfig::get_window_info() -> struct WaylandWindowGeometry +{ + return {}; +} + +void DummyWaylandConfig::initial_setup([[maybe_unused]] std::string_view appid) +{} + +void DummyWaylandConfig::move_window([[maybe_unused]] std::string_view appid, + [[maybe_unused]] int xcoord, [[maybe_unused]] int ycoord) +{} diff --git a/src/canvas/wayland/config/dummy.hpp b/src/canvas/wayland/config/dummy.hpp new file mode 100644 index 0000000..60b35c1 --- /dev/null +++ b/src/canvas/wayland/config/dummy.hpp @@ -0,0 +1,35 @@ +// 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 DUMMY_WAYLAND_CONFIG_H +#define DUMMY_WAYLAND_CONFIG_H + +#include "../config.hpp" + +class DummyWaylandConfig : public WaylandConfig +{ + public: + DummyWaylandConfig() = default; + ~DummyWaylandConfig() override = default; + + auto get_focused_output_name() -> std::string override { return {}; }; + auto get_window_info() -> struct WaylandWindowGeometry override; + auto is_dummy() -> bool override { return true; } + void initial_setup(std::string_view appid) override; + void move_window(std::string_view appid, int xcoord, int ycoord) override; +}; + +#endif diff --git a/src/canvas/wayland/config/hyprland.cpp b/src/canvas/wayland/config/hyprland.cpp new file mode 100644 index 0000000..42387fb --- /dev/null +++ b/src/canvas/wayland/config/hyprland.cpp @@ -0,0 +1,151 @@ +// 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 "hyprland.hpp" +#include "os.hpp" +#include "tmux.hpp" +#include "util/socket.hpp" + +#include <filesystem> +#include <fmt/format.h> +#include <nlohmann/json.hpp> +#include <range/v3/all.hpp> + +using njson = nlohmann::json; +namespace fs = std::filesystem; + +HyprlandSocket::HyprlandSocket(const std::string_view signature) + : logger(spdlog::get("wayland")) +{ + const auto socket_base_dir = os::getenv("XDG_RUNTIME_DIR").value_or("/tmp"); + const auto socket_rel_path = fmt::format("hypr/{}/.socket.sock", signature); + socket_path = fmt::format("{}/{}", socket_base_dir, socket_rel_path); + // XDG_RUNTIME_DIR set but hyprland < 0.40 + if (!fs::exists(socket_path)) { + socket_path = fmt::format("/tmp/{}", socket_rel_path); + } + + logger->info("Using hyprland socket {}", socket_path); + const auto active = request_result("j/activewindow"); + address = active.at("address"); + set_active_monitor(); +} + +void HyprlandSocket::set_active_monitor() +{ + const auto monitors = request_result("j/monitors"); + for (const auto &monitor : monitors) { + bool focused = monitor.at("focused"); + if (focused) { + output_name = monitor.at("name"); + output_scale = monitor.at("scale"); + break; + } + } +} + +auto HyprlandSocket::get_focused_output_name() -> std::string +{ + return output_name; +} + +auto HyprlandSocket::request_result(const std::string_view payload) -> nlohmann::json +{ + const UnixSocket socket{socket_path}; + socket.write(payload.data(), payload.size()); + const std::string result = socket.read_until_empty(); + return njson::parse(result); +} + +void HyprlandSocket::request(const std::string_view payload) +{ + const UnixSocket socket{socket_path}; + logger->debug("Running socket command {}", payload); + socket.write(payload.data(), payload.length()); +} + +auto HyprlandSocket::get_active_window() -> nlohmann::json +{ + // recalculate address in case it changed + if (tmux::is_used()) { + const auto active = request_result("j/activewindow"); + address = active.at("address"); + } + const auto clients = request_result("j/clients"); + const auto client = ranges::find_if(clients, [this](const njson &json) { return json.at("address") == address; }); + if (client == clients.end()) { + throw std::runtime_error("Active window not found"); + } + return *client; +} + +auto HyprlandSocket::get_window_info() -> struct WaylandWindowGeometry { + const auto terminal = get_active_window(); + const auto &sizes = terminal.at("size"); + const auto &coords = terminal.at("at"); + + return { + .width = sizes.at(0), + .height = sizes.at(1), + .x = coords.at(0), + .y = coords.at(1), + }; +} + +void HyprlandSocket::initial_setup(const std::string_view appid) +{ + disable_focus(appid); + enable_floating(appid); + remove_borders(appid); + remove_rounding(appid); +} + +void HyprlandSocket::remove_rounding(const std::string_view appid) +{ + const auto payload = fmt::format("/keyword windowrulev2 rounding 0,title:{}", appid); + request(payload); +} + +void HyprlandSocket::disable_focus(const std::string_view appid) +{ + const auto payload = fmt::format("/keyword windowrulev2 nofocus,title:{}", appid); + request(payload); +} + +void HyprlandSocket::enable_floating(const std::string_view appid) +{ + const auto payload = fmt::format("/keyword windowrulev2 float,title:{}", appid); + request(payload); +} + +void HyprlandSocket::remove_borders(const std::string_view appid) +{ + const auto payload = fmt::format("/keyword windowrulev2 noborder,title:{}", appid); + request(payload); +} + +void HyprlandSocket::move_window(const std::string_view appid, int xcoord, int ycoord) +{ + int res_x = xcoord; + int res_y = ycoord; + if (output_scale > 1.0F) { + const int offset = 10; + res_x = res_x / 2 + offset; + res_y = res_y / 2 + offset; + } + const auto payload = fmt::format("/dispatch movewindowpixel exact {} {},title:{}", res_x, res_y, appid); + request(payload); +} diff --git a/src/canvas/wayland/config/hyprland.hpp b/src/canvas/wayland/config/hyprland.hpp new file mode 100644 index 0000000..4353d98 --- /dev/null +++ b/src/canvas/wayland/config/hyprland.hpp @@ -0,0 +1,53 @@ +// 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 HYPRLAND_SOCKET_H +#define HYPRLAND_SOCKET_H + +#include "../config.hpp" + +#include <nlohmann/json.hpp> +#include <spdlog/spdlog.h> + +class HyprlandSocket : public WaylandConfig +{ + public: + explicit HyprlandSocket(std::string_view signature); + ~HyprlandSocket() override = default; + + auto get_window_info() -> struct WaylandWindowGeometry override; + auto get_focused_output_name() -> std::string override; + void initial_setup(std::string_view appid) override; + void move_window(std::string_view appid, int xcoord, int ycoord) override; + + private: + void disable_focus(std::string_view appid); + void enable_floating(std::string_view appid); + void remove_borders(std::string_view appid); + void remove_rounding(std::string_view appid); + void request(std::string_view payload); + auto request_result(std::string_view payload) -> nlohmann::json; + auto get_active_window() -> nlohmann::json; + void set_active_monitor(); + + std::shared_ptr<spdlog::logger> logger; + std::string socket_path; + std::string address; + std::string output_name; + float output_scale = 1.0F; +}; + +#endif diff --git a/src/canvas/wayland/config/sway.cpp b/src/canvas/wayland/config/sway.cpp new file mode 100644 index 0000000..85bff0c --- /dev/null +++ b/src/canvas/wayland/config/sway.cpp @@ -0,0 +1,183 @@ +// 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 "sway.hpp" +#include "application.hpp" +#include "os.hpp" +#include "tmux.hpp" +#include "util.hpp" + +#include <algorithm> +#include <array> +#include <stack> +#include <string> + +#include <fmt/format.h> +#include <range/v3/all.hpp> + +using njson = nlohmann::json; + +constexpr auto ipc_magic = std::string_view{"i3-ipc"}; +constexpr auto ipc_header_size = ipc_magic.size() + 8; + +SwaySocket::SwaySocket(const std::string_view endpoint) + : socket(endpoint), + logger(spdlog::get("wayland")) +{ + logger->info("Using sway socket {}", endpoint); + set_active_output_info(); +} + +struct __attribute__((packed)) ipc_header { + std::array<char, ipc_magic.size()> magic; + uint32_t len; + uint32_t type; +}; + +auto SwaySocket::get_window_info() -> struct WaylandWindowGeometry { + const auto nodes = get_nodes(); + const auto window = get_active_window(nodes); + const auto &rect = window.at("rect"); + return {.width = rect.at("width"), + .height = rect.at("height"), + .x = rect.at("x").get<int>() - output_info.x, + .y = rect.at("y").get<int>() - output_info.y}; +} + +auto SwaySocket::get_active_window(const std::vector<nlohmann::json>& nodes) -> nlohmann::json +{ + const auto pids = tmux::get_client_pids().value_or(std::vector<int>{Application::parent_pid}); + + for (const auto pid : pids) { + const auto tree = util::get_process_tree(pid); + const auto found = ranges::find_if(nodes, [&tree](const njson &json) -> bool { + try { + return ranges::find(tree, json.at("pid").get<int>()) != tree.end(); + } catch (const njson::out_of_range &err) { + return false; + } + }); + if (found != nodes.end()) { + return *found; + } + } + return nullptr; +} + +auto SwaySocket::get_focused_output_name() -> std::string +{ + return output_info.name; +}; + +void SwaySocket::set_active_output_info() +{ + const auto outputs = ipc_message(IPC_GET_OUTPUTS); + for (const auto &node : outputs) { + bool focused = node.at("focused"); + if (focused) { + const auto &rect = node.at("rect"); + output_info = { + .x = rect.at("x"), + .y = rect.at("y"), + .scale = node.at("scale"), + .name = node.at("name"), + }; + break; + } + } +} + +void SwaySocket::initial_setup(const std::string_view appid) +{ + disable_focus(appid); + enable_floating(appid); +} + +void SwaySocket::disable_focus(const std::string_view appid) +{ + const auto payload = fmt::format(R"(no_focus [app_id="{}"])", appid); + ipc_command(payload); +} + +void SwaySocket::enable_floating(const std::string_view appid) +{ + ipc_command(appid, "floating enable"); +} + +void SwaySocket::move_window(const std::string_view appid, int xcoord, int ycoord) +{ + int res_x = xcoord; + int res_y = ycoord; + if (output_info.scale > 1.0F) { + res_x = res_x / 2; + res_y = res_y / 2; + } + const auto payload = fmt::format(R"([app_id="{}"] move position {} {})", appid, res_x, res_y); + ipc_command(payload); +} + +auto SwaySocket::ipc_message(ipc_message_type type, const std::string_view payload) const -> nlohmann::json +{ + struct ipc_header header; + header.len = payload.size(); + header.type = type; + ipc_magic.copy(header.magic.data(), ipc_magic.size()); + + if (!payload.empty()) { + logger->debug("Running socket command {}", payload); + } + socket.write(&header, ipc_header_size); + socket.write(payload.data(), payload.size()); + + socket.read(&header, ipc_header_size); + std::string buff(header.len, 0); + socket.read(buff.data(), buff.size()); + return njson::parse(buff); +} + +auto SwaySocket::get_nodes() const -> std::vector<nlohmann::json> +{ + logger->debug("Obtaining sway tree"); + const auto tree = ipc_message(IPC_GET_TREE); + std::stack<njson> nodes_st; + std::vector<njson> nodes_vec; + + nodes_st.push(tree); + + while (!nodes_st.empty()) { + const auto top = nodes_st.top(); + nodes_st.pop(); + nodes_vec.push_back(top); + for (const auto &node : top.at("nodes")) { + nodes_st.push(node); + } + for (const auto &node : top.at("floating_nodes")) { + nodes_st.push(node); + } + } + return nodes_vec; +} + +void SwaySocket::ipc_command(const std::string_view appid, const std::string_view command) const +{ + const auto payload = fmt::format(R"(for_window [app_id="{}"] {})", appid, command); + ipc_command(payload); +} + +void SwaySocket::ipc_command(const std::string_view payload) const +{ + std::ignore = ipc_message(IPC_COMMAND, payload); +} diff --git a/src/canvas/wayland/config/sway.hpp b/src/canvas/wayland/config/sway.hpp new file mode 100644 index 0000000..9ac4d69 --- /dev/null +++ b/src/canvas/wayland/config/sway.hpp @@ -0,0 +1,62 @@ +// 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 SWAY_SOCKET_H +#define SWAY_SOCKET_H + +#include "../config.hpp" +#include "util/socket.hpp" + +#include <nlohmann/json.hpp> +#include <spdlog/spdlog.h> + +enum ipc_message_type { IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4 }; + +struct SwayOutputInfo { + int x; + int y; + float scale; + std::string name; +}; + +class SwaySocket : public WaylandConfig +{ + public: + explicit SwaySocket(std::string_view endpoint); + ~SwaySocket() override = default; + + auto get_focused_output_name() -> std::string override; + auto get_window_info() -> struct WaylandWindowGeometry override; + void initial_setup(std::string_view appid) override; + void move_window(std::string_view appid, int xcoord, int ycoord) override; + static auto get_active_window(const std::vector<nlohmann::json> &nodes) -> nlohmann::json; + + private: + void disable_focus(std::string_view appid); + void enable_floating(std::string_view appid); + void ipc_command(std::string_view appid, std::string_view command) const; + void ipc_command(std::string_view payload) const; + void set_active_output_info(); + + [[nodiscard]] auto get_nodes() const -> std::vector<nlohmann::json>; + [[nodiscard]] auto ipc_message(ipc_message_type type, std::string_view payload = "") const -> nlohmann::json; + + UnixSocket socket; + std::shared_ptr<spdlog::logger> logger; + struct SwayOutputInfo output_info; +}; + +#endif diff --git a/src/canvas/wayland/config/wayfire.cpp b/src/canvas/wayland/config/wayfire.cpp new file mode 100644 index 0000000..efde546 --- /dev/null +++ b/src/canvas/wayland/config/wayfire.cpp @@ -0,0 +1,73 @@ +// 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 "wayfire.hpp" + +using njson = nlohmann::json; + +WayfireSocket::WayfireSocket(const std::string_view endpoint): +socket(endpoint), +logger(spdlog::get("wayland")) +{ + logger->info("Using wayfire socket {}", endpoint); +} + +auto WayfireSocket::get_window_info() -> struct WaylandWindowGeometry +{ + const auto response = request("window-rules/get-focused-view"); + const auto& info = response.at("info"); + const auto& geometry = info.at("geometry"); + const int decoration_height = 25; + return { + .width = geometry.at("width"), + .height = geometry.at("height").get<int>() - decoration_height, + .x = 0, + .y = 0 + }; +} + +void WayfireSocket::initial_setup([[maybe_unused]] const std::string_view appid) +{ + // all is handled by the ueberzug plugin +} + +void WayfireSocket::move_window(const std::string_view appid, int xcoord, int ycoord) +{ + const njson payload_data = { + {"app-id", appid}, + {"x", xcoord}, + {"y", ycoord} + }; + std::ignore = request("ueberzugpp/set_offset", payload_data); +} + +auto WayfireSocket::request(const std::string_view method, const njson& data) const -> njson +{ + const njson json = { + {"method", method}, + {"data", data} + }; + const auto payload = json.dump(); + const uint32_t payload_size = payload.length(); + socket.write(&payload_size, sizeof(uint32_t)); + socket.write(payload.c_str(), payload_size); + + uint32_t response_size = 0; + socket.read(&response_size, sizeof(uint32_t)); + std::string buffer (response_size, 0); + socket.read(buffer.data(), response_size); + return njson::parse(buffer); +} diff --git a/src/canvas/wayland/config/wayfire.hpp b/src/canvas/wayland/config/wayfire.hpp new file mode 100644 index 0000000..e1e83eb --- /dev/null +++ b/src/canvas/wayland/config/wayfire.hpp @@ -0,0 +1,44 @@ +// 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 WAYFIRE_SOCKET_H +#define WAYFIRE_SOCKET_H + +#include "../config.hpp" +#include "util/socket.hpp" + +#include <nlohmann/json.hpp> +#include <spdlog/spdlog.h> + +class WayfireSocket : public WaylandConfig +{ + public: + explicit WayfireSocket(std::string_view endpoint); + ~WayfireSocket() override = default; + + auto get_window_info() -> struct WaylandWindowGeometry override; + auto get_focused_output_name() -> std::string override { return {}; }; + void initial_setup(std::string_view appid) override; + void move_window(std::string_view appid, int xcoord, int ycoord) override; + + private: + [[nodiscard]] auto request(std::string_view method, const nlohmann::json &data = {}) const -> nlohmann::json; + + UnixSocket socket; + std::shared_ptr<spdlog::logger> logger; +}; + +#endif 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); +} diff --git a/src/canvas/wayland/wayland.hpp b/src/canvas/wayland/wayland.hpp new file mode 100644 index 0000000..e2016df --- /dev/null +++ b/src/canvas/wayland/wayland.hpp @@ -0,0 +1,84 @@ +// 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_CANVAS_H +#define WAYLAND_CANVAS_H + +#include "canvas.hpp" +#include "config.hpp" +#include "flags.hpp" +#include "wayland-xdg-shell-client-protocol.h" +#include "window/waylandwindow.hpp" + +#include <memory> +#include <thread> +#include <unordered_map> + +#include <spdlog/spdlog.h> +#include <wayland-client.h> + +#ifdef ENABLE_OPENGL +# include "util/egl.hpp" +# include <wayland-egl.h> +#endif + +class WaylandCanvas : public Canvas +{ + public: + explicit WaylandCanvas(); + ~WaylandCanvas() override; + + static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, + uint32_t version); + static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial); + + static void output_scale(void *data, struct wl_output *output, int32_t scale); + static void output_name(void *data, struct wl_output *output, const char *name); + static void output_done(void *data, struct wl_output *output); + + void add_image(const std::string &identifier, std::unique_ptr<Image> new_image) override; + void remove_image(const std::string &identifier) override; + void show() override; + void hide() override; + + struct wl_compositor *compositor = nullptr; + struct wl_shm *wl_shm = nullptr; + struct xdg_wm_base *xdg_base = nullptr; + + std::pair<std::string, int32_t> output_pair; + std::unordered_map<std::string, int32_t> output_info; + + private: + struct wl_display *display = nullptr; + struct wl_registry *registry = nullptr; + std::thread event_handler; + + std::shared_ptr<spdlog::logger> logger; + std::unique_ptr<WaylandConfig> config; + std::shared_ptr<Flags> flags; + std::unordered_map<std::string, std::shared_ptr<WaylandWindow>> windows; + +#ifdef ENABLE_OPENGL + std::unique_ptr<EGLUtil<struct wl_display, struct wl_egl_window>> egl; + bool egl_available = true; +#endif + + struct XdgStructAgg xdg_agg; + + void handle_events(); +}; + +#endif 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 |
