summaryrefslogtreecommitdiff
path: root/start
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2025-12-05 15:40:03 +0000
committerGitHub <noreply@github.com>2025-12-05 15:40:03 +0000
commit016eb7a23db54cb33ed722f682c7171027a91945 (patch)
tree47e9a200d1048a2a4d6b10fe7eae0bee4822c0a5 /start
parentec6756f961b5b6b12a53ec0387632e4495529f28 (diff)
start: init start-hyprland and safe mode (#12484)
Diffstat (limited to 'start')
-rw-r--r--start/CMakeLists.txt24
-rw-r--r--start/src/core/Instance.cpp165
-rw-r--r--start/src/core/Instance.hpp36
-rw-r--r--start/src/core/State.hpp13
-rw-r--r--start/src/helpers/Logger.hpp9
-rw-r--r--start/src/helpers/Memory.hpp15
-rw-r--r--start/src/main.cpp87
7 files changed, 349 insertions, 0 deletions
diff --git a/start/CMakeLists.txt b/start/CMakeLists.txt
new file mode 100644
index 00000000..00b1fded
--- /dev/null
+++ b/start/CMakeLists.txt
@@ -0,0 +1,24 @@
+cmake_minimum_required(VERSION 3.19)
+
+project(start-hyprland DESCRIPTION "Hyprland watchdog binary")
+
+include(GNUInstallDirs)
+
+set(CMAKE_CXX_STANDARD 26)
+set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
+
+find_package(PkgConfig REQUIRED)
+
+pkg_check_modules(starthyprland_deps REQUIRED IMPORTED_TARGET hyprutils>=0.10.3)
+
+file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
+
+add_executable(start-hyprland ${SRCFILES})
+
+target_link_libraries(start-hyprland PUBLIC PkgConfig::starthyprland_deps)
+
+install(TARGETS start-hyprland)
+
+if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
+ set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
+endif()
diff --git a/start/src/core/Instance.cpp b/start/src/core/Instance.cpp
new file mode 100644
index 00000000..2ff53279
--- /dev/null
+++ b/start/src/core/Instance.cpp
@@ -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;
+} \ No newline at end of file
diff --git a/start/src/core/Instance.hpp b/start/src/core/Instance.hpp
new file mode 100644
index 00000000..2c72dc12
--- /dev/null
+++ b/start/src/core/Instance.hpp
@@ -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;
diff --git a/start/src/core/State.hpp b/start/src/core/State.hpp
new file mode 100644
index 00000000..6cf73a96
--- /dev/null
+++ b/start/src/core/State.hpp
@@ -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>(); \ No newline at end of file
diff --git a/start/src/helpers/Logger.hpp b/start/src/helpers/Logger.hpp
new file mode 100644
index 00000000..ae771203
--- /dev/null
+++ b/start/src/helpers/Logger.hpp
@@ -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;
diff --git a/start/src/helpers/Memory.hpp b/start/src/helpers/Memory.hpp
new file mode 100644
index 00000000..66ba2c1f
--- /dev/null
+++ b/start/src/helpers/Memory.hpp
@@ -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>;
diff --git a/start/src/main.cpp b/start/src/main.cpp
new file mode 100644
index 00000000..78fbf0f4
--- /dev/null
+++ b/start/src/main.cpp
@@ -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;
+} \ No newline at end of file