mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 06:00:04 +02:00
73 lines
2.9 KiB
C++
73 lines
2.9 KiB
C++
#include "companion_apps.hpp"
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
namespace frameyap {
|
|
namespace {
|
|
bool executable(const std::filesystem::path& path) {
|
|
struct stat info{};
|
|
return path.is_absolute() && ::stat(path.c_str(), &info) == 0 && S_ISREG(info.st_mode) &&
|
|
::access(path.c_str(), X_OK) == 0;
|
|
}
|
|
}
|
|
std::optional<std::filesystem::path> find_companion(std::string_view name) {
|
|
if (name != "tnkplan" && name != "tnkboard" && name != "tnkdraw") return {};
|
|
if (const char* home = std::getenv("HOME")) {
|
|
const auto launcher = std::filesystem::path(home) / ".local/bin" / name;
|
|
if (executable(launcher)) return launcher;
|
|
}
|
|
if (const char* path = std::getenv("PATH")) {
|
|
const std::string dirs(path);
|
|
for (size_t start = 0; start <= dirs.size();) {
|
|
const size_t end = dirs.find(':', start);
|
|
const auto launcher = std::filesystem::path(dirs.substr(start, end - start)) / name;
|
|
// Ignore empty/relative PATH entries: never execute from an untrusted cwd.
|
|
if (executable(launcher)) return launcher;
|
|
if (end == std::string::npos) break;
|
|
start = end + 1;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
bool launch_companion(const std::filesystem::path& launcher, CompanionCommand command) {
|
|
if (!executable(launcher)) return false;
|
|
const auto path = launcher.string();
|
|
const char* option = command == CompanionCommand::Show ? "--show" :
|
|
command == CompanionCommand::Recenter ? "--recenter" : nullptr;
|
|
char* const args[] = {const_cast<char*>(path.c_str()), const_cast<char*>(option), nullptr};
|
|
// Double fork so the long-lived companion is adopted rather than becoming
|
|
// a zombie owned by FrameYap. An O_CLOEXEC pipe reports exec failure only.
|
|
int pipefd[2];
|
|
if (::pipe2(pipefd, O_CLOEXEC) != 0) return false;
|
|
const pid_t intermediate = ::fork();
|
|
if (intermediate < 0) { ::close(pipefd[0]); ::close(pipefd[1]); return false; }
|
|
if (intermediate == 0) {
|
|
::close(pipefd[0]);
|
|
const pid_t child = ::fork();
|
|
if (child < 0) {
|
|
const int error = errno;
|
|
(void)::write(pipefd[1], &error, sizeof(error));
|
|
} else if (child == 0) {
|
|
::execv(path.c_str(), args);
|
|
const int error = errno;
|
|
(void)::write(pipefd[1], &error, sizeof(error));
|
|
}
|
|
::_exit(child < 0 ? 1 : 0);
|
|
}
|
|
::close(pipefd[1]);
|
|
int error = 0;
|
|
ssize_t count;
|
|
do { count = ::read(pipefd[0], &error, sizeof(error)); } while (count < 0 && errno == EINTR);
|
|
::close(pipefd[0]);
|
|
int status = 0;
|
|
pid_t reaped;
|
|
do { reaped = ::waitpid(intermediate, &status, 0); } while (reaped < 0 && errno == EINTR);
|
|
return count == 0 && reaped == intermediate && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
}
|
|
}
|