From 4cce7f60a9c8772d18ffb43cb74aa227ab4b3bc4 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:56:25 +0100 Subject: notifications: move and small refactor (#14094) moves to notification/, and adds a new style manager getter. --- src/Compositor.cpp | 22 +-- src/config/legacy/ConfigManager.cpp | 12 +- src/debug/HyprCtl.cpp | 6 +- src/debug/HyprNotificationOverlay.cpp | 247 ------------------------------ src/debug/HyprNotificationOverlay.hpp | 72 --------- src/helpers/Monitor.cpp | 11 +- src/notification/NotificationOverlay.cpp | 254 +++++++++++++++++++++++++++++++ src/notification/NotificationOverlay.hpp | 75 +++++++++ src/plugins/PluginAPI.cpp | 6 +- src/plugins/PluginSystem.cpp | 6 +- src/render/OpenGL.cpp | 2 +- src/render/Renderer.cpp | 14 +- 12 files changed, 368 insertions(+), 359 deletions(-) delete mode 100644 src/debug/HyprNotificationOverlay.cpp delete mode 100644 src/debug/HyprNotificationOverlay.hpp create mode 100644 src/notification/NotificationOverlay.cpp create mode 100644 src/notification/NotificationOverlay.hpp (limited to 'src') diff --git a/src/Compositor.cpp b/src/Compositor.cpp index d40f3ce8..87748109 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -73,7 +73,7 @@ #include "render/AsyncResourceGatherer.hpp" #include "plugins/PluginSystem.hpp" #include "hyprerror/HyprError.hpp" -#include "debug/HyprNotificationOverlay.hpp" +#include "notification/NotificationOverlay.hpp" #include "debug/HyprDebugOverlay.hpp" #include "helpers/MonitorFrameScheduler.hpp" #include "i18n/Engine.hpp" @@ -590,7 +590,7 @@ void CCompositor::cleanup() { g_pDecorationPositioner.reset(); g_pCursorManager.reset(); g_pPluginSystem.reset(); - g_pHyprNotificationOverlay.reset(); + Notification::overlay().reset(); g_pDebugOverlay.reset(); g_pEventManager.reset(); g_pSessionLockManager.reset(); @@ -697,8 +697,8 @@ void CCompositor::initManagers(eManagersInitStage stage) { Log::logger->log(Log::DEBUG, "Creating the HyprDebugOverlay!"); g_pDebugOverlay = makeUnique(); - Log::logger->log(Log::DEBUG, "Creating the HyprNotificationOverlay!"); - g_pHyprNotificationOverlay = makeUnique(); + Log::logger->log(Log::DEBUG, "Creating the NotificationOverlay!"); + Notification::overlay(); Log::logger->log(Log::DEBUG, "Creating the PluginSystem!"); g_pPluginSystem = makeUnique(); @@ -2573,7 +2573,7 @@ void CCompositor::performUserChecks() { if (!*PNOCHECKXDG) { const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP"); if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != "Hyprland") { - g_pHyprNotificationOverlay->addNotification( + Notification::overlay()->addNotification( I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, {{"value", CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"}}), CHyprColor{}, 15000, ICON_WARNING); } @@ -2581,16 +2581,16 @@ void CCompositor::performUserChecks() { if (!*PNOCHECKGUIUTILS) { if (!NFsUtils::executableExistsInPath("hyprland-dialog")) - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING); } if (g_pHyprRenderer->m_failedAssetsNo > 0) { - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprRenderer->m_failedAssetsNo)}}), - CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprRenderer->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); + Notification::overlay()->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(); @@ -2730,8 +2730,8 @@ void CCompositor::checkMonitorOverlaps() { for (const auto& m : m_monitors) { if (!monitorRegion.copy().intersect(m->logicalBox()).empty()) { Log::logger->log(Log::ERR, "Monitor {}: detected overlap with layout", m->m_name); - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000, - ICON_WARNING); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); break; } diff --git a/src/config/legacy/ConfigManager.cpp b/src/config/legacy/ConfigManager.cpp index ffc5cea5..c2bdeeae 100644 --- a/src/config/legacy/ConfigManager.cpp +++ b/src/config/legacy/ConfigManager.cpp @@ -34,7 +34,7 @@ #include "../../managers/eventLoop/EventLoopManager.hpp" #include "../../managers/EventManager.hpp" #include "../../managers/permissions/DynamicPermissionManager.hpp" -#include "../../debug/HyprNotificationOverlay.hpp" +#include "../../notification/NotificationOverlay.hpp" #include "../../plugins/PluginSystem.hpp" #include "../../managers/input/trackpad/TrackpadGestures.hpp" @@ -1360,8 +1360,8 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { // manual crash if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { m_manualCrashInitiated = true; - g_pHyprNotificationOverlay->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, - ICON_INFO); + Notification::overlay()->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, + ICON_INFO); } else if (m_manualCrashInitiated && !std::any_cast(m_config->getConfigValue("debug:manual_crash"))) { // cowabunga it is g_pHyprRenderer->initiateManualCrash(); @@ -1428,10 +1428,8 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: // manual crash if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { m_manualCrashInitiated = true; - if (g_pHyprNotificationOverlay) { - g_pHyprNotificationOverlay->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, - ICON_INFO); - } + Notification::overlay()->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, + ICON_INFO); } else if (m_manualCrashInitiated && !std::any_cast(m_config->getConfigValue("debug:manual_crash"))) { // cowabunga it is g_pHyprRenderer->initiateManualCrash(); diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 4772894f..a86c61a3 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -63,7 +63,7 @@ using namespace Hyprutils::OS; #include "../managers/XWaylandManager.hpp" #include "../plugins/PluginSystem.hpp" #include "../managers/animation/AnimationManager.hpp" -#include "../debug/HyprNotificationOverlay.hpp" +#include "../notification/NotificationOverlay.hpp" #include "../render/Renderer.hpp" #include "../render/OpenGL.hpp" #include "../layout/space/Space.hpp" @@ -2002,7 +2002,7 @@ static std::string dispatchNotify(eHyprCtlOutputFormat format, std::string reque const auto MESSAGE = vars.join(" ", msgidx); - g_pHyprNotificationOverlay->addNotification(MESSAGE, color, time, sc(icon), fontsize); + Notification::overlay()->addNotification(MESSAGE, color, time, sc(icon), fontsize); return "ok"; } @@ -2022,7 +2022,7 @@ static std::string dispatchDismissNotify(eHyprCtlOutputFormat format, std::strin } catch (std::exception& e) { return "invalid arg 1"; } } - g_pHyprNotificationOverlay->dismissNotifications(amount); + Notification::overlay()->dismissNotifications(amount); return "ok"; } diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp deleted file mode 100644 index 97835c0d..00000000 --- a/src/debug/HyprNotificationOverlay.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include -#include -#include -#include -#include "HyprNotificationOverlay.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" -#include "../render/pass/RectPassElement.hpp" -#include "../render/pass/TexPassElement.hpp" -#include "../event/EventBus.hpp" - -#include "../managers/animation/AnimationManager.hpp" -#include "../render/Renderer.hpp" - -static inline auto iconBackendFromLayout(PangoLayout* layout) { - // preference: Nerd > FontAwesome > text - auto eIconBackendChecks = std::array{ICONS_BACKEND_NF, ICONS_BACKEND_FA}; - for (auto iconID : eIconBackendChecks) { - auto iconsText = std::ranges::fold_left(ICONS_ARRAY[iconID], std::string(), std::plus<>()); - pango_layout_set_text(layout, iconsText.c_str(), -1); - if (pango_layout_get_unknown_glyphs_count(layout) == 0) - return iconID; - } - return ICONS_BACKEND_NONE; -} - -static constexpr auto ANIM_DURATION_MS = 600.F; -static constexpr auto ANIM_LAG_MS = 100.F; -static constexpr auto NOTIF_LEFTBAR_SIZE = 5.F; -static constexpr auto NOTIF_PAD_X = 20.F; -static constexpr auto NOTIF_PAD_Y = 10.F; -static constexpr auto NOTIF_OFFSET_Y = 10.F; -static constexpr auto NOTIF_GAP_Y = 10.F; -static constexpr auto NOTIF_DAMAGE_PAD_X = 20.F; -static constexpr auto ICON_PAD = 3.F; -static constexpr auto ICON_SCALE = 0.9F; - -CHyprNotificationOverlay::CHyprNotificationOverlay() { - static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { - if (m_notifications.empty()) - return; - - g_pHyprRenderer->damageBox(m_lastDamage); - }); -} - -CHyprNotificationOverlay::~CHyprNotificationOverlay() = default; - -eIconBackend CHyprNotificationOverlay::iconBackendForFont(const std::string& fontFamily) { - if (m_iconBackendValid && m_cachedIconBackendFontFamily == fontFamily) - return m_cachedIconBackend; - - auto* cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - auto* cairoCtx = cairo_create(cairoSurface); - auto* layout = pango_cairo_create_layout(cairoCtx); - auto* pangoFD = pango_font_description_new(); - - pango_font_description_set_family(pangoFD, fontFamily.c_str()); - pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); - pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); - pango_layout_set_font_description(layout, pangoFD); - - m_cachedIconBackend = iconBackendFromLayout(layout); - m_cachedIconBackendFontFamily = fontFamily; - m_iconBackendValid = true; - - pango_font_description_free(pangoFD); - g_object_unref(layout); - cairo_destroy(cairoCtx); - cairo_surface_destroy(cairoSurface); - - return m_cachedIconBackend; -} - -void CHyprNotificationOverlay::ensureNotificationCache(SNotification& notif, PHLMONITOR pMonitor, const std::string& fontFamily) { - const auto iconBackend = iconBackendForFont(fontFamily); - const auto fontSizePx = std::clamp(sc(notif.fontSize * ((pMonitor->m_pixelSize.x * pMonitor->m_scale) / 1920.f)), 8, 40); - - const bool cacheValid = notif.cache.monitor == pMonitor && notif.cache.fontFamily == fontFamily && notif.cache.fontSizePx == fontSizePx && - notif.cache.iconBackend == iconBackend && notif.cache.textTex && (notif.icon == ICON_NONE || notif.cache.iconTex); - - if (cacheValid) - return; - - notif.cache = {}; - notif.cache.monitor = pMonitor; - notif.cache.fontFamily = fontFamily; - notif.cache.fontSizePx = fontSizePx; - notif.cache.iconBackend = iconBackend; - - notif.cache.textTex = g_pHyprRenderer->renderText(notif.text, CHyprColor{1.F, 1.F, 1.F, 1.F}, fontSizePx, false, fontFamily); - if (notif.cache.textTex) - notif.cache.textSize = notif.cache.textTex->m_size; - - if (notif.icon != ICON_NONE) { - const auto iconGlyph = ICONS_ARRAY[iconBackend][notif.icon]; - const auto iconSizePx = std::max(8, sc(std::round(fontSizePx * ICON_SCALE))); - - notif.cache.iconTex = g_pHyprRenderer->renderText(iconGlyph, CHyprColor{1.F, 1.F, 1.F, 1.F}, iconSizePx, false, fontFamily); - if (notif.cache.iconTex) - notif.cache.iconSize = notif.cache.iconTex->m_size; - } -} - -void CHyprNotificationOverlay::addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon, const float fontSize) { - const auto PNOTIF = m_notifications.emplace_back(makeUnique()).get(); - - PNOTIF->text = text; - PNOTIF->color = color == CHyprColor(0) ? ICONS_COLORS[icon] : color; - PNOTIF->started.reset(); - PNOTIF->timeMs = timeMs; - PNOTIF->icon = icon; - PNOTIF->fontSize = fontSize; - - for (auto const& m : g_pCompositor->m_monitors) { - g_pCompositor->scheduleFrameForMonitor(m); - } -} - -void CHyprNotificationOverlay::dismissNotifications(const int amount) { - if (amount == -1) - m_notifications.clear(); - else { - const int AMT = std::min(amount, sc(m_notifications.size())); - - for (int i = 0; i < AMT; ++i) { - m_notifications.erase(m_notifications.begin()); - } - } - - for (auto const& m : g_pCompositor->m_monitors) { - g_pCompositor->scheduleFrameForMonitor(m); - } -} - -CBox CHyprNotificationOverlay::drawNotifications(PHLMONITOR pMonitor) { - float offsetY = NOTIF_OFFSET_Y; - float maxWidth = 0; - - const auto MONSIZE = pMonitor->m_transformedSize; - - static auto fontFamily = CConfigValue("misc:font_family"); - const auto PBEZIER = g_pAnimationManager->getBezier("default"); - - for (auto const& notif : m_notifications) { - ensureNotificationCache(*notif, pMonitor, *fontFamily); - - const auto ICONPADFORNOTIF = notif->icon == ICON_NONE ? 0.F : ICON_PAD; - const auto ICONW = notif->cache.iconSize.x; - const auto ICONH = notif->cache.iconSize.y; - const auto TEXTW = notif->cache.textSize.x; - const auto TEXTH = notif->cache.textSize.y; - const auto NOTIFSIZE = Vector2D{TEXTW + NOTIF_PAD_X + ICONW + 2 * ICONPADFORNOTIF, std::max(TEXTH, ICONH) + NOTIF_PAD_Y}; - - const float elapsed = notif->started.getMillis(); - const float lifeMs = std::max(notif->timeMs, 1.F); - - // first rect (bg, col) - const float FIRSTRECTANIMP = std::clamp( - (elapsed > (ANIM_DURATION_MS - ANIM_LAG_MS) ? (elapsed > lifeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? lifeMs - elapsed : (ANIM_DURATION_MS - ANIM_LAG_MS)) : elapsed) / - (ANIM_DURATION_MS - ANIM_LAG_MS), - 0.F, 1.F); - - const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP); - - // second rect (fg, black) - const float SECONDRECTANIMP = - std::clamp((elapsed > ANIM_DURATION_MS ? (elapsed > lifeMs - ANIM_DURATION_MS ? lifeMs - elapsed : ANIM_DURATION_MS) : elapsed) / ANIM_DURATION_MS, 0.F, 1.F); - - const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP); - - // third rect (horiz, col) - const float THIRDRECTPERC = std::clamp(elapsed / lifeMs, 0.F, 1.F); - - const float firstRectX = MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC; - const float firstRectW = (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC; - - const float secondRectX = MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC; - const float secondRectW = NOTIFSIZE.x * SECONDRECTPERC; - - CRectPassElement::SRectData bgData; - bgData.box = {firstRectX, offsetY, firstRectW, NOTIFSIZE.y}; - bgData.color = notif->color; - g_pHyprRenderer->m_renderPass.add(makeUnique(bgData)); - - CRectPassElement::SRectData fgData; - fgData.box = {secondRectX, offsetY, secondRectW, NOTIFSIZE.y}; - fgData.color = CHyprColor{0.F, 0.F, 0.F, 1.F}; - g_pHyprRenderer->m_renderPass.add(makeUnique(fgData)); - - CRectPassElement::SRectData progressData; - progressData.box = {secondRectX + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * std::max(0.0, NOTIFSIZE.x - 6.0), 2}; - progressData.color = notif->color; - g_pHyprRenderer->m_renderPass.add(makeUnique(progressData)); - - if (notif->icon != ICON_NONE && notif->cache.iconTex) { - CTexPassElement::SRenderData iconData; - iconData.tex = notif->cache.iconTex; - iconData.box = {secondRectX + NOTIF_LEFTBAR_SIZE + ICONPADFORNOTIF - 1, offsetY - 2 + std::round((NOTIFSIZE.y - ICONH) / 2.0), ICONW, ICONH}; - iconData.a = 1.F; - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(iconData))); - } - - if (notif->cache.textTex) { - CTexPassElement::SRenderData textData; - textData.tex = notif->cache.textTex; - textData.box = {secondRectX + NOTIF_LEFTBAR_SIZE + ICONW + 2 * ICONPADFORNOTIF, offsetY - 2 + std::round((NOTIFSIZE.y - TEXTH) / 2.0), TEXTW, TEXTH}; - textData.a = 1.F; - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(textData))); - } - - // adjust offset and move on - offsetY += NOTIFSIZE.y + NOTIF_GAP_Y; - - if (maxWidth < NOTIFSIZE.x) - maxWidth = NOTIFSIZE.x; - } - - // cleanup notifs - std::erase_if(m_notifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; }); - - return CBox{sc(pMonitor->m_position.x + pMonitor->m_size.x - maxWidth - NOTIF_DAMAGE_PAD_X), sc(pMonitor->m_position.y), sc(maxWidth + NOTIF_DAMAGE_PAD_X), - sc(offsetY + NOTIF_OFFSET_Y)}; -} - -void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) { - // Draw the notifications - if (m_notifications.empty()) { - if (m_lastDamage.width > 0 && m_lastDamage.height > 0) - g_pHyprRenderer->damageBox(m_lastDamage); - m_lastDamage = {}; - return; - } - - CBox damage = drawNotifications(pMonitor); - - g_pHyprRenderer->damageBox(damage); - g_pHyprRenderer->damageBox(m_lastDamage); - - g_pCompositor->scheduleFrameForMonitor(pMonitor); - - m_lastDamage = damage; -} - -bool CHyprNotificationOverlay::hasAny() { - return !m_notifications.empty(); -} diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp deleted file mode 100644 index e8cbf56a..00000000 --- a/src/debug/HyprNotificationOverlay.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../helpers/time/Timer.hpp" -#include "../render/Texture.hpp" -#include "../SharedDefs.hpp" - -#include - -enum eIconBackend : uint8_t { - ICONS_BACKEND_NONE = 0, - ICONS_BACKEND_NF, - ICONS_BACKEND_FA -}; - -static const std::array, 3 /* backends */> ICONS_ARRAY = { - std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, - std::array{"", "", "", "", "", "󰸞", ""}, std::array{"", "", "", "", "", ""}}; -static const std::array ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0}, - CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0}, - CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0}, - CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0}, - CHyprColor{255 / 255.0, 204 / 255.0, 153 / 255.0, 1.0}, - CHyprColor{128 / 255.0, 255 / 255.0, 128 / 255.0, 1.0}, - CHyprColor{0, 0, 0, 1.0}}; - -struct SNotification { - struct SRenderCache { - SP textTex; - SP iconTex; - - Vector2D textSize = {}; - Vector2D iconSize = {}; - - PHLMONITORREF monitor; - std::string fontFamily; - int fontSizePx = -1; - eIconBackend iconBackend = ICONS_BACKEND_NONE; - } cache; - - std::string text = ""; - CHyprColor color; - CTimer started; - float timeMs = 0; - eIcons icon = ICON_NONE; - float fontSize = 13.f; -}; - -class CHyprNotificationOverlay { - public: - CHyprNotificationOverlay(); - ~CHyprNotificationOverlay(); - - void draw(PHLMONITOR pMonitor); - void addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon = ICON_NONE, const float fontSize = 13.f); - void dismissNotifications(const int amount); - bool hasAny(); - - private: - void ensureNotificationCache(SNotification& notif, PHLMONITOR pMonitor, const std::string& fontFamily); - eIconBackend iconBackendForFont(const std::string& fontFamily); - CBox drawNotifications(PHLMONITOR pMonitor); - CBox m_lastDamage; - - std::vector> m_notifications; - - std::string m_cachedIconBackendFontFamily; - eIconBackend m_cachedIconBackend = ICONS_BACKEND_NONE; - bool m_iconBackendValid = false; -}; - -inline UP g_pHyprNotificationOverlay; diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 2c0727c0..eef68c2f 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -42,7 +42,7 @@ #include "Drm.hpp" #include #include "debug/log/Logger.hpp" -#include "debug/HyprNotificationOverlay.hpp" +#include "notification/NotificationOverlay.hpp" #include "MonitorFrameScheduler.hpp" #include #include @@ -896,7 +896,7 @@ bool CMonitor::applyMonitorRule(Config::CMonitorRule&& pMonitorRule, bool force) auto errorMessage = I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_MODE_FAIL, {{"name", m_name}, {"mode", std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f)}}); Log::logger->log(Log::WARN, errorMessage); - g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); + Notification::overlay()->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); m_refreshRate = mode->refreshRate / 1000.f; m_size = mode->pixelSize; @@ -1037,11 +1037,12 @@ bool CMonitor::applyMonitorRule(Config::CMonitorRule&& pMonitorRule, bool force) if (!autoScale) { Log::logger->log(Log::ERR, "Invalid scale passed to monitor, {} found suggestion {}", m_scale, searchScale); static auto PDISABLENOTIFICATION = CConfigValue("misc:disable_scale_notification"); - if (!*PDISABLENOTIFICATION) - g_pHyprNotificationOverlay->addNotification( + if (!*PDISABLENOTIFICATION) { + Notification::overlay()->addNotification( I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, {{"name", m_name}, {"scale", std::format("{:.2f}", m_scale)}, {"fixed_scale", std::format("{:.2f}", searchScale)}}), CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING); + } } m_scale = searchScale; } @@ -1683,7 +1684,7 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { return reasons; } - if (g_pHyprNotificationOverlay->hasAny()) { + if (Notification::overlay()->hasAny()) { reasons |= SC_NOTIFICATION; if (!full) return reasons; diff --git a/src/notification/NotificationOverlay.cpp b/src/notification/NotificationOverlay.cpp new file mode 100644 index 00000000..0f38bfa3 --- /dev/null +++ b/src/notification/NotificationOverlay.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include "NotificationOverlay.hpp" +#include "../Compositor.hpp" +#include "../config/ConfigValue.hpp" +#include "../render/pass/RectPassElement.hpp" +#include "../render/pass/TexPassElement.hpp" +#include "../event/EventBus.hpp" + +#include "../managers/animation/AnimationManager.hpp" +#include "../render/Renderer.hpp" + +using namespace Notification; + +static inline auto iconBackendFromLayout(PangoLayout* layout) { + // preference: Nerd > FontAwesome > text + auto eIconBackendChecks = std::array{ICONS_BACKEND_NF, ICONS_BACKEND_FA}; + for (auto iconID : eIconBackendChecks) { + auto iconsText = std::ranges::fold_left(ICONS_ARRAY[iconID], std::string(), std::plus<>()); + pango_layout_set_text(layout, iconsText.c_str(), -1); + if (pango_layout_get_unknown_glyphs_count(layout) == 0) + return iconID; + } + return ICONS_BACKEND_NONE; +} + +static constexpr auto ANIM_DURATION_MS = 600.F; +static constexpr auto ANIM_LAG_MS = 100.F; +static constexpr auto NOTIF_LEFTBAR_SIZE = 5.F; +static constexpr auto NOTIF_PAD_X = 20.F; +static constexpr auto NOTIF_PAD_Y = 10.F; +static constexpr auto NOTIF_OFFSET_Y = 10.F; +static constexpr auto NOTIF_GAP_Y = 10.F; +static constexpr auto NOTIF_DAMAGE_PAD_X = 20.F; +static constexpr auto ICON_PAD = 3.F; +static constexpr auto ICON_SCALE = 0.9F; + +UP& Notification::overlay() { + static UP p = makeUnique(); + return p; +} + +CNotificationOverlay::CNotificationOverlay() { + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { + if (m_notifications.empty()) + return; + + g_pHyprRenderer->damageBox(m_lastDamage); + }); +} + +CNotificationOverlay::~CNotificationOverlay() = default; + +eIconBackend CNotificationOverlay::iconBackendForFont(const std::string& fontFamily) { + if (m_iconBackendValid && m_cachedIconBackendFontFamily == fontFamily) + return m_cachedIconBackend; + + auto* cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + auto* cairoCtx = cairo_create(cairoSurface); + auto* layout = pango_cairo_create_layout(cairoCtx); + auto* pangoFD = pango_font_description_new(); + + pango_font_description_set_family(pangoFD, fontFamily.c_str()); + pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); + pango_layout_set_font_description(layout, pangoFD); + + m_cachedIconBackend = iconBackendFromLayout(layout); + m_cachedIconBackendFontFamily = fontFamily; + m_iconBackendValid = true; + + pango_font_description_free(pangoFD); + g_object_unref(layout); + cairo_destroy(cairoCtx); + cairo_surface_destroy(cairoSurface); + + return m_cachedIconBackend; +} + +void CNotificationOverlay::ensureNotificationCache(SNotification& notif, PHLMONITOR pMonitor, const std::string& fontFamily) { + const auto iconBackend = iconBackendForFont(fontFamily); + const auto fontSizePx = std::clamp(sc(notif.fontSize * ((pMonitor->m_pixelSize.x * pMonitor->m_scale) / 1920.f)), 8, 40); + + const bool cacheValid = notif.cache.monitor == pMonitor && notif.cache.fontFamily == fontFamily && notif.cache.fontSizePx == fontSizePx && + notif.cache.iconBackend == iconBackend && notif.cache.textTex && (notif.icon == ICON_NONE || notif.cache.iconTex); + + if (cacheValid) + return; + + notif.cache = {}; + notif.cache.monitor = pMonitor; + notif.cache.fontFamily = fontFamily; + notif.cache.fontSizePx = fontSizePx; + notif.cache.iconBackend = iconBackend; + + notif.cache.textTex = g_pHyprRenderer->renderText(notif.text, CHyprColor{1.F, 1.F, 1.F, 1.F}, fontSizePx, false, fontFamily); + if (notif.cache.textTex) + notif.cache.textSize = notif.cache.textTex->m_size; + + if (notif.icon != ICON_NONE) { + const auto iconGlyph = ICONS_ARRAY[iconBackend][notif.icon]; + const auto iconSizePx = std::max(8, sc(std::round(fontSizePx * ICON_SCALE))); + + notif.cache.iconTex = g_pHyprRenderer->renderText(iconGlyph, CHyprColor{1.F, 1.F, 1.F, 1.F}, iconSizePx, false, fontFamily); + if (notif.cache.iconTex) + notif.cache.iconSize = notif.cache.iconTex->m_size; + } +} + +void CNotificationOverlay::addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon, const float fontSize) { + const auto PNOTIF = m_notifications.emplace_back(makeUnique()).get(); + + PNOTIF->text = text; + PNOTIF->color = color == CHyprColor(0) ? ICONS_COLORS[icon] : color; + PNOTIF->started.reset(); + PNOTIF->timeMs = timeMs; + PNOTIF->icon = icon; + PNOTIF->fontSize = fontSize; + + for (auto const& m : g_pCompositor->m_monitors) { + g_pCompositor->scheduleFrameForMonitor(m); + } +} + +void CNotificationOverlay::dismissNotifications(const int amount) { + if (amount == -1) + m_notifications.clear(); + else { + const int AMT = std::min(amount, sc(m_notifications.size())); + + for (int i = 0; i < AMT; ++i) { + m_notifications.erase(m_notifications.begin()); + } + } + + for (auto const& m : g_pCompositor->m_monitors) { + g_pCompositor->scheduleFrameForMonitor(m); + } +} + +CBox CNotificationOverlay::drawNotifications(PHLMONITOR pMonitor) { + float offsetY = NOTIF_OFFSET_Y; + float maxWidth = 0; + + const auto MONSIZE = pMonitor->m_transformedSize; + + static auto fontFamily = CConfigValue("misc:font_family"); + const auto PBEZIER = g_pAnimationManager->getBezier("default"); + + for (auto const& notif : m_notifications) { + ensureNotificationCache(*notif, pMonitor, *fontFamily); + + const auto ICONPADFORNOTIF = notif->icon == ICON_NONE ? 0.F : ICON_PAD; + const auto ICONW = notif->cache.iconSize.x; + const auto ICONH = notif->cache.iconSize.y; + const auto TEXTW = notif->cache.textSize.x; + const auto TEXTH = notif->cache.textSize.y; + const auto NOTIFSIZE = Vector2D{TEXTW + NOTIF_PAD_X + ICONW + 2 * ICONPADFORNOTIF, std::max(TEXTH, ICONH) + NOTIF_PAD_Y}; + + const float elapsed = notif->started.getMillis(); + const float lifeMs = std::max(notif->timeMs, 1.F); + + // first rect (bg, col) + const float FIRSTRECTANIMP = std::clamp( + (elapsed > (ANIM_DURATION_MS - ANIM_LAG_MS) ? (elapsed > lifeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? lifeMs - elapsed : (ANIM_DURATION_MS - ANIM_LAG_MS)) : elapsed) / + (ANIM_DURATION_MS - ANIM_LAG_MS), + 0.F, 1.F); + + const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP); + + // second rect (fg, black) + const float SECONDRECTANIMP = + std::clamp((elapsed > ANIM_DURATION_MS ? (elapsed > lifeMs - ANIM_DURATION_MS ? lifeMs - elapsed : ANIM_DURATION_MS) : elapsed) / ANIM_DURATION_MS, 0.F, 1.F); + + const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP); + + // third rect (horiz, col) + const float THIRDRECTPERC = std::clamp(elapsed / lifeMs, 0.F, 1.F); + + const float firstRectX = MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC; + const float firstRectW = (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC; + + const float secondRectX = MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC; + const float secondRectW = NOTIFSIZE.x * SECONDRECTPERC; + + CRectPassElement::SRectData bgData; + bgData.box = {firstRectX, offsetY, firstRectW, NOTIFSIZE.y}; + bgData.color = notif->color; + g_pHyprRenderer->m_renderPass.add(makeUnique(bgData)); + + CRectPassElement::SRectData fgData; + fgData.box = {secondRectX, offsetY, secondRectW, NOTIFSIZE.y}; + fgData.color = CHyprColor{0.F, 0.F, 0.F, 1.F}; + g_pHyprRenderer->m_renderPass.add(makeUnique(fgData)); + + CRectPassElement::SRectData progressData; + progressData.box = {secondRectX + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * std::max(0.0, NOTIFSIZE.x - 6.0), 2}; + progressData.color = notif->color; + g_pHyprRenderer->m_renderPass.add(makeUnique(progressData)); + + if (notif->icon != ICON_NONE && notif->cache.iconTex) { + CTexPassElement::SRenderData iconData; + iconData.tex = notif->cache.iconTex; + iconData.box = {secondRectX + NOTIF_LEFTBAR_SIZE + ICONPADFORNOTIF - 1, offsetY - 2 + std::round((NOTIFSIZE.y - ICONH) / 2.0), ICONW, ICONH}; + iconData.a = 1.F; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(iconData))); + } + + if (notif->cache.textTex) { + CTexPassElement::SRenderData textData; + textData.tex = notif->cache.textTex; + textData.box = {secondRectX + NOTIF_LEFTBAR_SIZE + ICONW + 2 * ICONPADFORNOTIF, offsetY - 2 + std::round((NOTIFSIZE.y - TEXTH) / 2.0), TEXTW, TEXTH}; + textData.a = 1.F; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(textData))); + } + + // adjust offset and move on + offsetY += NOTIFSIZE.y + NOTIF_GAP_Y; + + if (maxWidth < NOTIFSIZE.x) + maxWidth = NOTIFSIZE.x; + } + + // cleanup notifs + std::erase_if(m_notifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; }); + + return CBox{sc(pMonitor->m_position.x + pMonitor->m_size.x - maxWidth - NOTIF_DAMAGE_PAD_X), sc(pMonitor->m_position.y), sc(maxWidth + NOTIF_DAMAGE_PAD_X), + sc(offsetY + NOTIF_OFFSET_Y)}; +} + +void CNotificationOverlay::draw(PHLMONITOR pMonitor) { + // Draw the notifications + if (m_notifications.empty()) { + if (m_lastDamage.width > 0 && m_lastDamage.height > 0) + g_pHyprRenderer->damageBox(m_lastDamage); + m_lastDamage = {}; + return; + } + + CBox damage = drawNotifications(pMonitor); + + g_pHyprRenderer->damageBox(damage); + g_pHyprRenderer->damageBox(m_lastDamage); + + g_pCompositor->scheduleFrameForMonitor(pMonitor); + + m_lastDamage = damage; +} + +bool CNotificationOverlay::hasAny() { + return !m_notifications.empty(); +} diff --git a/src/notification/NotificationOverlay.hpp b/src/notification/NotificationOverlay.hpp new file mode 100644 index 00000000..b516cf22 --- /dev/null +++ b/src/notification/NotificationOverlay.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "../defines.hpp" +#include "../helpers/time/Timer.hpp" +#include "../render/Texture.hpp" +#include "../SharedDefs.hpp" + +#include + +namespace Notification { + + enum eIconBackend : uint8_t { + ICONS_BACKEND_NONE = 0, + ICONS_BACKEND_NF, + ICONS_BACKEND_FA + }; + + static const std::array, 3 /* backends */> ICONS_ARRAY = { + std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, + std::array{"", "", "", "", "", "󰸞", ""}, std::array{"", "", "", "", "", ""}}; + static const std::array ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0}, + CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0}, + CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0}, + CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0}, + CHyprColor{255 / 255.0, 204 / 255.0, 153 / 255.0, 1.0}, + CHyprColor{128 / 255.0, 255 / 255.0, 128 / 255.0, 1.0}, + CHyprColor{0, 0, 0, 1.0}}; + + struct SNotification { + struct SRenderCache { + SP textTex; + SP iconTex; + + Vector2D textSize = {}; + Vector2D iconSize = {}; + + PHLMONITORREF monitor; + std::string fontFamily; + int fontSizePx = -1; + eIconBackend iconBackend = ICONS_BACKEND_NONE; + } cache; + + std::string text = ""; + CHyprColor color; + CTimer started; + float timeMs = 0; + eIcons icon = ICON_NONE; + float fontSize = 13.f; + }; + + class CNotificationOverlay { + public: + CNotificationOverlay(); + ~CNotificationOverlay(); + + void draw(PHLMONITOR pMonitor); + void addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon = ICON_NONE, const float fontSize = 13.f); + void dismissNotifications(const int amount); + bool hasAny(); + + private: + void ensureNotificationCache(SNotification& notif, PHLMONITOR pMonitor, const std::string& fontFamily); + eIconBackend iconBackendForFont(const std::string& fontFamily); + CBox drawNotifications(PHLMONITOR pMonitor); + CBox m_lastDamage; + + std::vector> m_notifications; + + std::string m_cachedIconBackendFontFamily; + eIconBackend m_cachedIconBackend = ICONS_BACKEND_NONE; + bool m_iconBackendValid = false; + }; + + UP& overlay(); +} \ No newline at end of file diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index c979ea96..4722924d 100644 --- a/src/plugins/PluginAPI.cpp +++ b/src/plugins/PluginAPI.cpp @@ -4,7 +4,7 @@ #include "../plugins/PluginSystem.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../config/legacy/ConfigManager.hpp" -#include "../debug/HyprNotificationOverlay.hpp" +#include "../notification/NotificationOverlay.hpp" #include "../layout/target/Target.hpp" #include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include @@ -113,7 +113,7 @@ APICALL bool HyprlandAPI::addNotification(HANDLE handle, const std::string& text if (!PLUGIN) return false; - g_pHyprNotificationOverlay->addNotification(text, color, timeMs); + Notification::overlay()->addNotification(text, color, timeMs); return true; } @@ -301,7 +301,7 @@ APICALL bool addNotificationV2(HANDLE handle, const std::unordered_map(iterator->second); - g_pHyprNotificationOverlay->addNotification(text, COLOR, TIME, icon); + Notification::overlay()->addNotification(text, COLOR, TIME, icon); } catch (std::exception& e) { // bad any_cast most likely, plugin error diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index b52a994e..18b6adde 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -6,7 +6,7 @@ #include "../debug/HyprCtl.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" -#include "../debug/HyprNotificationOverlay.hpp" +#include "../notification/NotificationOverlay.hpp" #include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include "../i18n/Engine.hpp" @@ -228,8 +228,8 @@ void CPluginSystem::updateConfigPlugins(const std::vector& plugins, if (result->hasError()) { const auto NAME = path.contains('/') ? path.substr(path.find_last_of('/') + 1) : path; Log::logger->log(Log::ERR, "CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}", NAME, result->error()); - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{"name", NAME}, {"error", result->error()}}), - CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{"name", NAME}, {"error", result->error()}}), + CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); return; } diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index bf12ac8d..8ae52c8e 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -32,7 +32,7 @@ #include "../i18n/Engine.hpp" #include "../event/EventBus.hpp" #include "../managers/screenshare/ScreenshareManager.hpp" -#include "../debug/HyprNotificationOverlay.hpp" +#include "../notification/NotificationOverlay.hpp" #include "hyprerror/HyprError.hpp" #include "macros.hpp" #include "pass/TexPassElement.hpp" diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index f5855c44..ba82fa15 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -26,7 +26,7 @@ #include "../helpers/sync/SyncTimeline.hpp" #include "../hyprerror/HyprError.hpp" #include "../debug/HyprDebugOverlay.hpp" -#include "../debug/HyprNotificationOverlay.hpp" +#include "../notification/NotificationOverlay.hpp" #include "../layout/LayoutManager.hpp" #include "../layout/space/Space.hpp" #include "../i18n/Engine.hpp" @@ -2040,7 +2040,7 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { renderLockscreen(pMonitor, NOW, renderBox); if (pMonitor == Desktop::focusState()->monitor()) { - g_pHyprNotificationOverlay->draw(pMonitor); + Notification::overlay()->draw(pMonitor); g_pHyprError->draw(); } @@ -2261,8 +2261,8 @@ bool IHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { Log::logger->log(Log::WARN, "Wide color gamut is enabled but the display is not in 10bit mode"); static bool shown = false; if (!shown) { - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000, - ICON_WARNING); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); shown = true; } } @@ -2834,8 +2834,8 @@ std::tuple IHyprRenderer::getRenderTimes(PHLMONITOR pMonito static int handleCrashLoop(void* data) { - g_pHyprNotificationOverlay->addNotification("Hyprland will crash in " + std::to_string(10 - sc(g_pHyprRenderer->m_crashingDistort * 2.f)) + "s.", CHyprColor(0), 5000, - ICON_INFO); + Notification::overlay()->addNotification("Hyprland will crash in " + std::to_string(10 - sc(g_pHyprRenderer->m_crashingDistort * 2.f)) + "s.", CHyprColor(0), 5000, + ICON_INFO); g_pHyprRenderer->m_crashingDistort += 0.5f; @@ -2848,7 +2848,7 @@ static int handleCrashLoop(void* data) { } void IHyprRenderer::initiateManualCrash() { - g_pHyprNotificationOverlay->addNotification("Manual crash initiated. Farewell...", CHyprColor(0), 5000, ICON_INFO); + Notification::overlay()->addNotification("Manual crash initiated. Farewell...", CHyprColor(0), 5000, ICON_INFO); m_crashingLoop = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, handleCrashLoop, nullptr); wl_event_source_timer_update(m_crashingLoop, 1000); -- cgit v1.2.3