diff options
Diffstat (limited to 'src/canvas/wayland/config')
| -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 |
8 files changed, 630 insertions, 0 deletions
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 |
