summaryrefslogtreecommitdiff
path: root/src
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 /src
parentec6756f961b5b6b12a53ec0387632e4495529f28 (diff)
start: init start-hyprland and safe mode (#12484)
Diffstat (limited to 'src')
-rw-r--r--src/Compositor.cpp62
-rw-r--r--src/Compositor.hpp34
-rw-r--r--src/config/ConfigDescriptions.hpp6
-rw-r--r--src/config/ConfigManager.cpp39
-rw-r--r--src/config/ConfigManager.hpp2
-rw-r--r--src/i18n/Engine.cpp35
-rw-r--r--src/i18n/Engine.hpp7
-rw-r--r--src/main.cpp24
-rw-r--r--src/plugins/PluginSystem.hpp1
9 files changed, 185 insertions, 25 deletions
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 39aaee37..49cfb561 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -142,6 +142,10 @@ static void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) {
Debug::log(aqLevelToHl(level), "[AQ] {}", msg);
}
+void CCompositor::setWatchdogFd(int fd) {
+ m_watchdogWriteFd = Hyprutils::OS::CFileDescriptor{fd};
+}
+
void CCompositor::bumpNofile() {
if (!getrlimit(RLIMIT_NOFILE, &m_originalNofile))
Debug::log(LOG, "Old rlimit: soft -> {}, hard -> {}", m_originalNofile.rlim_cur, m_originalNofile.rlim_max);
@@ -543,6 +547,9 @@ void CCompositor::cleanup() {
if (!m_wlDisplay)
return;
+ if (m_watchdogWriteFd.isValid())
+ write(m_watchdogWriteFd.get(), "end", 3);
+
signal(SIGABRT, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
@@ -796,6 +803,8 @@ void CCompositor::startCompositor() {
createLockFile();
EMIT_HOOK_EVENT("ready", nullptr);
+ if (m_watchdogWriteFd.isValid())
+ write(m_watchdogWriteFd.get(), "vax", 3);
// This blocks until we are done.
Debug::log(LOG, "Hyprland is ready, running the event loop!");
@@ -2459,6 +2468,7 @@ std::vector<PHLWORKSPACE> CCompositor::getWorkspacesCopy() {
void CCompositor::performUserChecks() {
static auto PNOCHECKXDG = CConfigValue<Hyprlang::INT>("misc:disable_xdg_env_checks");
static auto PNOCHECKGUIUTILS = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_guiutils_check");
+ static auto PNOWATCHDOG = CConfigValue<Hyprlang::INT>("misc:disable_watchdog_warning");
if (!*PNOCHECKXDG) {
const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP");
@@ -2470,15 +2480,63 @@ void CCompositor::performUserChecks() {
}
if (!*PNOCHECKGUIUTILS) {
- if (!NFsUtils::executableExistsInPath("hyprland-dialog")) {
+ if (!NFsUtils::executableExistsInPath("hyprland-dialog"))
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING);
- }
}
if (g_pHyprOpenGL->m_failedAssetsNo > 0) {
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprOpenGL->m_failedAssetsNo)}}),
CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR);
}
+
+ if (!m_watchdogWriteFd.isValid() && !*PNOWATCHDOG)
+ g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_WATCHDOG), CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_WARNING);
+
+ if (m_safeMode)
+ openSafeModeBox();
+}
+
+void CCompositor::openSafeModeBox() {
+ const auto OPT_LOAD = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG);
+ const auto OPT_OPEN = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR);
+ const auto OPT_OK = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD);
+
+ auto box = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_TITLE), I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_DESCRIPTION),
+ {
+ OPT_LOAD,
+ OPT_OPEN,
+ OPT_OK,
+ });
+
+ box->open()->then([OPT_LOAD, OPT_OK, OPT_OPEN, this](SP<CPromiseResult<std::string>> result) {
+ if (result->hasError())
+ return;
+
+ const auto RES = result->result();
+
+ if (RES.starts_with(OPT_LOAD)) {
+ m_safeMode = false;
+ g_pConfigManager->reload();
+ } else if (RES.starts_with(OPT_OPEN)) {
+ std::string reportPath;
+ const auto HOME = getenv("HOME");
+ const auto CACHE_HOME = getenv("XDG_CACHE_HOME");
+
+ if (CACHE_HOME && CACHE_HOME[0] != '\0') {
+ reportPath += CACHE_HOME;
+ reportPath += "/hyprland/";
+ } else if (HOME && HOME[0] != '\0') {
+ reportPath += HOME;
+ reportPath += "/.cache/hyprland/";
+ }
+ Hyprutils::OS::CProcess proc("xdg-open", {reportPath});
+
+ proc.runAsync();
+
+ // reopen
+ openSafeModeBox();
+ }
+ });
}
void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) {
diff --git a/src/Compositor.hpp b/src/Compositor.hpp
index 77627a84..af06059f 100644
--- a/src/Compositor.hpp
+++ b/src/Compositor.hpp
@@ -40,6 +40,7 @@ class CCompositor {
} m_drmRenderNode;
bool m_initialized = false;
+ bool m_safeMode = false;
SP<Aquamarine::CBackend> m_aqBackend;
std::string m_hyprTempDataRoot = "";
@@ -65,6 +66,7 @@ class CCompositor {
void cleanup();
void bumpNofile();
void restoreNofile();
+ void setWatchdogFd(int fd);
bool m_readyToProcess = false;
bool m_sessionActive = true;
@@ -167,21 +169,23 @@ class CCompositor {
std::string m_explicitConfigPath;
private:
- void initAllSignals();
- void removeAllSignals();
- void cleanEnvironment();
- void setRandomSplash();
- void initManagers(eManagersInitStage stage);
- void prepareFallbackOutput();
- void createLockFile();
- void removeLockFile();
- void setMallocThreshold();
-
- uint64_t m_hyprlandPID = 0;
- wl_event_source* m_critSigSource = nullptr;
- rlimit m_originalNofile = {};
-
- std::vector<PHLWORKSPACEREF> m_workspaces;
+ void initAllSignals();
+ void removeAllSignals();
+ void cleanEnvironment();
+ void setRandomSplash();
+ void initManagers(eManagersInitStage stage);
+ void prepareFallbackOutput();
+ void createLockFile();
+ void removeLockFile();
+ void setMallocThreshold();
+ void openSafeModeBox();
+
+ uint64_t m_hyprlandPID = 0;
+ wl_event_source* m_critSigSource = nullptr;
+ rlimit m_originalNofile = {};
+ Hyprutils::OS::CFileDescriptor m_watchdogWriteFd;
+
+ std::vector<PHLWORKSPACEREF> m_workspaces;
};
inline UP<CCompositor> g_pCompositor;
diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp
index a30b7b3d..85655dfd 100644
--- a/src/config/ConfigDescriptions.hpp
+++ b/src/config/ConfigDescriptions.hpp
@@ -1322,6 +1322,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
+ .value = "misc:disable_watchdog_warning",
+ .description = "whether to disable the warning about not using start-hyprland.",
+ .type = CONFIG_OPTION_BOOL,
+ .data = SConfigOptionDescription::SBoolData{false},
+ },
+ SConfigOptionDescription{
.value = "misc:lockdead_screen_delay",
.description = "the delay in ms after the lockdead screen appears if the lock screen did not appear after a lock event occurred.",
.type = CONFIG_OPTION_INT,
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index d39a33ee..bde4ebc0 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -498,6 +498,7 @@ CConfigManager::CConfigManager() {
registerConfigVar("misc:render_unfocused_fps", Hyprlang::INT{15});
registerConfigVar("misc:disable_xdg_env_checks", Hyprlang::INT{0});
registerConfigVar("misc:disable_hyprland_guiutils_check", Hyprlang::INT{0});
+ registerConfigVar("misc:disable_watchdog_warning", Hyprlang::INT{0});
registerConfigVar("misc:lockdead_screen_delay", Hyprlang::INT{1000});
registerConfigVar("misc:enable_anr_dialog", Hyprlang::INT{1});
registerConfigVar("misc:anr_missed_pings", Hyprlang::INT{5});
@@ -914,7 +915,7 @@ void CConfigManager::reloadRuleConfigs() {
}
}
-std::optional<std::string> CConfigManager::generateConfig(std::string configPath) {
+std::optional<std::string> CConfigManager::generateConfig(std::string configPath, bool safeMode) {
std::string parentPath = std::filesystem::path(configPath).parent_path();
if (!parentPath.empty()) {
@@ -931,7 +932,14 @@ std::optional<std::string> CConfigManager::generateConfig(std::string configPath
Debug::log(WARN, "No config file found; attempting to generate.");
std::ofstream ofs;
ofs.open(configPath, std::ios::trunc);
- ofs << AUTOGENERATED_PREFIX << EXAMPLE_CONFIG;
+ if (!safeMode) {
+ ofs << AUTOGENERATED_PREFIX;
+ ofs << EXAMPLE_CONFIG;
+ } else {
+ std::string n = std::string{EXAMPLE_CONFIG};
+ replaceInString(n, "\n$menu = hyprlauncher\n", "\n$menu = hyprland-run\n");
+ ofs << n;
+ }
ofs.close();
if (ofs.fail())
@@ -941,7 +949,16 @@ std::optional<std::string> CConfigManager::generateConfig(std::string configPath
}
std::string CConfigManager::getMainConfigPath() {
- static std::string CONFIG_PATH = [this]() -> std::string {
+ static bool lastSafeMode = g_pCompositor->m_safeMode;
+ static auto getCfgPath = [this]() -> std::string {
+ lastSafeMode = g_pCompositor->m_safeMode;
+ m_firstExecDispatched = false;
+
+ if (g_pCompositor->m_safeMode) {
+ const auto CONFIGPATH = g_pCompositor->m_instancePath + "/recoverycfg.conf";
+ return generateConfig(CONFIGPATH, false).value();
+ }
+
if (!g_pCompositor->m_explicitConfigPath.empty())
return g_pCompositor->m_explicitConfigPath;
@@ -956,7 +973,13 @@ std::string CConfigManager::getMainConfigPath() {
return generateConfig(CONFIGPATH).value();
} else
throw std::runtime_error("Neither HOME nor XDG_CONFIG_HOME are set in the environment. Could not find config in XDG_CONFIG_DIRS or /etc/xdg.");
- }();
+ };
+ static std::string CONFIG_PATH = getCfgPath();
+
+ if (lastSafeMode != g_pCompositor->m_safeMode) {
+ CONFIG_PATH = getCfgPath();
+ m_config->changeRootPath(CONFIG_PATH.c_str());
+ }
return CONFIG_PATH;
}
@@ -1415,7 +1438,6 @@ void CConfigManager::init() {
reload();
});
- const std::string CONFIGPATH = getMainConfigPath();
reload();
m_isFirstLaunch = false;
@@ -1637,7 +1659,12 @@ void CConfigManager::dispatchExecOnce() {
g_pInputManager->setTabletConfigs();
// check for user's possible errors with their setup and notify them if needed
- g_pCompositor->performUserChecks();
+ // this is additionally guarded because exiting safe mode will re-run this.
+ static bool once = true;
+ if (once) {
+ g_pCompositor->performUserChecks();
+ once = false;
+ }
}
void CConfigManager::dispatchExecShutdown() {
diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp
index a13547b5..1055e5f2 100644
--- a/src/config/ConfigManager.hpp
+++ b/src/config/ConfigManager.hpp
@@ -317,7 +317,7 @@ class CConfigManager {
// internal methods
void setDefaultAnimationVars();
std::optional<std::string> resetHLConfig();
- std::optional<std::string> generateConfig(std::string configPath);
+ std::optional<std::string> generateConfig(std::string configPath, bool safeMode = false);
std::optional<std::string> verifyConfigExists();
void reloadRuleConfigs();
diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp
index 62406df3..818fa75e 100644
--- a/src/i18n/Engine.cpp
+++ b/src/i18n/Engine.cpp
@@ -99,6 +99,18 @@ I18n::CI18nEngine::CI18nEngine() {
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Failed to load plugin {name}: {error}");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader reload failed, falling back to rgba/rgbx.");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut is enabled but the display is not in 10-bit mode.");
+ huEngine->registerEntry("en_US", TXT_KEY_NOTIF_NO_WATCHDOG,
+ "Hyprland was started without start-hyprland. This is highly not recommended unless you are in a debugging environment.");
+
+ huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_TITLE, "Safe Mode");
+ huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_DESCRIPTION,
+ "Hyprland has been launched in safe mode, which means your last session crashed.\nSafe mode prevents your config from being loaded. You can "
+ "troubleshoot in this environment, or load your config with the button below.\nDefault keybinds apply: SUPER+Q for kitty, SUPER+R for a basic runner, "
+ "SUPER+M to exit.\nRestarting "
+ "Hyprland will launch in normal mode again.");
+ huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Load config");
+ huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Open crash report directory");
+ huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ok, close this");
// as_IN (Assamese)
huEngine->registerEntry("as_IN", TXT_KEY_ANR_TITLE, "এপ্লিকেচনে উত্তৰ দিয়া নাই");
@@ -619,6 +631,17 @@ I18n::CI18nEngine::CI18nEngine() {
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "プラグイン{name}のロード失敗: {error}");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}:広い色域は設定していますけど、画面は10ビットモードに設定されていません。");
+ huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprlandはstart-hyprlandなしで実行されました。これはデバグ環境以外でおすすめしません。");
+
+ huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_TITLE, "安全モード");
+ huEngine->registerEntry(
+ "ja_JP", TXT_KEY_SAFE_MODE_DESCRIPTION,
+ "Hyprlandは安全モードに実行しました。これは、Hyprlandはクラッシュしましたから。\n安全モードはコンフィグをロードしなくて、問題を修正できる環境です。下のボタンでコンフィグを"
+ "ロードできます。\nデフォルトなキーバインドがあります。SUPER+Qはkitty、SUPER+Rは簡素なランチャー、SUPER+"
+ "MはHyprlandから退出。\nHyprlandを再び実行すれば、普通モードで実行します。");
+ huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "コンフィグをロード");
+ huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "クラッシュレポートフォルダーを開く");
+ huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "分かりました、このウィンドウをクローズ");
// lv_LV (Latvian)
huEngine->registerEntry("lv_LV", TXT_KEY_ANR_TITLE, "Lietotne nereaģē");
@@ -903,6 +926,18 @@ I18n::CI18nEngine::CI18nEngine() {
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nie udało się załadować plugin'a {name}: {error}");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx.");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit.");
+ huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_NO_WATCHDOG,
+ "Hyprland został uruchomiony bez start-hyprland. Nie jest to zalecane, chyba, że jest to środowisko do debugowania.");
+
+ huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_TITLE, "Tryb Bezpieczny");
+ huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_DESCRIPTION,
+ "Hyprland został uruchomiony w trybie bezpiecznym, co oznacza, że twoja ostatnia sesja uległa awarii.\nTryb bezpieczny zapobiega ładowaniu twojej "
+ "konfiguracji. Możesz próbować rozwiązać"
+ "problem w tym środowisku, lub załadować swoją konfigurację przyciskiem poniżej.\nDomyślne skróty klawiszowe są dostępne: SUPER+Q uruchamia kitty, "
+ "SUPER+R otwiera podstawowy launcher, SUPER+M zamyka Hyprland.\nUruchomienie ponowne Hyprland'a uruchomi go w trybie normalnym.");
+ huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Załaduj konfigurację");
+ huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Otwórz folder z raportami awarii");
+ huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ok, zamknij to okno");
// pt_PT (Portuguese Portugal)
huEngine->registerEntry("pt_PT", TXT_KEY_ANR_TITLE, "A aplicação não está a responder");
diff --git a/src/i18n/Engine.hpp b/src/i18n/Engine.hpp
index d1182632..c3892546 100644
--- a/src/i18n/Engine.hpp
+++ b/src/i18n/Engine.hpp
@@ -36,6 +36,13 @@ namespace I18n {
TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN,
TXT_KEY_NOTIF_CM_RELOAD_FAILED,
TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B,
+ TXT_KEY_NOTIF_NO_WATCHDOG,
+
+ TXT_KEY_SAFE_MODE_TITLE,
+ TXT_KEY_SAFE_MODE_DESCRIPTION,
+ TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR,
+ TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG,
+ TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD,
};
class CI18nEngine {
diff --git a/src/main.cpp b/src/main.cpp
index 3e21c965..ed436934 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -68,7 +68,8 @@ int main(int argc, char** argv) {
std::string configPath;
std::string socketName;
int socketFd = -1;
- bool ignoreSudo = false, verifyConfig = false;
+ bool ignoreSudo = false, verifyConfig = false, safeMode = false;
+ int watchdogFd = -1;
if (argc > 1) {
std::span<char*> args{argv + 1, sc<std::size_t>(argc - 1)};
@@ -152,6 +153,23 @@ int main(int argc, char** argv) {
} else if (value == "--verify-config") {
verifyConfig = true;
continue;
+ } else if (value == "--safe-mode") {
+ safeMode = true;
+ continue;
+ } else if (value == "--watchdog-fd") {
+ if (std::next(it) == args.end()) {
+ help();
+ return 1;
+ }
+
+ try {
+ watchdogFd = std::stoi(*std::next(it));
+ it++;
+ } catch (...) {
+ std::println(stderr, "[ ERROR ] Invalid fd for watchdog fd");
+ help();
+ return 1;
+ }
} else {
std::println(stderr, "[ ERROR ] Unknown option '{}' !", value);
help();
@@ -193,6 +211,10 @@ int main(int argc, char** argv) {
reapZombieChildrenAutomatically();
+ if (watchdogFd > 0)
+ g_pCompositor->setWatchdogFd(watchdogFd);
+ if (safeMode)
+ g_pCompositor->m_safeMode = true;
g_pCompositor->initServer(socketName, socketFd);
if (verifyConfig)
diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp
index 7d062a9b..ed421960 100644
--- a/src/plugins/PluginSystem.hpp
+++ b/src/plugins/PluginSystem.hpp
@@ -2,6 +2,7 @@
#include "../defines.hpp"
#include "../helpers/defer/Promise.hpp"
+#include "../helpers/time/Timer.hpp"
#include "PluginAPI.hpp"
#include "../managers/permissions/DynamicPermissionManager.hpp"
#include <csetjmp>