start: init start-hyprland and safe mode (#12484)

This commit is contained in:
Vaxry 2025-12-05 15:40:03 +00:00 committed by GitHub
parent ec6756f961
commit 016eb7a23d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 550 additions and 28 deletions

165
start/src/core/Instance.cpp Normal file
View file

@ -0,0 +1,165 @@
#include "Instance.hpp"
#include "State.hpp"
#include "../helpers/Logger.hpp"
#include <cstdlib>
#include <cstring>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <ranges>
#include <string_view>
#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::OS;
using namespace std::string_literals;
//
void CHyprlandInstance::runHyprlandThread(bool safeMode) {
std::vector<std::string> argsStd;
argsStd.emplace_back("--watchdog-fd");
argsStd.emplace_back(std::format("{}", m_toHlPid.get()));
if (safeMode)
argsStd.emplace_back("--safe-mode");
for (const auto& a : g_state->rawArgvNoBinPath) {
argsStd.emplace_back(a);
}
// spawn a process manually. Hyprutils' Async is detached, while Sync redirects stdout
// TODO: make Sync respect fds?
std::vector<char*> args = {strdup(g_state->customPath.value_or("Hyprland").c_str())};
for (const auto& a : argsStd) {
args.emplace_back(strdup(a.c_str()));
}
args.emplace_back(nullptr);
int forkRet = fork();
if (forkRet == 0) {
// Make hyprland die on our SIGKILL
prctl(PR_SET_PDEATHSIG, SIGKILL);
execvp(g_state->customPath.value_or("Hyprland").c_str(), args.data());
g_logger->log(Hyprutils::CLI::LOG_ERR, "fork(): execvp failed: {}", strerror(errno));
std::fflush(stdout);
exit(1);
} else
m_hlPid = forkRet;
m_hlThread = std::thread([this] {
while (true) {
int status = 0;
int ret = waitpid(m_hlPid, &status, 0);
if (ret == -1) {
g_logger->log(Hyprutils::CLI::LOG_ERR, "Couldn't waitpid for hyprland: {}", strerror(errno));
break;
}
if (WIFEXITED(status))
break;
}
write(m_wakeupWrite.get(), "vax", 3);
std::fflush(stdout);
std::fflush(stderr);
});
}
void CHyprlandInstance::forceQuit() {
m_hyprlandExiting = true;
kill(m_hlPid, SIGTERM); // gracefully, can get stuck but it's unlikely
}
void CHyprlandInstance::clearFd(const Hyprutils::OS::CFileDescriptor& fd) {
static std::array<char, 1024> buf;
read(fd.get(), buf.data(), 1023);
}
void CHyprlandInstance::dispatchHyprlandEvent() {
std::string recvd = "";
static std::array<char, 4096> buf;
ssize_t n = read(m_fromHlPid.get(), buf.data(), 4096);
if (n < 0) {
g_logger->log(Hyprutils::CLI::LOG_ERR, "Failed dispatching hl events");
return;
}
recvd.append(buf.data(), n);
if (recvd.empty())
return;
for (const auto& s : std::views::split(recvd, '\n')) {
const std::string_view sv = std::string_view{s};
if (sv == "vax") {
// init passed
m_hyprlandInitialized = true;
continue;
}
if (sv == "end") {
// exiting
m_hyprlandExiting = true;
continue;
}
}
}
bool CHyprlandInstance::run(bool safeMode) {
int pipefds[2];
pipe(pipefds);
m_fromHlPid = CFileDescriptor{pipefds[0]};
m_toHlPid = CFileDescriptor{pipefds[1]};
pipe(pipefds);
m_wakeupRead = CFileDescriptor{pipefds[0]};
m_wakeupWrite = CFileDescriptor{pipefds[1]};
runHyprlandThread(safeMode);
pollfd pollfds[2] = {
{
.fd = m_wakeupRead.get(),
.events = POLLIN,
.revents = 0,
},
{
.fd = m_fromHlPid.get(),
.events = POLLIN,
.revents = 0,
},
};
while (true) {
int ret = poll(pollfds, 2, -1);
if (ret < 0) {
g_logger->log(Hyprutils::CLI::LOG_ERR, "poll() failed, exiting");
exit(1);
}
if (pollfds[1].revents & POLLIN) {
g_logger->log(Hyprutils::CLI::LOG_DEBUG, "got an event from hyprland");
dispatchHyprlandEvent();
continue;
}
if (pollfds[0].revents & POLLIN) {
g_logger->log(Hyprutils::CLI::LOG_DEBUG, "hyprland exit, breaking poll, checking state");
clearFd(m_wakeupRead);
break;
}
}
m_hlThread.join();
return !m_hyprlandInitialized || m_hyprlandExiting;
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <hyprutils/os/FileDescriptor.hpp>
#include <thread>
#include "../helpers/Memory.hpp"
class CHyprlandInstance {
public:
CHyprlandInstance() = default;
~CHyprlandInstance() = default;
CHyprlandInstance(const CHyprlandInstance&) = delete;
CHyprlandInstance(CHyprlandInstance&) = delete;
CHyprlandInstance(CHyprlandInstance&&) = delete;
bool run(bool safeMode = false); // if returns false, restart.
void forceQuit();
private:
void runHyprlandThread(bool safeMode);
void clearFd(const Hyprutils::OS::CFileDescriptor& fd);
void dispatchHyprlandEvent();
int m_hlPid = -1;
Hyprutils::OS::CFileDescriptor m_fromHlPid, m_toHlPid;
Hyprutils::OS::CFileDescriptor m_wakeupRead, m_wakeupWrite;
bool m_hyprlandInitialized = false;
bool m_hyprlandExiting = false;
std::thread m_hlThread;
};
inline UP<CHyprlandInstance> g_instance;

13
start/src/core/State.hpp Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include "../helpers/Memory.hpp"
#include <span>
#include <optional>
struct SState {
std::span<const char*> rawArgvNoBinPath;
std::optional<std::string> customPath;
};
inline UP<SState> g_state = makeUnique<SState>();

View file

@ -0,0 +1,9 @@
#pragma once
#include <hyprutils/cli/Logger.hpp>
#include "Memory.hpp"
// we do this to add a from start-hyprland to the logs
inline UP<Hyprutils::CLI::CLogger> g_loggerMain = makeUnique<Hyprutils::CLI::CLogger>();
inline UP<Hyprutils::CLI::CLoggerConnection> g_logger;

View file

@ -0,0 +1,15 @@
#pragma once
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
using namespace Hyprutils::Memory;
template <typename T>
using SP = Hyprutils::Memory::CSharedPointer<T>;
template <typename T>
using WP = Hyprutils::Memory::CWeakPointer<T>;
template <typename T>
using UP = Hyprutils::Memory::CUniquePointer<T>;
template <typename T>
using ASP = Hyprutils::Memory::CAtomicSharedPointer<T>;

87
start/src/main.cpp Normal file
View file

@ -0,0 +1,87 @@
#include <csignal>
#include <print>
#include "helpers/Logger.hpp"
#include "core/State.hpp"
#include "core/Instance.hpp"
using namespace Hyprutils::CLI;
#define ASSERT(expr) \
if (!(expr)) { \
g_logger->log(LOG_CRIT, "Failed assertion at line {} in {}: {} was false", __LINE__, \
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find("/src/") + 1); })(), #expr); \
std::abort(); \
}
constexpr const char* HELP_INFO = R"#(start-hyprland - A binary to properly start Hyprland via a watchdog process.
Any arguments after -- are passed to Hyprland. For Hyprland help, run start-hyprland -- --help or Hyprland --help)#";
//
static void onSignal(int sig) {
if (!g_instance)
return;
g_instance->forceQuit();
g_instance.reset();
exit(0);
}
int main(int argc, const char** argv, const char** envp) {
g_logger = makeUnique<Hyprutils::CLI::CLoggerConnection>(*g_loggerMain);
g_logger->setName("start-hyprland");
g_logger->setLogLevel(Hyprutils::CLI::LOG_DEBUG);
signal(SIGTERM, ::onSignal);
signal(SIGINT, ::onSignal);
signal(SIGKILL, ::onSignal);
int startArgv = -1;
for (int i = 1; i < argc; ++i) {
std::string_view arg = argv[i];
if (arg == "--") {
startArgv = i + 1;
break;
}
if (arg == "-h" || arg == "--help") {
std::println("{}", HELP_INFO);
return 0;
}
if (arg == "--path" || arg == "-p") {
if (i + 1 >= argc) {
std::println("{} requires a path", arg);
return 1;
}
g_state->customPath = argv[++i];
continue;
}
}
if (startArgv != -1)
g_state->rawArgvNoBinPath = std::span<const char*>{argv + startArgv, argc - startArgv};
if (!g_state->rawArgvNoBinPath.empty())
g_logger->log(Hyprutils::CLI::LOG_WARN, "Arguments after -- are passed to Hyprland");
bool safeMode = false;
while (true) {
g_instance = makeUnique<CHyprlandInstance>();
const bool RET = g_instance->run(safeMode);
g_instance.reset();
if (!RET) {
g_logger->log(Hyprutils::CLI::LOG_ERR, "Hyprland exit not-cleanly, restarting");
safeMode = true;
continue;
}
g_logger->log(Hyprutils::CLI::LOG_DEBUG, "Hyprland exit cleanly.");
break;
}
return 0;
}