// Display images inside a terminal
// Copyright (C) 2023 JustKidding
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#include "sway.hpp"
#include "application.hpp"
#include "os.hpp"
#include "tmux.hpp"
#include "util.hpp"
#include
#include
#include
#include
#include
#include
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 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() - output_info.x,
.y = rect.at("y").get() - output_info.y};
}
auto SwaySocket::get_active_window(const std::vector& nodes) -> nlohmann::json
{
const auto pids = tmux::get_client_pids().value_or(std::vector{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()) != 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
{
logger->debug("Obtaining sway tree");
const auto tree = ipc_message(IPC_GET_TREE);
std::stack nodes_st;
std::vector 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);
}