summaryrefslogtreecommitdiff
path: root/src/canvas/wayland/config/sway.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/canvas/wayland/config/sway.cpp')
-rw-r--r--src/canvas/wayland/config/sway.cpp183
1 files changed, 183 insertions, 0 deletions
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);
+}