#include #include #include "Compositor.hpp" #include "debug/Log.hpp" #include "desktop/DesktopTypes.hpp" #include "helpers/Splashes.hpp" #include "config/ConfigValue.hpp" #include "config/ConfigWatcher.hpp" #include "managers/CursorManager.hpp" #include "managers/TokenManager.hpp" #include "managers/PointerManager.hpp" #include "managers/SeatManager.hpp" #include "managers/VersionKeeperManager.hpp" #include "managers/DonationNagManager.hpp" #include "managers/ANRManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp" #include "managers/permissions/DynamicPermissionManager.hpp" #include #include #include #include #include #include #include #include #include #include "debug/HyprCtl.hpp" #include "debug/CrashReporter.hpp" #ifdef USES_SYSTEMD #include // for SdNotify #endif #include "helpers/fs/FsUtils.hpp" #include "protocols/FractionalScale.hpp" #include "protocols/PointerConstraints.hpp" #include "protocols/LayerShell.hpp" #include "protocols/XDGShell.hpp" #include "protocols/XDGOutput.hpp" #include "protocols/SecurityContext.hpp" #include "protocols/ColorManagement.hpp" #include "protocols/core/Compositor.hpp" #include "protocols/core/Subcompositor.hpp" #include "desktop/LayerSurface.hpp" #include "render/Renderer.hpp" #include "xwayland/XWayland.hpp" #include "helpers/ByteOperations.hpp" #include "render/decorations/CHyprGroupBarDecoration.hpp" #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" #include "managers/XWaylandManager.hpp" #include "config/ConfigManager.hpp" #include "render/OpenGL.hpp" #include "managers/input/InputManager.hpp" #include "managers/AnimationManager.hpp" #include "managers/EventManager.hpp" #include "managers/HookSystemManager.hpp" #include "managers/ProtocolManager.hpp" #include "managers/LayoutManager.hpp" #include "plugins/PluginSystem.hpp" #include "hyprerror/HyprError.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "debug/HyprDebugOverlay.hpp" #include "helpers/MonitorFrameScheduler.hpp" #include #include #include #include #include #include #include #include using namespace Hyprutils::String; using namespace Aquamarine; using enum NContentType::eContentType; using namespace NColorManagement; static int handleCritSignal(int signo, void* data) { Debug::log(LOG, "Hyprland received signal {}", signo); if (signo == SIGTERM || signo == SIGINT || signo == SIGKILL) g_pCompositor->stopCompositor(); return 0; } static void handleUnrecoverableSignal(int sig) { // remove our handlers signal(SIGABRT, SIG_DFL); signal(SIGSEGV, SIG_DFL); if (g_pHookSystem && g_pHookSystem->m_currentEventPlugin) { longjmp(g_pHookSystem->m_hookFaultJumpBuf, 1); return; } // Kill the program if the crash-reporter is caught in a deadlock. signal(SIGALRM, [](int _) { char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n"; write(2, msg, strlen(msg)); abort(); }); alarm(15); NCrashReporter::createAndSaveCrash(sig); abort(); } static void handleUserSignal(int sig) { if (sig == SIGUSR1) { // means we have to unwind a timed out event throw std::exception(); } } static eLogLevel aqLevelToHl(Aquamarine::eBackendLogLevel level) { switch (level) { case Aquamarine::eBackendLogLevel::AQ_LOG_TRACE: return TRACE; case Aquamarine::eBackendLogLevel::AQ_LOG_DEBUG: return LOG; case Aquamarine::eBackendLogLevel::AQ_LOG_ERROR: return ERR; case Aquamarine::eBackendLogLevel::AQ_LOG_WARNING: return WARN; case Aquamarine::eBackendLogLevel::AQ_LOG_CRITICAL: return CRIT; default: break; } return NONE; } static void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) { Debug::log(aqLevelToHl(level), "[AQ] {}", msg); } void CCompositor::bumpNofile() { if (!getrlimit(RLIMIT_NOFILE, &m_originalNofile)) Debug::log(LOG, "Old rlimit: soft -> {}, hard -> {}", m_originalNofile.rlim_cur, m_originalNofile.rlim_max); else { Debug::log(ERR, "Failed to get NOFILE rlimits"); m_originalNofile.rlim_max = 0; return; } rlimit newLimit = m_originalNofile; newLimit.rlim_cur = newLimit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &newLimit) < 0) { Debug::log(ERR, "Failed bumping NOFILE limits higher"); m_originalNofile.rlim_max = 0; return; } if (!getrlimit(RLIMIT_NOFILE, &newLimit)) Debug::log(LOG, "New rlimit: soft -> {}, hard -> {}", newLimit.rlim_cur, newLimit.rlim_max); } void CCompositor::restoreNofile() { if (m_originalNofile.rlim_max <= 0) return; if (setrlimit(RLIMIT_NOFILE, &m_originalNofile) < 0) Debug::log(ERR, "Failed restoring NOFILE limits"); } void CCompositor::setMallocThreshold() { #ifdef M_TRIM_THRESHOLD // The default is 128 pages, // which is very large and can lead to a lot of memory used for no reason // because trimming hasn't happened static const int PAGESIZE = sysconf(_SC_PAGESIZE); mallopt(M_TRIM_THRESHOLD, 6 * PAGESIZE); #endif } CCompositor::CCompositor(bool onlyConfig) : m_onlyConfigVerification(onlyConfig), m_hyprlandPID(getpid()) { if (onlyConfig) return; setMallocThreshold(); m_hyprTempDataRoot = std::string{getenv("XDG_RUNTIME_DIR")} + "/hypr"; if (m_hyprTempDataRoot.starts_with("/hypr")) { std::println("Bailing out, $XDG_RUNTIME_DIR is invalid"); throw std::runtime_error("CCompositor() failed"); } if (!m_hyprTempDataRoot.starts_with("/run/user")) std::println("[!!WARNING!!] XDG_RUNTIME_DIR looks non-standard. Proceeding anyways..."); std::random_device dev; std::mt19937 engine(dev()); std::uniform_int_distribution<> distribution(0, INT32_MAX); m_instanceSignature = std::format("{}_{}_{}", GIT_COMMIT_HASH, std::time(nullptr), distribution(engine)); setenv("HYPRLAND_INSTANCE_SIGNATURE", m_instanceSignature.c_str(), true); if (!std::filesystem::exists(m_hyprTempDataRoot)) mkdir(m_hyprTempDataRoot.c_str(), S_IRWXU); else if (!std::filesystem::is_directory(m_hyprTempDataRoot)) { std::println("Bailing out, {} is not a directory", m_hyprTempDataRoot); throw std::runtime_error("CCompositor() failed"); } m_instancePath = m_hyprTempDataRoot + "/" + m_instanceSignature; if (std::filesystem::exists(m_instancePath)) { std::println("Bailing out, {} exists??", m_instancePath); throw std::runtime_error("CCompositor() failed"); } if (mkdir(m_instancePath.c_str(), S_IRWXU) < 0) { std::println("Bailing out, couldn't create {}", m_instancePath); throw std::runtime_error("CCompositor() failed"); } Debug::init(m_instancePath); Debug::log(LOG, "Instance Signature: {}", m_instanceSignature); Debug::log(LOG, "Runtime directory: {}", m_instancePath); Debug::log(LOG, "Hyprland PID: {}", m_hyprlandPID); Debug::log(LOG, "===== SYSTEM INFO: ====="); logSystemInfo(); Debug::log(LOG, "========================"); Debug::log(NONE, "\n\n"); // pad Debug::log(INFO, "If you are crashing, or encounter any bugs, please consult https://wiki.hypr.land/Crashes-and-Bugs/\n\n"); setRandomSplash(); Debug::log(LOG, "\nCurrent splash: {}\n\n", m_currentSplash); bumpNofile(); } CCompositor::~CCompositor() { if (!m_isShuttingDown && !m_onlyConfigVerification) cleanup(); } void CCompositor::setRandomSplash() { auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); auto local = *localtime(&tt); const auto* SPLASHES = &NSplashes::SPLASHES; if (local.tm_mon + 1 == 12 && local.tm_mday >= 23 && local.tm_mday <= 27) // dec 23-27 SPLASHES = &NSplashes::SPLASHES_CHRISTMAS; if ((local.tm_mon + 1 == 12 && local.tm_mday >= 29) || (local.tm_mon + 1 == 1 && local.tm_mday <= 3)) SPLASHES = &NSplashes::SPLASHES_NEWYEAR; std::random_device dev; std::mt19937 engine(dev()); std::uniform_int_distribution<> distribution(0, SPLASHES->size() - 1); m_currentSplash = SPLASHES->at(distribution(engine)); } static std::vector> pendingOutputs; // static bool filterGlobals(const wl_client* client, const wl_global* global, void* data) { if (!PROTO::securityContext->isClientSandboxed(client)) return true; return !g_pProtocolManager || !g_pProtocolManager->isGlobalPrivileged(global); } // void CCompositor::initServer(std::string socketName, int socketFd) { if (m_onlyConfigVerification) { g_pHookSystem = makeUnique(); g_pKeybindManager = makeUnique(); g_pAnimationManager = makeUnique(); g_pConfigManager = makeUnique(); std::println("\n\n======== Config parsing result:\n\n{}", g_pConfigManager->verify()); return; } m_wlDisplay = wl_display_create(); wl_display_set_global_filter(m_wlDisplay, ::filterGlobals, nullptr); m_wlEventLoop = wl_display_get_event_loop(m_wlDisplay); // register crit signal handler m_critSigSource = wl_event_loop_add_signal(m_wlEventLoop, SIGTERM, handleCritSignal, nullptr); if (!envEnabled("HYPRLAND_NO_CRASHREPORTER")) { signal(SIGSEGV, handleUnrecoverableSignal); signal(SIGABRT, handleUnrecoverableSignal); } signal(SIGUSR1, handleUserSignal); initManagers(STAGE_PRIORITY); if (envEnabled("HYPRLAND_TRACE")) Debug::m_trace = true; // set the buffer size to 1MB to avoid disconnects due to an app hanging for a short while wl_display_set_default_max_buffer_size(m_wlDisplay, 1_MB); Aquamarine::SBackendOptions options{}; options.logFunction = aqLog; std::vector implementations; Aquamarine::SBackendImplementationOptions option; option.backendType = Aquamarine::eBackendType::AQ_BACKEND_HEADLESS; option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_MANDATORY; implementations.emplace_back(option); option.backendType = Aquamarine::eBackendType::AQ_BACKEND_DRM; option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_IF_AVAILABLE; implementations.emplace_back(option); option.backendType = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND; option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_FALLBACK; implementations.emplace_back(option); m_aqBackend = CBackend::create(implementations, options); if (!m_aqBackend) { Debug::log(CRIT, "m_pAqBackend was null! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a Wayland " "session, NOT an X11 one."); throwError("CBackend::create() failed!"); } // TODO: headless only initAllSignals(); if (!m_aqBackend->start()) { Debug::log(CRIT, "m_pAqBackend couldn't start! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a " "Wayland session, NOT an X11 one."); throwError("CBackend::create() failed!"); } m_initialized = true; m_drmFD = m_aqBackend->drmFD(); Debug::log(LOG, "Running on DRMFD: {}", m_drmFD); if (!socketName.empty() && socketFd != -1) { fcntl(socketFd, F_SETFD, FD_CLOEXEC); const auto RETVAL = wl_display_add_socket_fd(m_wlDisplay, socketFd); if (RETVAL >= 0) { m_wlDisplaySocket = socketName; Debug::log(LOG, "wl_display_add_socket_fd for {} succeeded with {}", socketName, RETVAL); } else Debug::log(WARN, "wl_display_add_socket_fd for {} returned {}: skipping", socketName, RETVAL); } else { // get socket, avoid using 0 for (int candidate = 1; candidate <= 32; candidate++) { const auto CANDIDATESTR = ("wayland-" + std::to_string(candidate)); const auto RETVAL = wl_display_add_socket(m_wlDisplay, CANDIDATESTR.c_str()); if (RETVAL >= 0) { m_wlDisplaySocket = CANDIDATESTR; Debug::log(LOG, "wl_display_add_socket for {} succeeded with {}", CANDIDATESTR, RETVAL); break; } else Debug::log(WARN, "wl_display_add_socket for {} returned {}: skipping candidate {}", CANDIDATESTR, RETVAL, candidate); } } if (m_wlDisplaySocket.empty()) { Debug::log(WARN, "All candidates failed, trying wl_display_add_socket_auto"); const auto SOCKETSTR = wl_display_add_socket_auto(m_wlDisplay); if (SOCKETSTR) m_wlDisplaySocket = SOCKETSTR; } if (m_wlDisplaySocket.empty()) { Debug::log(CRIT, "m_szWLDisplaySocket NULL!"); throwError("m_szWLDisplaySocket was null! (wl_display_add_socket and wl_display_add_socket_auto failed)"); } setenv("WAYLAND_DISPLAY", m_wlDisplaySocket.c_str(), 1); if (!getenv("XDG_CURRENT_DESKTOP")) { setenv("XDG_CURRENT_DESKTOP", "Hyprland", 1); m_desktopEnvSet = true; } initManagers(STAGE_BASICINIT); initManagers(STAGE_LATE); for (auto const& o : pendingOutputs) { onNewMonitor(o); } pendingOutputs.clear(); } void CCompositor::initAllSignals() { m_aqBackend->events.newOutput.listenStatic([this](const SP& output) { Debug::log(LOG, "New aquamarine output with name {}", output->name); if (m_initialized) onNewMonitor(output); else pendingOutputs.emplace_back(output); }); m_aqBackend->events.newPointer.listenStatic([](const SP& dev) { Debug::log(LOG, "New aquamarine pointer with name {}", dev->getName()); g_pInputManager->newMouse(dev); g_pInputManager->updateCapabilities(); }); m_aqBackend->events.newKeyboard.listenStatic([](const SP& dev) { Debug::log(LOG, "New aquamarine keyboard with name {}", dev->getName()); g_pInputManager->newKeyboard(dev); g_pInputManager->updateCapabilities(); }); m_aqBackend->events.newTouch.listenStatic([](const SP& dev) { Debug::log(LOG, "New aquamarine touch with name {}", dev->getName()); g_pInputManager->newTouchDevice(dev); g_pInputManager->updateCapabilities(); }); m_aqBackend->events.newSwitch.listenStatic([](const SP& dev) { Debug::log(LOG, "New aquamarine switch with name {}", dev->getName()); g_pInputManager->newSwitch(dev); }); m_aqBackend->events.newTablet.listenStatic([](const SP& dev) { Debug::log(LOG, "New aquamarine tablet with name {}", dev->getName()); g_pInputManager->newTablet(dev); }); m_aqBackend->events.newTabletPad.listenStatic([](const SP& dev) { Debug::log(LOG, "New aquamarine tablet pad with name {}", dev->getName()); g_pInputManager->newTabletPad(dev); }); if (m_aqBackend->hasSession()) { m_aqBackend->session->events.changeActive.listenStatic([this] { if (m_aqBackend->session->active) { Debug::log(LOG, "Session got activated!"); m_sessionActive = true; for (auto const& m : m_monitors) { scheduleFrameForMonitor(m); m->applyMonitorRule(&m->m_activeMonitorRule, true); } g_pConfigManager->m_wantsMonitorReload = true; g_pCursorManager->syncGsettings(); } else { Debug::log(LOG, "Session got deactivated!"); m_sessionActive = false; } }); } } void CCompositor::removeAllSignals() { ; } void CCompositor::cleanEnvironment() { // in compositor constructor unsetenv("WAYLAND_DISPLAY"); // in startCompositor unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); // in main unsetenv("HYPRLAND_CMD"); unsetenv("XDG_BACKEND"); if (m_desktopEnvSet) unsetenv("XDG_CURRENT_DESKTOP"); if (m_aqBackend->hasSession() && !envEnabled("HYPRLAND_NO_SD_VARS")) { const auto CMD = #ifdef USES_SYSTEMD "systemctl --user unset-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " "dbus-update-activation-environment 2>/dev/null && " #endif "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS"; CKeybindManager::spawn(CMD); } } void CCompositor::stopCompositor() { Debug::log(LOG, "Hyprland is stopping!"); // this stops the wayland loop, wl_display_run wl_display_terminate(m_wlDisplay); m_isShuttingDown = true; } void CCompositor::cleanup() { if (!m_wlDisplay) return; signal(SIGABRT, SIG_DFL); signal(SIGSEGV, SIG_DFL); removeLockFile(); m_isShuttingDown = true; Debug::m_shuttingDown = true; #ifdef USES_SYSTEMD if (NSystemd::sdBooted() > 0 && !envEnabled("HYPRLAND_NO_SD_NOTIFY")) NSystemd::sdNotify(0, "STOPPING=1"); #endif cleanEnvironment(); // unload all remaining plugins while the compositor is // still in a normal working state. g_pPluginSystem->unloadAllPlugins(); m_lastFocus.reset(); m_lastWindow.reset(); m_workspaces.clear(); m_windows.clear(); for (auto const& m : m_monitors) { g_pHyprOpenGL->destroyMonitorResources(m); m->m_output->state->setEnabled(false); m->m_state.commit(); } g_pXWayland.reset(); m_monitors.clear(); wl_display_destroy_clients(g_pCompositor->m_wlDisplay); removeAllSignals(); g_pInputManager.reset(); g_pDynamicPermissionManager.reset(); g_pDecorationPositioner.reset(); g_pCursorManager.reset(); g_pPluginSystem.reset(); g_pHyprNotificationOverlay.reset(); g_pDebugOverlay.reset(); g_pEventManager.reset(); g_pSessionLockManager.reset(); g_pProtocolManager.reset(); g_pHyprRenderer.reset(); g_pHyprOpenGL.reset(); g_pConfigManager.reset(); g_pLayoutManager.reset(); g_pHyprError.reset(); g_pConfigManager.reset(); g_pKeybindManager.reset(); g_pHookSystem.reset(); g_pXWaylandManager.reset(); g_pPointerManager.reset(); g_pSeatManager.reset(); g_pHyprCtl.reset(); g_pEventLoopManager.reset(); g_pVersionKeeperMgr.reset(); g_pDonationNagManager.reset(); g_pANRManager.reset(); g_pConfigWatcher.reset(); if (m_aqBackend) m_aqBackend.reset(); if (m_critSigSource) wl_event_source_remove(m_critSigSource); // this frees all wayland resources, including sockets wl_display_destroy(m_wlDisplay); Debug::close(); } void CCompositor::initManagers(eManagersInitStage stage) { switch (stage) { case STAGE_PRIORITY: { Debug::log(LOG, "Creating the EventLoopManager!"); g_pEventLoopManager = makeUnique(m_wlDisplay, m_wlEventLoop); Debug::log(LOG, "Creating the HookSystem!"); g_pHookSystem = makeUnique(); Debug::log(LOG, "Creating the KeybindManager!"); g_pKeybindManager = makeUnique(); Debug::log(LOG, "Creating the AnimationManager!"); g_pAnimationManager = makeUnique(); Debug::log(LOG, "Creating the DynamicPermissionManager!"); g_pDynamicPermissionManager = makeUnique(); Debug::log(LOG, "Creating the ConfigManager!"); g_pConfigManager = makeUnique(); Debug::log(LOG, "Creating the CHyprError!"); g_pHyprError = makeUnique(); Debug::log(LOG, "Creating the LayoutManager!"); g_pLayoutManager = makeUnique(); Debug::log(LOG, "Creating the TokenManager!"); g_pTokenManager = makeUnique(); g_pConfigManager->init(); Debug::log(LOG, "Creating the PointerManager!"); g_pPointerManager = makeUnique(); Debug::log(LOG, "Creating the EventManager!"); g_pEventManager = makeUnique(); } break; case STAGE_BASICINIT: { Debug::log(LOG, "Creating the CHyprOpenGLImpl!"); g_pHyprOpenGL = makeUnique(); Debug::log(LOG, "Creating the ProtocolManager!"); g_pProtocolManager = makeUnique(); Debug::log(LOG, "Creating the SeatManager!"); g_pSeatManager = makeUnique(); } break; case STAGE_LATE: { Debug::log(LOG, "Creating CHyprCtl"); g_pHyprCtl = makeUnique(); Debug::log(LOG, "Creating the InputManager!"); g_pInputManager = makeUnique(); Debug::log(LOG, "Creating the HyprRenderer!"); g_pHyprRenderer = makeUnique(); Debug::log(LOG, "Creating the XWaylandManager!"); g_pXWaylandManager = makeUnique(); Debug::log(LOG, "Creating the SessionLockManager!"); g_pSessionLockManager = makeUnique(); Debug::log(LOG, "Creating the HyprDebugOverlay!"); g_pDebugOverlay = makeUnique(); Debug::log(LOG, "Creating the HyprNotificationOverlay!"); g_pHyprNotificationOverlay = makeUnique(); Debug::log(LOG, "Creating the PluginSystem!"); g_pPluginSystem = makeUnique(); g_pConfigManager->handlePluginLoads(); Debug::log(LOG, "Creating the DecorationPositioner!"); g_pDecorationPositioner = makeUnique(); Debug::log(LOG, "Creating the CursorManager!"); g_pCursorManager = makeUnique(); Debug::log(LOG, "Creating the VersionKeeper!"); g_pVersionKeeperMgr = makeUnique(); Debug::log(LOG, "Creating the DonationNag!"); g_pDonationNagManager = makeUnique(); Debug::log(LOG, "Creating the ANRManager!"); g_pANRManager = makeUnique(); Debug::log(LOG, "Starting XWayland"); g_pXWayland = makeUnique(g_pCompositor->m_wantsXwayland); } break; default: UNREACHABLE(); } } void CCompositor::createLockFile() { const auto PATH = m_instancePath + "/hyprland.lock"; std::ofstream ofs(PATH, std::ios::trunc); ofs << m_hyprlandPID << "\n" << m_wlDisplaySocket << "\n"; ofs.close(); } void CCompositor::removeLockFile() { const auto PATH = m_instancePath + "/hyprland.lock"; if (std::filesystem::exists(PATH)) std::filesystem::remove(PATH); } void CCompositor::prepareFallbackOutput() { // create a backup monitor SP headless; for (auto const& impl : m_aqBackend->getImplementations()) { if (impl->type() == Aquamarine::AQ_BACKEND_HEADLESS) { headless = impl; break; } } if (!headless) { Debug::log(WARN, "No headless in prepareFallbackOutput?!"); return; } headless->createOutput(); } void CCompositor::startCompositor() { signal(SIGPIPE, SIG_IGN); if ( /* Session-less Hyprland usually means a nest, don't update the env in that case */ m_aqBackend->hasSession() && /* Activation environment management is not disabled */ !envEnabled("HYPRLAND_NO_SD_VARS")) { const auto CMD = #ifdef USES_SYSTEMD "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " "dbus-update-activation-environment 2>/dev/null && " #endif "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS"; CKeybindManager::spawn(CMD); } Debug::log(LOG, "Running on WAYLAND_DISPLAY: {}", m_wlDisplaySocket); prepareFallbackOutput(); g_pHyprRenderer->setCursorFromName("left_ptr"); #ifdef USES_SYSTEMD if (NSystemd::sdBooted() > 0) { // tell systemd that we are ready so it can start other bond, following, related units if (!envEnabled("HYPRLAND_NO_SD_NOTIFY")) NSystemd::sdNotify(0, "READY=1"); } else Debug::log(LOG, "systemd integration is baked in but system itself is not booted à la systemd!"); #endif createLockFile(); EMIT_HOOK_EVENT("ready", nullptr); // This blocks until we are done. Debug::log(LOG, "Hyprland is ready, running the event loop!"); g_pEventLoopManager->enterLoop(); } PHLMONITOR CCompositor::getMonitorFromID(const MONITORID& id) { for (auto const& m : m_monitors) { if (m->m_id == id) { return m; } } return nullptr; } PHLMONITOR CCompositor::getMonitorFromName(const std::string& name) { for (auto const& m : m_monitors) { if (m->m_name == name) { return m; } } return nullptr; } PHLMONITOR CCompositor::getMonitorFromDesc(const std::string& desc) { for (auto const& m : m_monitors) { if (m->m_description.starts_with(desc)) return m; } return nullptr; } PHLMONITOR CCompositor::getMonitorFromCursor() { return getMonitorFromVector(g_pPointerManager->position()); } PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) { if (m_monitors.empty()) { Debug::log(WARN, "getMonitorFromVector called with empty monitor list"); return nullptr; } PHLMONITOR mon; for (auto const& m : m_monitors) { if (CBox{m->m_position, m->m_size}.containsPoint(point)) { mon = m; break; } } if (!mon) { float bestDistance = 0.f; PHLMONITOR pBestMon; for (auto const& m : m_monitors) { float dist = vecToRectDistanceSquared(point, m->m_position, m->m_position + m->m_size); if (dist < bestDistance || !pBestMon) { bestDistance = dist; pBestMon = m; } } if (!pBestMon) { // ????? Debug::log(WARN, "getMonitorFromVector no close mon???"); return m_monitors.front(); } return pBestMon; } return mon; } void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) { if (!pWindow->m_fadingOut) { EMIT_HOOK_EVENT("destroyWindow", pWindow); std::erase_if(m_windows, [&](SP& el) { return el == pWindow; }); std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; }); } } bool CCompositor::monitorExists(PHLMONITOR pMonitor) { return std::ranges::any_of(m_realMonitors, [&](const PHLMONITOR& m) { return m == pMonitor; }); } PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) { const auto PMONITOR = getMonitorFromVector(pos); static auto PRESIZEONBORDER = CConfigValue("general:resize_on_border"); static auto PBORDERSIZE = CConfigValue("general:border_size"); static auto PBORDERGRABEXTEND = CConfigValue("general:extend_border_grab_area"); static auto PSPECIALFALLTHRU = CConfigValue("input:special_fallthrough"); const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0; const bool ONLY_PRIORITY = properties & FOCUS_PRIORITY; // pinned windows on top of floating regardless if (properties & ALLOW_FLOATING) { for (auto const& w : m_windows | std::views::reverse) { if (ONLY_PRIORITY && !w->priorityFocus()) continue; if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow) { const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); if (box.containsPoint(g_pPointerManager->position())) return w; if (!w->m_isX11) { if (w->hasPopupAt(pos)) return w; } } } } auto windowForWorkspace = [&](bool special) -> PHLWINDOW { auto floating = [&](bool aboveFullscreen) -> PHLWINDOW { for (auto const& w : m_windows | std::views::reverse) { if (special && !w->onSpecialWorkspace()) // because special floating may creep up into regular continue; if (!w->m_workspace) continue; if (ONLY_PRIORITY && !w->priorityFocus()) continue; const auto PWINDOWMONITOR = w->m_monitor.lock(); // to avoid focusing windows behind special workspaces from other monitors if (!*PSPECIALFALLTHRU && PWINDOWMONITOR && PWINDOWMONITOR->m_activeSpecialWorkspace && w->m_workspace != PWINDOWMONITOR->m_activeSpecialWorkspace) { const auto BB = w->getWindowBoxUnified(properties); if (BB.x >= PWINDOWMONITOR->m_position.x && BB.y >= PWINDOWMONITOR->m_position.y && BB.x + BB.width <= PWINDOWMONITOR->m_position.x + PWINDOWMONITOR->m_size.x && BB.y + BB.height <= PWINDOWMONITOR->m_position.y + PWINDOWMONITOR->m_size.y) continue; } if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow && (!aboveFullscreen || w->m_createdOverFullscreen)) { // OR windows should add focus to parent if (w->m_X11ShouldntFocus && !w->isX11OverrideRedirect()) continue; const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); if (box.containsPoint(g_pPointerManager->position())) { if (w->m_isX11 && w->isX11OverrideRedirect() && !w->m_xwaylandSurface->wantsFocus()) { // Override Redirect return g_pCompositor->m_lastWindow.lock(); // we kinda trick everything here. // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases. } return w; } if (!w->m_isX11) { if (w->hasPopupAt(pos)) return w; } } } return nullptr; }; if (properties & ALLOW_FLOATING) { // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. auto found = floating(true); if (found) return found; } if (properties & FLOATING_ONLY) return floating(false); const WORKSPACEID WSPID = special ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID(); const auto PWORKSPACE = getWorkspaceByID(WSPID); if (PWORKSPACE->m_hasFullscreenWindow && !(properties & SKIP_FULLSCREEN_PRIORITY) && !ONLY_PRIORITY) return PWORKSPACE->getFullscreenWindow(); auto found = floating(false); if (found) return found; // for windows, we need to check their extensions too, first. for (auto const& w : m_windows) { if (ONLY_PRIORITY && !w->priorityFocus()) continue; if (special != w->onSpecialWorkspace()) continue; if (!w->m_workspace) continue; if (!w->m_isX11 && !w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow) { if (w->hasPopupAt(pos)) return w; } } for (auto const& w : m_windows) { if (ONLY_PRIORITY && !w->priorityFocus()) continue; if (special != w->onSpecialWorkspace()) continue; if (!w->m_workspace) continue; if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow) { CBox box = (properties & USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size}; if (box.containsPoint(pos)) return w; } } return nullptr; }; // special workspace if (PMONITOR->m_activeSpecialWorkspace && !*PSPECIALFALLTHRU) return windowForWorkspace(true); if (PMONITOR->m_activeSpecialWorkspace) { const auto PWINDOW = windowForWorkspace(true); if (PWINDOW) return PWINDOW; } return windowForWorkspace(false); } SP CCompositor::vectorWindowToSurface(const Vector2D& pos, PHLWINDOW pWindow, Vector2D& sl) { if (!validMapped(pWindow)) return nullptr; RASSERT(!pWindow->m_isX11, "Cannot call vectorWindowToSurface on an X11 window!"); // try popups first const auto PPOPUP = pWindow->m_popupHead->at(pos); if (PPOPUP) { const auto OFF = PPOPUP->coordsRelativeToParent(); sl = pos - pWindow->m_realPosition->goal() - OFF; return PPOPUP->m_wlSurface->resource(); } auto [surf, local] = pWindow->m_wlSurface->resource()->at(pos - pWindow->m_realPosition->goal(), true); if (surf) { sl = local; return surf; } return nullptr; } Vector2D CCompositor::vectorToSurfaceLocal(const Vector2D& vec, PHLWINDOW pWindow, SP pSurface) { if (!validMapped(pWindow)) return {}; if (pWindow->m_isX11) return vec - pWindow->m_realPosition->goal(); const auto PPOPUP = pWindow->m_popupHead->at(vec); if (PPOPUP) return vec - PPOPUP->coordsGlobal(); std::tuple, Vector2D> iterData = {pSurface, {-1337, -1337}}; pWindow->m_wlSurface->resource()->breadthfirst( [](SP surf, const Vector2D& offset, void* data) { const auto PDATA = (std::tuple, Vector2D>*)data; if (surf == std::get<0>(*PDATA)) std::get<1>(*PDATA) = offset; }, &iterData); CBox geom = pWindow->m_xdgSurface->m_current.geometry; if (std::get<1>(iterData) == Vector2D{-1337, -1337}) return vec - pWindow->m_realPosition->goal(); return vec - pWindow->m_realPosition->goal() - std::get<1>(iterData) + Vector2D{geom.x, geom.y}; } PHLMONITOR CCompositor::getMonitorFromOutput(SP out) { for (auto const& m : m_monitors) { if (m->m_output == out) { return m; } } return nullptr; } PHLMONITOR CCompositor::getRealMonitorFromOutput(SP out) { for (auto const& m : m_realMonitors) { if (m->m_output == out) { return m; } } return nullptr; } void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface, bool preserveFocusHistory) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PSPECIALFALLTHROUGH = CConfigValue("input:special_fallthrough"); if (!pWindow || !pWindow->priorityFocus()) { if (g_pSessionLockManager->isSessionLocked()) { Debug::log(LOG, "Refusing a keyboard focus to a window because of a sessionlock"); return; } if (!g_pInputManager->m_exclusiveLSes.empty()) { Debug::log(LOG, "Refusing a keyboard focus to a window because of an exclusive ls"); return; } } if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) return; g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); if (!pWindow || !validMapped(pWindow)) { if (m_lastWindow.expired() && !pWindow) return; const auto PLASTWINDOW = m_lastWindow.lock(); m_lastWindow.reset(); if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { updateWindowAnimatedDecorationValues(PLASTWINDOW); g_pXWaylandManager->activateWindow(PLASTWINDOW, false); } g_pSeatManager->setKeyboardFocus(nullptr); g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); EMIT_HOOK_EVENT("activeWindow", (PHLWINDOW) nullptr); g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr); m_lastFocus.reset(); g_pInputManager->recheckIdleInhibitorStatus(); return; } if (pWindow->m_windowData.noFocus.valueOrDefault()) { Debug::log(LOG, "Ignoring focus to nofocus window!"); return; } if (m_lastWindow.lock() == pWindow && g_pSeatManager->m_state.keyboardFocus == pSurface && g_pSeatManager->m_state.keyboardFocus) return; if (pWindow->m_pinned) pWindow->m_workspace = m_lastMonitor->m_activeWorkspace; const auto PMONITOR = pWindow->m_monitor.lock(); if (!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) { const auto PWORKSPACE = pWindow->m_workspace; // This is to fix incorrect feedback on the focus history. PWORKSPACE->m_lastFocusedWindow = pWindow; if (m_lastMonitor->m_activeWorkspace) PWORKSPACE->rememberPrevWorkspace(m_lastMonitor->m_activeWorkspace); if (PWORKSPACE->m_isSpecialWorkspace) m_lastMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor else if (PMONITOR) PMONITOR->changeWorkspace(PWORKSPACE, false, true); // changeworkspace already calls focusWindow return; } const auto PLASTWINDOW = m_lastWindow.lock(); m_lastWindow = pWindow; /* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which window focuses are "via keybinds" and which ones aren't. */ if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace != pWindow->m_workspace && !pWindow->m_pinned && !*PSPECIALFALLTHROUGH) PMONITOR->setSpecialWorkspace(nullptr); // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { PLASTWINDOW->updateDynamicRules(); updateWindowAnimatedDecorationValues(PLASTWINDOW); if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect()) g_pXWaylandManager->activateWindow(PLASTWINDOW, false); } m_lastWindow = PLASTWINDOW; const auto PWINDOWSURFACE = pSurface ? pSurface : pWindow->m_wlSurface->resource(); focusSurface(PWINDOWSURFACE, pWindow); g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow pWindow->updateDynamicRules(); pWindow->onFocusAnimUpdate(); updateWindowAnimatedDecorationValues(pWindow); if (pWindow->m_isUrgent) pWindow->m_isUrgent = false; // Send an event g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", (uintptr_t)pWindow.get())}); EMIT_HOOK_EVENT("activeWindow", pWindow); g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow); g_pInputManager->recheckIdleInhibitorStatus(); if (!preserveFocusHistory) { // move to front of the window history const auto HISTORYPIVOT = std::ranges::find_if(m_windowFocusHistory, [&](const auto& other) { return other.lock() == pWindow; }); if (HISTORYPIVOT == m_windowFocusHistory.end()) Debug::log(ERR, "BUG THIS: {} has no pivot in history", pWindow); else std::rotate(m_windowFocusHistory.begin(), HISTORYPIVOT, HISTORYPIVOT + 1); } if (*PFOLLOWMOUSE == 0) g_pInputManager->sendMotionEventsToFocused(); if (pWindow->m_groupData.pNextWindow) pWindow->deactivateGroupMembers(); } void CCompositor::focusSurface(SP pSurface, PHLWINDOW pWindowOwner) { if (g_pSeatManager->m_state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->m_state.keyboardFocus == pWindowOwner->m_wlSurface->resource())) return; // Don't focus when already focused on this. if (g_pSessionLockManager->isSessionLocked() && pSurface && !g_pSessionLockManager->isSurfaceSessionLock(pSurface)) return; if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(pSurface)) { Debug::log(LOG, "surface {:x} won't receive kb focus becuase grab rejected it", (uintptr_t)pSurface.get()); return; } const auto PLASTSURF = m_lastFocus.lock(); // Unfocus last surface if should if (m_lastFocus && !pWindowOwner) g_pXWaylandManager->activateSurface(m_lastFocus.lock(), false); if (!pSurface) { g_pSeatManager->setKeyboardFocus(nullptr); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); EMIT_HOOK_EVENT("keyboardFocus", (SP)nullptr); m_lastFocus.reset(); return; } if (g_pSeatManager->m_keyboard) g_pSeatManager->setKeyboardFocus(pSurface); if (pWindowOwner) Debug::log(LOG, "Set keyboard focus to surface {:x}, with {}", (uintptr_t)pSurface.get(), pWindowOwner); else Debug::log(LOG, "Set keyboard focus to surface {:x}", (uintptr_t)pSurface.get()); g_pXWaylandManager->activateSurface(pSurface, true); m_lastFocus = pSurface; EMIT_HOOK_EVENT("keyboardFocus", pSurface); const auto SURF = CWLSurface::fromResource(pSurface); const auto OLDSURF = CWLSurface::fromResource(PLASTSURF); if (OLDSURF && OLDSURF->constraint()) OLDSURF->constraint()->deactivate(); if (SURF && SURF->constraint()) SURF->constraint()->activate(); } SP CCompositor::vectorToLayerPopupSurface(const Vector2D& pos, PHLMONITOR monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) { for (auto const& lsl : monitor->m_layerSurfaceLayers | std::views::reverse) { for (auto const& ls : lsl | std::views::reverse) { if (!ls->m_mapped || ls->m_fadingOut || !ls->m_layerSurface || (ls->m_layerSurface && !ls->m_layerSurface->m_mapped) || ls->m_alpha->value() == 0.f) continue; auto SURFACEAT = ls->m_popupHead->at(pos, true); if (SURFACEAT) { *ppLayerSurfaceFound = ls.lock(); *sCoords = pos - SURFACEAT->coordsGlobal(); return SURFACEAT->m_wlSurface->resource(); } } } return nullptr; } SP CCompositor::vectorToLayerSurface(const Vector2D& pos, std::vector* layerSurfaces, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound, bool aboveLockscreen) { for (auto const& ls : *layerSurfaces | std::views::reverse) { if (!ls->m_mapped || ls->m_fadingOut || !ls->m_layerSurface || (ls->m_layerSurface && !ls->m_layerSurface->m_surface->m_mapped) || ls->m_alpha->value() == 0.f || (aboveLockscreen && (!ls->m_aboveLockscreen || !ls->m_aboveLockscreenInteractable))) continue; auto [surf, local] = ls->m_layerSurface->m_surface->at(pos - ls->m_geometry.pos(), true); if (surf) { if (surf->m_current.input.empty()) continue; *ppLayerSurfaceFound = ls.lock(); *sCoords = local; return surf; } } return nullptr; } PHLWINDOW CCompositor::getWindowFromSurface(SP pSurface) { if (!pSurface || !pSurface->m_hlSurface) return nullptr; return pSurface->m_hlSurface->getWindow(); } PHLWINDOW CCompositor::getWindowFromHandle(uint32_t handle) { for (auto const& w : m_windows) { if ((uint32_t)(((uint64_t)w.get()) & 0xFFFFFFFF) == handle) { return w; } } return nullptr; } PHLWORKSPACE CCompositor::getWorkspaceByID(const WORKSPACEID& id) { for (auto const& w : m_workspaces) { if (w->m_id == id && !w->inert()) return w; } return nullptr; } void CCompositor::sanityCheckWorkspaces() { auto it = m_workspaces.begin(); while (it != m_workspaces.end()) { const auto& WORKSPACE = *it; // If ref == 1, only the compositor holds a ref, which means it's inactive and has no mapped windows. if (!WORKSPACE->m_persistent && WORKSPACE.strongRef() == 1) { it = m_workspaces.erase(it); continue; } ++it; } } PHLWINDOW CCompositor::getUrgentWindow() { for (auto const& w : m_windows) { if (w->m_isMapped && w->m_isUrgent) return w; } return nullptr; } bool CCompositor::isWindowActive(PHLWINDOW pWindow) { if (m_lastWindow.expired() && !m_lastFocus) return false; if (!pWindow->m_isMapped) return false; const auto PSURFACE = pWindow->m_wlSurface->resource(); return PSURFACE == m_lastFocus || pWindow == m_lastWindow.lock(); } void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { if (!validMapped(pWindow)) return; if (top) pWindow->m_createdOverFullscreen = true; if (pWindow == (top ? m_windows.back() : m_windows.front())) return; auto moveToZ = [&](PHLWINDOW pw, bool top) -> void { if (top) { for (auto it = m_windows.begin(); it != m_windows.end(); ++it) { if (*it == pw) { std::rotate(it, it + 1, m_windows.end()); break; } } } else { for (auto it = m_windows.rbegin(); it != m_windows.rend(); ++it) { if (*it == pw) { std::rotate(it, it + 1, m_windows.rend()); break; } } } if (pw->m_isMapped) g_pHyprRenderer->damageMonitor(pw->m_monitor.lock()); }; if (!pWindow->m_isX11) moveToZ(pWindow, top); else { // move X11 window stack std::vector toMove; auto x11Stack = [&](PHLWINDOW pw, bool top, auto&& x11Stack) -> void { if (top) toMove.emplace_back(pw); else toMove.insert(toMove.begin(), pw); for (auto const& w : m_windows) { if (w->m_isMapped && !w->isHidden() && w->m_isX11 && w->x11TransientFor() == pw && w != pw && std::ranges::find(toMove, w) == toMove.end()) { x11Stack(w, top, x11Stack); } } }; x11Stack(pWindow, top, x11Stack); for (const auto& it : toMove) { moveToZ(it, top); } } } void CCompositor::cleanupFadingOut(const MONITORID& monid) { for (auto const& ww : m_windowsFadingOut) { auto w = ww.lock(); if (w->monitorID() != monid && w->m_monitor) continue; if (!w->m_fadingOut || w->m_alpha->value() == 0.f) { w->m_fadingOut = false; if (!w->m_readyToDelete) continue; removeWindowFromVectorSafe(w); w.reset(); Debug::log(LOG, "Cleanup: destroyed a window"); return; } } bool layersDirty = false; for (auto const& lsr : m_surfacesFadingOut) { auto ls = lsr.lock(); if (!ls) { layersDirty = true; continue; } if (ls->monitorID() != monid && ls->m_monitor) continue; // mark blur for recalc if (ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) g_pHyprOpenGL->markBlurDirtyForMonitor(getMonitorFromID(monid)); if (ls->m_fadingOut && ls->m_readyToDelete && ls->isFadedOut()) { for (auto const& m : m_monitors) { for (auto& lsl : m->m_layerSurfaceLayers) { if (!lsl.empty() && std::ranges::find_if(lsl, [&](auto& other) { return other == ls; }) != lsl.end()) { std::erase_if(lsl, [&](auto& other) { return other == ls || !other; }); } } } std::erase_if(m_surfacesFadingOut, [ls](const auto& el) { return el.lock() == ls; }); std::erase_if(m_layers, [ls](const auto& el) { return el == ls; }); ls.reset(); Debug::log(LOG, "Cleanup: destroyed a layersurface"); return; } } if (layersDirty) std::erase_if(m_surfacesFadingOut, [](const auto& el) { return el.expired(); }); } void CCompositor::addToFadingOutSafe(PHLLS pLS) { const auto FOUND = std::ranges::find_if(m_surfacesFadingOut, [&](auto& other) { return other.lock() == pLS; }); if (FOUND != m_surfacesFadingOut.end()) return; // if it's already added, don't add it. m_surfacesFadingOut.emplace_back(pLS); } void CCompositor::removeFromFadingOutSafe(PHLLS ls) { std::erase(m_surfacesFadingOut, ls); } void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) { const auto FOUND = std::ranges::find_if(m_windowsFadingOut, [&](PHLWINDOWREF& other) { return other.lock() == pWindow; }); if (FOUND != m_windowsFadingOut.end()) return; // if it's already added, don't add it. m_windowsFadingOut.emplace_back(pWindow); } PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { if (!isDirection(dir)) return nullptr; const auto PMONITOR = pWindow->m_monitor.lock(); if (!PMONITOR) return nullptr; // ?? const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{PMONITOR->m_position, PMONITOR->m_size} : pWindow->getWindowIdealBoundingBoxIgnoreReserved(); const auto PWORKSPACE = pWindow->m_workspace; return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); } PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { if (!isDirection(dir)) return nullptr; // 0 -> history, 1 -> shared length static auto PMETHOD = CConfigValue("binds:focus_preferred_method"); static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); const auto POSA = box.pos(); const auto SIZEA = box.size(); auto leaderValue = -1; PHLWINDOW leaderWindow = nullptr; if (!useVectorAngles) { for (auto const& w : m_windows) { if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) continue; if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace) continue; if (pWorkspace->m_hasFullscreenWindow && !w->isFullscreen() && !w->m_createdOverFullscreen) continue; if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor) continue; const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved(); const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y); const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height); double intersectLength = -1; switch (dir) { case 'l': if (STICKS(POSA.x, POSB.x + SIZEB.x)) { intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); } break; case 'r': if (STICKS(POSA.x + SIZEA.x, POSB.x)) { intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); } break; case 't': case 'u': if (STICKS(POSA.y, POSB.y + SIZEB.y)) { intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); } break; case 'b': case 'd': if (STICKS(POSA.y + SIZEA.y, POSB.y)) { intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); } break; } if (*PMETHOD == 0 /* history */) { if (intersectLength > 0) { // get idx int windowIDX = -1; for (size_t i = 0; i < g_pCompositor->m_windowFocusHistory.size(); ++i) { if (g_pCompositor->m_windowFocusHistory[i].lock() == w) { windowIDX = i; break; } } windowIDX = g_pCompositor->m_windowFocusHistory.size() - windowIDX; if (windowIDX > leaderValue) { leaderValue = windowIDX; leaderWindow = w; } } } else /* length */ { if (intersectLength > leaderValue) { leaderValue = intersectLength; leaderWindow = w; } } } } else { if (dir == 'u') dir = 't'; if (dir == 'd') dir = 'b'; static const std::unordered_map VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}}; // auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double { double dot = (a.x * b.x) + (a.y * b.y); double ang = std::acos(dot / (a.size() * b.size())); return ang; }; float bestAngleAbs = 2.0 * M_PI; constexpr float THRESHOLD = 0.3 * M_PI; for (auto const& w : m_windows) { if (w == ignoreWindow || !w->m_isMapped || !w->m_workspace || w->isHidden() || (!w->isFullscreen() && !w->m_isFloating) || !w->m_workspace->isVisible()) continue; if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace) continue; if (pWorkspace->m_hasFullscreenWindow && !w->isFullscreen() && !w->m_createdOverFullscreen) continue; if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor) continue; const auto DIST = w->middle().distance(box.middle()); const auto ANGLE = vectorAngles(Vector2D{w->middle() - box.middle()}, VECTORS.at(dir)); if (ANGLE > M_PI_2) continue; // if the angle is over 90 degrees, ignore. Wrong direction entirely. if ((bestAngleAbs < THRESHOLD && DIST < leaderValue && ANGLE < THRESHOLD) || (ANGLE < bestAngleAbs && bestAngleAbs > THRESHOLD) || leaderValue == -1) { leaderValue = DIST; bestAngleAbs = ANGLE; leaderWindow = w; } } if (!leaderWindow && pWorkspace->m_hasFullscreenWindow) leaderWindow = pWorkspace->getFullscreenWindow(); } if (leaderValue != -1) return leaderWindow; return nullptr; } template static bool isWorkspaceMatches(WINDOWPTR pWindow, const WINDOWPTR w, bool anyWorkspace) { return anyWorkspace ? w->m_workspace && w->m_workspace->isVisible() : w->m_workspace == pWindow->m_workspace; } template static bool isFloatingMatches(WINDOWPTR w, std::optional floating) { return !floating.has_value() || w->m_isFloating == floating.value(); } template static bool isWindowAvailableForCycle(WINDOWPTR pWindow, WINDOWPTR w, bool focusableOnly, std::optional floating, bool anyWorkspace = false) { return isFloatingMatches(w, floating) && (w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_windowData.noFocus.valueOrDefault())); } template static PHLWINDOW getWindowPred(Iterator cur, Iterator end, Iterator begin, const std::function PRED) { const auto IN_ONE_SIDE = std::find_if(cur, end, PRED); if (IN_ONE_SIDE != end) return *IN_ONE_SIDE; const auto IN_OTHER_SIDE = std::find_if(begin, cur, PRED); return *IN_OTHER_SIDE; } template static PHLWINDOW getWeakWindowPred(Iterator cur, Iterator end, Iterator begin, const std::function PRED) { const auto IN_ONE_SIDE = std::find_if(cur, end, PRED); if (IN_ONE_SIDE != end) return IN_ONE_SIDE->lock(); const auto IN_OTHER_SIDE = std::find_if(begin, cur, PRED); return IN_OTHER_SIDE->lock(); } PHLWINDOW CCompositor::getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly, std::optional floating, bool visible, bool next) { const auto FINDER = [&](const PHLWINDOWREF& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); }; // also m_vWindowFocusHistory has reverse order, so when it is next - we need to reverse again return next ? getWeakWindowPred(std::ranges::find(m_windowFocusHistory | std::views::reverse, cur), m_windowFocusHistory.rend(), m_windowFocusHistory.rbegin(), FINDER) : getWeakWindowPred(std::ranges::find(m_windowFocusHistory, cur), m_windowFocusHistory.end(), m_windowFocusHistory.begin(), FINDER); } PHLWINDOW CCompositor::getWindowCycle(PHLWINDOW cur, bool focusableOnly, std::optional floating, bool visible, bool prev) { const auto FINDER = [&](const PHLWINDOW& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); }; return prev ? getWindowPred(std::ranges::find(m_windows | std::views::reverse, cur), m_windows.rend(), m_windows.rbegin(), FINDER) : getWindowPred(std::ranges::find(m_windows, cur), m_windows.end(), m_windows.begin(), FINDER); } WORKSPACEID CCompositor::getNextAvailableNamedWorkspace() { WORKSPACEID lowest = -1337 + 1; for (auto const& w : m_workspaces) { if (w->m_id < -1 && w->m_id < lowest) lowest = w->m_id; } return lowest - 1; } PHLWORKSPACE CCompositor::getWorkspaceByName(const std::string& name) { for (auto const& w : m_workspaces) { if (w->m_name == name && !w->inert()) return w; } return nullptr; } PHLWORKSPACE CCompositor::getWorkspaceByString(const std::string& str) { if (str.starts_with("name:")) { return getWorkspaceByName(str.substr(str.find_first_of(':') + 1)); } try { return getWorkspaceByID(getWorkspaceIDNameFromString(str).id); } catch (std::exception& e) { Debug::log(ERR, "Error in getWorkspaceByString, invalid id"); } return nullptr; } bool CCompositor::isPointOnAnyMonitor(const Vector2D& point) { return std::ranges::any_of( m_monitors, [&](const PHLMONITOR& m) { return VECINRECT(point, m->m_position.x, m->m_position.y, m->m_size.x + m->m_position.x, m->m_size.y + m->m_position.y); }); } bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR pMonitor) { const auto PMONITOR = pMonitor ? pMonitor : getMonitorFromVector(point); const auto XY1 = PMONITOR->m_position + PMONITOR->m_reservedTopLeft; const auto XY2 = PMONITOR->m_position + PMONITOR->m_size - PMONITOR->m_reservedBottomRight; return VECNOTINRECT(point, XY1.x, XY1.y, XY2.x, XY2.y); } PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) { return getMonitorInDirection(m_lastMonitor.lock(), dir); } PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) { if (!pSourceMonitor) return nullptr; const auto POSA = pSourceMonitor->m_position; const auto SIZEA = pSourceMonitor->m_size; auto longestIntersect = -1; PHLMONITOR longestIntersectMonitor = nullptr; for (auto const& m : m_monitors) { if (m == pSourceMonitor) continue; const auto POSB = m->m_position; const auto SIZEB = m->m_size; switch (dir) { case 'l': if (STICKS(POSA.x, POSB.x + SIZEB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { longestIntersect = INTERSECTLEN; longestIntersectMonitor = m; } } break; case 'r': if (STICKS(POSA.x + SIZEA.x, POSB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { longestIntersect = INTERSECTLEN; longestIntersectMonitor = m; } } break; case 't': case 'u': if (STICKS(POSA.y, POSB.y + SIZEB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { longestIntersect = INTERSECTLEN; longestIntersectMonitor = m; } } break; case 'b': case 'd': if (STICKS(POSA.y + SIZEA.y, POSB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { longestIntersect = INTERSECTLEN; longestIntersectMonitor = m; } } break; } } if (longestIntersect != -1) return longestIntersectMonitor; return nullptr; } void CCompositor::updateAllWindowsAnimatedDecorationValues() { for (auto const& w : m_windows) { if (!w->m_isMapped) continue; updateWindowAnimatedDecorationValues(w); } } void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) { // optimization static auto PACTIVECOL = CConfigValue("general:col.active_border"); static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); static auto PFULLSCREENALPHA = CConfigValue("decoration:fullscreen_opacity"); static auto PSHADOWCOL = CConfigValue("decoration:shadow:color"); static auto PSHADOWCOLINACTIVE = CConfigValue("decoration:shadow:color_inactive"); static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); static auto PDIMENABLED = CConfigValue("decoration:dim_inactive"); auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); auto* const INACTIVECOL = (CGradientValueData*)(PINACTIVECOL.ptr())->getData(); auto* const NOGROUPACTIVECOL = (CGradientValueData*)(PNOGROUPACTIVECOL.ptr())->getData(); auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); auto setBorderColor = [&](CGradientValueData grad) -> void { if (grad == pWindow->m_realBorderColor) return; pWindow->m_realBorderColorPrevious = pWindow->m_realBorderColor; pWindow->m_realBorderColor = grad; pWindow->m_borderFadeAnimationProgress->setValueAndWarp(0.f); *pWindow->m_borderFadeAnimationProgress = 1.f; }; const bool IS_SHADOWED_BY_MODAL = pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel && pWindow->m_xdgSurface->m_toplevel->anyChildModal(); // border const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(pWindow); if (RENDERDATA.isBorderGradient) setBorderColor(*RENDERDATA.borderGradient); else { const bool GROUPLOCKED = pWindow->m_groupData.pNextWindow.lock() ? pWindow->getGroupHead()->m_groupData.locked : false; if (pWindow == m_lastWindow) { const auto* const ACTIVECOLOR = !pWindow->m_groupData.pNextWindow.lock() ? (!pWindow->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); setBorderColor(pWindow->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR)); } else { const auto* const INACTIVECOLOR = !pWindow->m_groupData.pNextWindow.lock() ? (!pWindow->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); setBorderColor(pWindow->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR)); } } // opacity const auto PWORKSPACE = pWindow->m_workspace; if (pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alphaFullscreen.valueOrDefault().applyAlpha(*PFULLSCREENALPHA); } else { if (pWindow == m_lastWindow) *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alpha.valueOrDefault().applyAlpha(*PACTIVEALPHA); else *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alphaInactive.valueOrDefault().applyAlpha(*PINACTIVEALPHA); } // dim float goalDim = 1.F; if (pWindow == m_lastWindow.lock() || pWindow->m_windowData.noDim.valueOrDefault() || !*PDIMENABLED) goalDim = 0; else goalDim = *PDIMSTRENGTH; if (IS_SHADOWED_BY_MODAL) goalDim += (1.F - goalDim) / 2.F; *pWindow->m_dimPercent = goalDim; // shadow if (!pWindow->isX11OverrideRedirect() && !pWindow->m_X11DoesntWantBorders) { if (pWindow == m_lastWindow) *pWindow->m_realShadowColor = CHyprColor(*PSHADOWCOL); else *pWindow->m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); } else { pWindow->m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow } pWindow->updateWindowDecos(); } MONITORID CCompositor::getNextAvailableMonitorID(std::string const& name) { // reuse ID if it's already in the map, and the monitor with that ID is not being used by another monitor if (m_monitorIDMap.contains(name) && !std::ranges::any_of(m_realMonitors, [&](auto m) { return m->m_id == m_monitorIDMap[name]; })) return m_monitorIDMap[name]; // otherwise, find minimum available ID that is not in the map std::unordered_set usedIDs; for (auto const& monitor : m_realMonitors) { usedIDs.insert(monitor->m_id); } MONITORID nextID = 0; while (usedIDs.contains(nextID)) { nextID++; } m_monitorIDMap[name] = nextID; return nextID; } void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitorB) { const auto PWORKSPACEA = pMonitorA->m_activeWorkspace; const auto PWORKSPACEB = pMonitorB->m_activeWorkspace; PWORKSPACEA->m_monitor = pMonitorB; PWORKSPACEA->m_events.monitorChanged.emit(); for (auto const& w : m_windows) { if (w->m_workspace == PWORKSPACEA) { if (w->m_pinned) { w->m_workspace = PWORKSPACEB; continue; } w->m_monitor = pMonitorB; // additionally, move floating and fs windows manually if (w->m_isFloating) *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position; if (w->isFullscreen()) { *w->m_realPosition = pMonitorB->m_position; *w->m_realSize = pMonitorB->m_size; } w->updateToplevel(); } } PWORKSPACEB->m_monitor = pMonitorA; PWORKSPACEB->m_events.monitorChanged.emit(); for (auto const& w : m_windows) { if (w->m_workspace == PWORKSPACEB) { if (w->m_pinned) { w->m_workspace = PWORKSPACEA; continue; } w->m_monitor = pMonitorA; // additionally, move floating and fs windows manually if (w->m_isFloating) *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position; if (w->isFullscreen()) { *w->m_realPosition = pMonitorA->m_position; *w->m_realSize = pMonitorA->m_size; } w->updateToplevel(); } } pMonitorA->m_activeWorkspace = PWORKSPACEB; pMonitorB->m_activeWorkspace = PWORKSPACEA; PWORKSPACEA->rememberPrevWorkspace(PWORKSPACEB); PWORKSPACEB->rememberPrevWorkspace(PWORKSPACEA); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id); updateFullscreenFadeOnWorkspace(PWORKSPACEB); updateFullscreenFadeOnWorkspace(PWORKSPACEA); if (pMonitorA->m_id == g_pCompositor->m_lastMonitor->m_id || pMonitorB->m_id == g_pCompositor->m_lastMonitor->m_id) { const auto LASTWIN = pMonitorA->m_id == g_pCompositor->m_lastMonitor->m_id ? PWORKSPACEB->getLastFocusedWindow() : PWORKSPACEA->getLastFocusedWindow(); g_pCompositor->focusWindow(LASTWIN ? LASTWIN : (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING))); const auto PNEWWORKSPACE = pMonitorA->m_id == g_pCompositor->m_lastMonitor->m_id ? PWORKSPACEB : PWORKSPACEA; g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspace", .data = PNEWWORKSPACE->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)}); EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE); } // event g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEA->m_name + "," + pMonitorB->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEA->m_id, PWORKSPACEA->m_name, pMonitorB->m_name)}); EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEA, pMonitorB})); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEB->m_name + "," + pMonitorA->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)}); EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEB, pMonitorA})); } PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { if (name == "current") return g_pCompositor->m_lastMonitor.lock(); else if (isDirection(name)) return getMonitorInDirection(name[0]); else if (name[0] == '+' || name[0] == '-') { // relative if (m_monitors.size() == 1) return *m_monitors.begin(); const auto OFFSET = name[0] == '-' ? name : name.substr(1); if (!isNumber(OFFSET)) { Debug::log(ERR, "Error in getMonitorFromString: Not a number in relative."); return nullptr; } int offsetLeft = std::stoi(OFFSET); offsetLeft = offsetLeft < 0 ? -((-offsetLeft) % m_monitors.size()) : offsetLeft % m_monitors.size(); int currentPlace = 0; for (int i = 0; i < (int)m_monitors.size(); i++) { if (m_monitors[i] == m_lastMonitor) { currentPlace = i; break; } } currentPlace += offsetLeft; if (currentPlace < 0) { currentPlace = m_monitors.size() + currentPlace; } else { currentPlace = currentPlace % m_monitors.size(); } if (currentPlace != std::clamp(currentPlace, 0, (int)m_monitors.size() - 1)) { Debug::log(WARN, "Error in getMonitorFromString: Vaxry's code sucks."); currentPlace = std::clamp(currentPlace, 0, (int)m_monitors.size() - 1); } return m_monitors[currentPlace]; } else if (isNumber(name)) { // change by ID MONITORID monID = MONITOR_INVALID; try { monID = std::stoi(name); } catch (std::exception& e) { // shouldn't happen but jic Debug::log(ERR, "Error in getMonitorFromString: invalid num"); return nullptr; } if (monID > -1 && monID < (MONITORID)m_monitors.size()) { return getMonitorFromID(monID); } else { Debug::log(ERR, "Error in getMonitorFromString: invalid arg 1"); return nullptr; } } else { for (auto const& m : m_monitors) { if (!m->m_output) continue; if (m->matchesStaticSelector(name)) { return m; } } } return nullptr; } void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor, bool noWarpCursor) { static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue("binds:hide_special_on_workspace_change"); if (!pWorkspace || !pMonitor) return; if (pWorkspace->m_monitor == pMonitor) return; Debug::log(LOG, "moveWorkspaceToMonitor: Moving {} to monitor {}", pWorkspace->m_id, pMonitor->m_id); const auto POLDMON = pWorkspace->m_monitor.lock(); const bool SWITCHINGISACTIVE = POLDMON ? POLDMON->m_activeWorkspace == pWorkspace : false; // fix old mon WORKSPACEID nextWorkspaceOnMonitorID = WORKSPACE_INVALID; if (!SWITCHINGISACTIVE) nextWorkspaceOnMonitorID = pWorkspace->m_id; else { for (auto const& w : m_workspaces) { if (w->m_monitor == POLDMON && w->m_id != pWorkspace->m_id && !w->m_isSpecialWorkspace) { nextWorkspaceOnMonitorID = w->m_id; break; } } if (nextWorkspaceOnMonitorID == WORKSPACE_INVALID) { nextWorkspaceOnMonitorID = 1; while (getWorkspaceByID(nextWorkspaceOnMonitorID) || [&]() -> bool { const auto B = g_pConfigManager->getBoundMonitorForWS(std::to_string(nextWorkspaceOnMonitorID)); return B && B != POLDMON; }()) nextWorkspaceOnMonitorID++; Debug::log(LOG, "moveWorkspaceToMonitor: Plugging gap with new {}", nextWorkspaceOnMonitorID); if (POLDMON) g_pCompositor->createNewWorkspace(nextWorkspaceOnMonitorID, POLDMON->m_id); } Debug::log(LOG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID); if (POLDMON) POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, true); } // move the workspace pWorkspace->m_monitor = pMonitor; pWorkspace->m_events.monitorChanged.emit(); for (auto const& w : m_windows) { if (w->m_workspace == pWorkspace) { if (w->m_pinned) { w->m_workspace = g_pCompositor->getWorkspaceByID(nextWorkspaceOnMonitorID); continue; } w->m_monitor = pMonitor; // additionally, move floating and fs windows manually if (w->m_isMapped && !w->isHidden()) { if (POLDMON) { if (w->m_isFloating) *w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position; if (w->isFullscreen()) { *w->m_realPosition = pMonitor->m_position; *w->m_realSize = pMonitor->m_size; } } else *w->m_realPosition = Vector2D{ (pMonitor->m_size.x != 0) ? (int)w->m_realPosition->goal().x % (int)pMonitor->m_size.x : 0, (pMonitor->m_size.y != 0) ? (int)w->m_realPosition->goal().y % (int)pMonitor->m_size.y : 0, }; } w->updateToplevel(); } } if (SWITCHINGISACTIVE && POLDMON == g_pCompositor->m_lastMonitor) { // if it was active, preserve its' status. If it wasn't, don't. Debug::log(LOG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id); if (valid(pMonitor->m_activeWorkspace)) { pMonitor->m_activeWorkspace->m_visible = false; pMonitor->m_activeWorkspace->startAnim(false, false); } if (*PHIDESPECIALONWORKSPACECHANGE) pMonitor->setSpecialWorkspace(nullptr); setActiveMonitor(pMonitor); auto oldWorkspace = pMonitor->m_activeWorkspace; pMonitor->m_activeWorkspace = pWorkspace; if (oldWorkspace) oldWorkspace->m_events.activeChanged.emit(); pWorkspace->m_events.activeChanged.emit(); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); pWorkspace->startAnim(true, true, true); pWorkspace->m_visible = true; if (!noWarpCursor) g_pPointerManager->warpTo(pMonitor->m_position + pMonitor->m_transformedSize / 2.F); g_pInputManager->sendMotionEventsToFocused(); } // finalize if (POLDMON) { g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id); if (valid(POLDMON->m_activeWorkspace)) updateFullscreenFadeOnWorkspace(POLDMON->m_activeWorkspace); updateSuspendedStates(); } updateFullscreenFadeOnWorkspace(pWorkspace); updateSuspendedStates(); // event g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)}); EMIT_HOOK_EVENT("moveWorkspace", (std::vector{pWorkspace, pMonitor})); } bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) { WORKSPACEID lowestID = INT64_MAX; WORKSPACEID highestID = INT64_MIN; for (auto const& w : m_workspaces) { if (w->m_isSpecialWorkspace) continue; lowestID = std::min(w->m_id, lowestID); highestID = std::max(w->m_id, highestID); } return std::clamp(id, lowestID, highestID) != id; } void CCompositor::updateFullscreenFadeOnWorkspace(PHLWORKSPACE pWorkspace) { if (!pWorkspace) return; const auto FULLSCREEN = pWorkspace->m_hasFullscreenWindow; for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == pWorkspace) { if (w->m_fadingOut || w->m_pinned || w->isFullscreen()) continue; if (!FULLSCREEN) *w->m_alpha = 1.f; else if (!w->isFullscreen()) *w->m_alpha = !w->m_createdOverFullscreen ? 0.f : 1.f; } } const auto PMONITOR = pWorkspace->m_monitor.lock(); if (pWorkspace->m_id == PMONITOR->activeWorkspaceID() || pWorkspace->m_id == PMONITOR->activeSpecialWorkspaceID()) { for (auto const& ls : PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { if (!ls->m_fadingOut) *ls->m_alpha = FULLSCREEN && pWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f; } } } void CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON) { setWindowFullscreenClient(PWINDOW, (eFullscreenMode)(ON ? (uint8_t)PWINDOW->m_fullscreenState.client | (uint8_t)MODE : ((uint8_t)PWINDOW->m_fullscreenState.client & (uint8_t)~MODE))); } void CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); else setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = PWINDOW->m_fullscreenState.client}); } void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); else setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = MODE}); } void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenState state) { static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); static auto PALLOWPINFULLSCREEN = CConfigValue("binds:allow_pin_fullscreen"); if (!validMapped(PWINDOW) || g_pCompositor->m_unsafeState) return; state.internal = std::clamp(state.internal, (eFullscreenMode)0, FSMODE_MAX); state.client = std::clamp(state.client, (eFullscreenMode)0, FSMODE_MAX); const auto PMONITOR = PWINDOW->m_monitor.lock(); const auto PWORKSPACE = PWINDOW->m_workspace; const eFullscreenMode CURRENT_EFFECTIVE_MODE = (eFullscreenMode)std::bit_floor((uint8_t)PWINDOW->m_fullscreenState.internal); const eFullscreenMode EFFECTIVE_MODE = (eFullscreenMode)std::bit_floor((uint8_t)state.internal); if (PWINDOW->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE && EFFECTIVE_MODE != FSMODE_NONE) g_pHyprRenderer->damageWindow(PWINDOW); if (*PALLOWPINFULLSCREEN && !PWINDOW->m_pinFullscreened && !PWINDOW->isFullscreen() && PWINDOW->m_pinned) { PWINDOW->m_pinned = false; PWINDOW->m_pinFullscreened = true; } if (PWORKSPACE->m_hasFullscreenWindow && !PWINDOW->isFullscreen()) setWindowFullscreenInternal(PWORKSPACE->getFullscreenWindow(), FSMODE_NONE); const bool CHANGEINTERNAL = !PWINDOW->m_pinned && CURRENT_EFFECTIVE_MODE != EFFECTIVE_MODE; if (*PALLOWPINFULLSCREEN && PWINDOW->m_pinFullscreened && PWINDOW->isFullscreen() && !PWINDOW->m_pinned && state.internal == FSMODE_NONE) { PWINDOW->m_pinned = true; PWINDOW->m_pinFullscreened = false; } // TODO: update the state on syncFullscreen changes if (!CHANGEINTERNAL && PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) return; PWINDOW->m_fullscreenState.client = state.client; g_pXWaylandManager->setWindowFullscreen(PWINDOW, state.client & FSMODE_FULLSCREEN); if (!CHANGEINTERNAL) { PWINDOW->updateDynamicRules(); updateWindowAnimatedDecorationValues(PWINDOW); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); return; } g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); PWINDOW->m_fullscreenState.internal = state.internal; PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string((int)EFFECTIVE_MODE != FSMODE_NONE)}); EMIT_HOOK_EVENT("fullscreen", PWINDOW); PWINDOW->updateDynamicRules(); updateWindowAnimatedDecorationValues(PWINDOW); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); // make all windows on the same workspace under the fullscreen window for (auto const& w : m_windows) { if (w->m_workspace == PWORKSPACE && !w->isFullscreen() && !w->m_fadingOut && !w->m_pinned) w->m_createdOverFullscreen = false; } updateFullscreenFadeOnWorkspace(PWORKSPACE); PWINDOW->sendWindowSize(true); PWORKSPACE->forceReportSizesToWindows(); g_pInputManager->recheckIdleInhibitorStatus(); // further updates require a monitor if (!PMONITOR) return; // send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't. // ignore if DS is disabled. if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) g_pHyprRenderer->setSurfaceScanoutMode(PWINDOW->m_wlSurface->resource(), EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr); g_pConfigManager->ensureVRR(PMONITOR); } PHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) { if (!pWindow->m_isX11) return nullptr; for (auto const& w : m_windows) { if (!w->m_isX11) continue; if (w->m_xwaylandSurface == pWindow->m_xwaylandSurface->m_parent) return w; } return nullptr; } void CCompositor::scheduleFrameForMonitor(PHLMONITOR pMonitor, IOutput::scheduleFrameReason reason) { if ((m_aqBackend->hasSession() && !m_aqBackend->session->active) || !m_sessionActive) return; if (!pMonitor->m_enabled) return; if (pMonitor->m_renderingActive) pMonitor->m_pendingFrame = true; pMonitor->m_output->scheduleFrame(reason); } PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { auto regexp = trim(regexp_); if (regexp.starts_with("active")) return m_lastWindow.lock(); else if (regexp.starts_with("floating") || regexp.starts_with("tiled")) { // first floating on the current ws if (!valid(m_lastWindow)) return nullptr; const bool FLOAT = regexp.starts_with("floating"); for (auto const& w : m_windows) { if (!w->m_isMapped || w->m_isFloating != FLOAT || w->m_workspace != m_lastWindow->m_workspace || w->isHidden()) continue; return w; } return nullptr; } eFocusWindowMode mode = MODE_CLASS_REGEX; std::string regexCheck; std::string matchCheck; if (regexp.starts_with("class:")) { regexCheck = regexp.substr(6); } else if (regexp.starts_with("initialclass:")) { mode = MODE_INITIAL_CLASS_REGEX; regexCheck = regexp.substr(13); } else if (regexp.starts_with("title:")) { mode = MODE_TITLE_REGEX; regexCheck = regexp.substr(6); } else if (regexp.starts_with("initialtitle:")) { mode = MODE_INITIAL_TITLE_REGEX; regexCheck = regexp.substr(13); } else if (regexp.starts_with("tag:")) { mode = MODE_TAG_REGEX; regexCheck = regexp.substr(4); } else if (regexp.starts_with("address:")) { mode = MODE_ADDRESS; matchCheck = regexp.substr(8); } else if (regexp.starts_with("pid:")) { mode = MODE_PID; matchCheck = regexp.substr(4); } for (auto const& w : g_pCompositor->m_windows) { if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w))) continue; switch (mode) { case MODE_CLASS_REGEX: { const auto windowClass = w->m_class; if (!RE2::FullMatch(windowClass, regexCheck)) continue; break; } case MODE_INITIAL_CLASS_REGEX: { const auto initialWindowClass = w->m_initialClass; if (!RE2::FullMatch(initialWindowClass, regexCheck)) continue; break; } case MODE_TITLE_REGEX: { const auto windowTitle = w->m_title; if (!RE2::FullMatch(windowTitle, regexCheck)) continue; break; } case MODE_INITIAL_TITLE_REGEX: { const auto initialWindowTitle = w->m_initialTitle; if (!RE2::FullMatch(initialWindowTitle, regexCheck)) continue; break; } case MODE_TAG_REGEX: { bool tagMatched = false; for (auto const& t : w->m_tags.getTags()) { if (RE2::FullMatch(t, regexCheck)) { tagMatched = true; break; } } if (!tagMatched) continue; break; } case MODE_ADDRESS: { std::string addr = std::format("0x{:x}", (uintptr_t)w.get()); if (matchCheck != addr) continue; break; } case MODE_PID: { std::string pid = std::format("{}", w->getPID()); if (matchCheck != pid) continue; break; } default: break; } return w; } return nullptr; } void CCompositor::warpCursorTo(const Vector2D& pos, bool force) { // warpCursorTo should only be used for warps that // should be disabled with no_warps static auto PNOWARPS = CConfigValue("cursor:no_warps"); if (*PNOWARPS && !force) { const auto PMONITORNEW = getMonitorFromVector(pos); if (PMONITORNEW != m_lastMonitor) setActiveMonitor(PMONITORNEW); return; } g_pPointerManager->warpTo(pos); const auto PMONITORNEW = getMonitorFromVector(pos); if (PMONITORNEW != m_lastMonitor) setActiveMonitor(PMONITORNEW); } void CCompositor::closeWindow(PHLWINDOW pWindow) { if (pWindow && validMapped(pWindow)) g_pXWaylandManager->sendCloseWindow(pWindow); } PHLLS CCompositor::getLayerSurfaceFromSurface(SP pSurface) { std::pair, bool> result = {pSurface, false}; for (auto const& ls : m_layers) { if (ls->m_layerSurface && ls->m_layerSurface->m_surface == pSurface) return ls; if (!ls->m_layerSurface || !ls->m_mapped) continue; ls->m_layerSurface->m_surface->breadthfirst( [&result](SP surf, const Vector2D& offset, void* data) { if (surf == result.first) { result.second = true; return; } }, nullptr); if (result.second) return ls; } return nullptr; } // returns a delta Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, const Vector2D& relativeTo) { if (!args.contains(' ') && !args.contains('\t')) return relativeTo; const auto PMONITOR = m_lastMonitor; bool xIsPercent = false; bool yIsPercent = false; bool isExact = false; CVarList varList(args, 0, 's', true); std::string x = varList[0]; std::string y = varList[1]; if (x == "exact") { x = varList[1]; y = varList[2]; isExact = true; } if (x.contains('%')) { xIsPercent = true; x = x.substr(0, x.length() - 1); } if (y.contains('%')) { yIsPercent = true; y = y.substr(0, y.length() - 1); } if (!isNumber(x) || !isNumber(y)) { Debug::log(ERR, "parseWindowVectorArgsRelative: args not numbers"); return relativeTo; } int X = 0; int Y = 0; if (isExact) { X = xIsPercent ? std::stof(x) * 0.01 * PMONITOR->m_size.x : std::stoi(x); Y = yIsPercent ? std::stof(y) * 0.01 * PMONITOR->m_size.y : std::stoi(y); } else { X = xIsPercent ? (std::stof(x) * 0.01 * relativeTo.x) + relativeTo.x : std::stoi(x) + relativeTo.x; Y = yIsPercent ? (std::stof(y) * 0.01 * relativeTo.y) + relativeTo.y : std::stoi(y) + relativeTo.y; } return Vector2D(X, Y); } PHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITORID& monid, const std::string& name, bool isEmpty) { const auto NAME = name.empty() ? std::to_string(id) : name; auto monID = monid; // check if bound if (const auto PMONITOR = g_pConfigManager->getBoundMonitorForWS(NAME); PMONITOR) monID = PMONITOR->m_id; const bool SPECIAL = id >= SPECIAL_WORKSPACE_START && id <= -2; const auto PMONITOR = getMonitorFromID(monID); if (!PMONITOR) { Debug::log(ERR, "BUG THIS: No pMonitor for new workspace in createNewWorkspace"); return nullptr; } const auto PWORKSPACE = m_workspaces.emplace_back(CWorkspace::create(id, PMONITOR, NAME, SPECIAL, isEmpty)); PWORKSPACE->m_alpha->setValueAndWarp(0); return PWORKSPACE; } void CCompositor::setActiveMonitor(PHLMONITOR pMonitor) { if (m_lastMonitor == pMonitor) return; if (!pMonitor) { m_lastMonitor.reset(); return; } const auto PWORKSPACE = pMonitor->m_activeWorkspace; const auto WORKSPACE_ID = PWORKSPACE ? std::to_string(PWORKSPACE->m_id) : std::to_string(WORKSPACE_INVALID); const auto WORKSPACE_NAME = PWORKSPACE ? PWORKSPACE->m_name : "?"; g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID}); EMIT_HOOK_EVENT("focusedMon", pMonitor); m_lastMonitor = pMonitor->m_self; } bool CCompositor::isWorkspaceSpecial(const WORKSPACEID& id) { return id >= SPECIAL_WORKSPACE_START && id <= -2; } WORKSPACEID CCompositor::getNewSpecialID() { WORKSPACEID highest = SPECIAL_WORKSPACE_START; for (auto const& ws : m_workspaces) { if (ws->m_isSpecialWorkspace && ws->m_id > highest) { highest = ws->m_id; } } return highest + 1; } void CCompositor::performUserChecks() { static auto PNOCHECKXDG = CConfigValue("misc:disable_xdg_env_checks"); static auto PNOCHECKQTUTILS = CConfigValue("misc:disable_hyprland_qtutils_check"); if (!*PNOCHECKXDG) { const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP"); if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != "Hyprland") { g_pHyprNotificationOverlay->addNotification( std::format("Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {}.\nThis might cause issues unless it's intentional.", CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"), CHyprColor{}, 15000, ICON_WARNING); } } if (!*PNOCHECKQTUTILS) { if (!NFsUtils::executableExistsInPath("hyprland-dialog")) { g_pHyprNotificationOverlay->addNotification( "Your system does not have hyprland-qtutils installed. This is a runtime dependency for some dialogs. Consider installing it.", CHyprColor{}, 15000, ICON_WARNING); } } if (g_pHyprOpenGL->m_failedAssetsNo > 0) { g_pHyprNotificationOverlay->addNotification(std::format("Hyprland failed to load {} essential asset{}, blame your distro's packager for doing a bad job at packaging!", g_pHyprOpenGL->m_failedAssetsNo, g_pHyprOpenGL->m_failedAssetsNo > 1 ? "s" : ""), CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR); } } void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) { if (!pWindow || !pWorkspace) return; if (pWindow->m_pinned && pWorkspace->m_isSpecialWorkspace) return; if (pWindow->m_workspace == pWorkspace) return; const bool FULLSCREEN = pWindow->isFullscreen(); const auto FULLSCREENMODE = pWindow->m_fullscreenState.internal; const bool WASVISIBLE = pWindow->m_workspace && pWindow->m_workspace->isVisible(); if (FULLSCREEN) setWindowFullscreenInternal(pWindow, FSMODE_NONE); const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow(); const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true); const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{}); const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock(); if (!pWindow->m_isFloating) g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow); pWindow->moveToWorkspace(pWorkspace); pWindow->m_monitor = pWorkspace->m_monitor; static auto PGROUPONMOVETOWORKSPACE = CConfigValue("group:group_on_movetoworkspace"); if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) { pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa. if (!pWindow->m_groupData.pNextWindow.expired()) { PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); while (next != pWindow) { next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members next = next->m_groupData.pNextWindow.lock(); } } static auto USECURRPOS = CConfigValue("group:insert_after_current"); (*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow); pFirstWindowOnWorkspace->setGroupCurrent(pWindow); pWindow->updateWindowDecos(); g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) pWindow->addWindowDeco(makeUnique(pWindow)); } else { if (!pWindow->m_isFloating) g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow); if (pWindow->m_isFloating) *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position; } pWindow->updateToplevel(); pWindow->updateDynamicRules(); pWindow->uncacheWindowDecos(); pWindow->updateGroupOutputs(); if (!pWindow->m_groupData.pNextWindow.expired()) { PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); while (next != pWindow) { next->updateToplevel(); next = next->m_groupData.pNextWindow.lock(); } } if (FULLSCREEN) setWindowFullscreenInternal(pWindow, FULLSCREENMODE); pWorkspace->updateWindows(); if (pWindow->m_workspace) pWindow->m_workspace->updateWindows(); g_pCompositor->updateSuspendedStates(); if (!WASVISIBLE && pWindow->m_workspace && pWindow->m_workspace->isVisible()) { pWindow->m_movingFromWorkspaceAlpha->setValueAndWarp(0.F); *pWindow->m_movingFromWorkspaceAlpha = 1.F; } } PHLWINDOW CCompositor::getForceFocus() { for (auto const& w : m_windows) { if (!w->m_isMapped || w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible()) continue; if (!w->m_stayFocused) continue; return w; } return nullptr; } void CCompositor::arrangeMonitors() { static auto* const PXWLFORCESCALEZERO = (Hyprlang::INT* const*)g_pConfigManager->getConfigValuePtr("xwayland:force_zero_scaling"); std::vector toArrange(m_monitors.begin(), m_monitors.end()); std::vector arranged; arranged.reserve(toArrange.size()); Debug::log(LOG, "arrangeMonitors: {} to arrange", toArrange.size()); for (auto it = toArrange.begin(); it != toArrange.end();) { auto m = *it; if (m->m_activeMonitorRule.offset != Vector2D{-INT32_MAX, -INT32_MAX}) { // explicit. Debug::log(LOG, "arrangeMonitors: {} explicit {:j}", m->m_name, m->m_activeMonitorRule.offset); m->moveTo(m->m_activeMonitorRule.offset); arranged.push_back(m); it = toArrange.erase(it); if (it == toArrange.end()) break; continue; } ++it; } // Variables to store the max and min values of monitors on each axis. int maxXOffsetRight = 0; int maxXOffsetLeft = 0; int maxYOffsetUp = 0; int maxYOffsetDown = 0; auto recalcMaxOffsets = [&]() { maxXOffsetRight = 0; maxXOffsetLeft = 0; maxYOffsetUp = 0; maxYOffsetDown = 0; // Finds the max and min values of explicitely placed monitors. for (auto const& m : arranged) { maxXOffsetRight = std::max(m->m_position.x + m->m_size.x, maxXOffsetRight); maxXOffsetLeft = std::min(m->m_position.x, maxXOffsetLeft); maxYOffsetDown = std::max(m->m_position.y + m->m_size.y, maxYOffsetDown); maxYOffsetUp = std::min(m->m_position.y, maxYOffsetUp); } }; // Iterates through all non-explicitly placed monitors. for (auto const& m : toArrange) { recalcMaxOffsets(); // Moves the monitor to their appropriate position on the x/y axis and // increments/decrements the corresponding max offset. Vector2D newPosition = {0, 0}; switch (m->m_activeMonitorRule.autoDir) { case eAutoDirs::DIR_AUTO_UP: newPosition.y = maxYOffsetUp - m->m_size.y; break; case eAutoDirs::DIR_AUTO_DOWN: newPosition.y = maxYOffsetDown; break; case eAutoDirs::DIR_AUTO_LEFT: newPosition.x = maxXOffsetLeft - m->m_size.x; break; case eAutoDirs::DIR_AUTO_RIGHT: case eAutoDirs::DIR_AUTO_NONE: newPosition.x = maxXOffsetRight; break; case eAutoDirs::DIR_AUTO_CENTER_UP: { int width = maxXOffsetRight - maxXOffsetLeft; newPosition.y = maxYOffsetUp - m->m_size.y; newPosition.x = maxXOffsetLeft + (width - m->m_size.x) / 2; break; } case eAutoDirs::DIR_AUTO_CENTER_DOWN: { int width = maxXOffsetRight - maxXOffsetLeft; newPosition.y = maxYOffsetDown; newPosition.x = maxXOffsetLeft + (width - m->m_size.x) / 2; break; } case eAutoDirs::DIR_AUTO_CENTER_LEFT: { int height = maxYOffsetDown - maxYOffsetUp; newPosition.x = maxXOffsetLeft - m->m_size.x; newPosition.y = maxYOffsetUp + (height - m->m_size.y) / 2; break; } case eAutoDirs::DIR_AUTO_CENTER_RIGHT: { int height = maxYOffsetDown - maxYOffsetUp; newPosition.x = maxXOffsetRight; newPosition.y = maxYOffsetUp + (height - m->m_size.y) / 2; break; } default: UNREACHABLE(); } Debug::log(LOG, "arrangeMonitors: {} auto {:j}", m->m_name, m->m_position); m->moveTo(newPosition); arranged.emplace_back(m); } // reset maxXOffsetRight (reuse) // and set xwayland positions aka auto for all maxXOffsetRight = 0; for (auto const& m : m_monitors) { Debug::log(LOG, "arrangeMonitors: {} xwayland [{}, {}]", m->m_name, maxXOffsetRight, 0); m->m_xwaylandPosition = {maxXOffsetRight, 0}; maxXOffsetRight += (*PXWLFORCESCALEZERO ? m->m_transformedSize.x : m->m_size.x); if (*PXWLFORCESCALEZERO) m->m_xwaylandScale = m->m_scale; else m->m_xwaylandScale = 1.f; } PROTO::xdgOutput->updateAllOutputs(); } void CCompositor::enterUnsafeState() { if (m_unsafeState) return; Debug::log(LOG, "Entering unsafe state"); if (!m_unsafeOutput->m_enabled) m_unsafeOutput->onConnect(false); m_unsafeState = true; setActiveMonitor(m_unsafeOutput.lock()); } void CCompositor::leaveUnsafeState() { if (!m_unsafeState) return; Debug::log(LOG, "Leaving unsafe state"); m_unsafeState = false; PHLMONITOR pNewMonitor = nullptr; for (auto const& pMonitor : m_monitors) { if (pMonitor->m_output != m_unsafeOutput->m_output) { pNewMonitor = pMonitor; break; } } RASSERT(pNewMonitor, "Tried to leave unsafe without a monitor"); if (m_unsafeOutput->m_enabled) m_unsafeOutput->onDisconnect(); for (auto const& m : m_monitors) { scheduleFrameForMonitor(m); } } void CCompositor::setPreferredScaleForSurface(SP pSurface, double scale) { PROTO::fractional->sendScale(pSurface, scale); pSurface->sendPreferredScale(std::ceil(scale)); const auto PSURFACE = CWLSurface::fromResource(pSurface); if (!PSURFACE) { Debug::log(WARN, "Orphaned CWLSurfaceResource {:x} in setPreferredScaleForSurface", (uintptr_t)pSurface.get()); return; } PSURFACE->m_lastScaleFloat = scale; PSURFACE->m_lastScaleInt = static_cast(std::ceil(scale)); } void CCompositor::setPreferredTransformForSurface(SP pSurface, wl_output_transform transform) { pSurface->sendPreferredTransform(transform); const auto PSURFACE = CWLSurface::fromResource(pSurface); if (!PSURFACE) { Debug::log(WARN, "Orphaned CWLSurfaceResource {:x} in setPreferredTransformForSurface", (uintptr_t)pSurface.get()); return; } PSURFACE->m_lastTransform = transform; } void CCompositor::updateSuspendedStates() { for (auto const& w : g_pCompositor->m_windows) { if (!w->m_isMapped) continue; w->setSuspended(w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible()); } } static void checkDefaultCursorWarp(PHLMONITOR monitor) { static auto PCURSORMONITOR = CConfigValue("cursor:default_monitor"); static bool cursorDefaultDone = false; static bool firstLaunch = true; const auto POS = monitor->middle(); // by default, cursor should be set to first monitor detected // this is needed as a default if the monitor given in config above doesn't exist if (firstLaunch) { firstLaunch = false; g_pCompositor->warpCursorTo(POS, true); g_pInputManager->refocus(); return; } if (!cursorDefaultDone && *PCURSORMONITOR != STRVAL_EMPTY) { if (*PCURSORMONITOR == monitor->m_name) { cursorDefaultDone = true; g_pCompositor->warpCursorTo(POS, true); g_pInputManager->refocus(); return; } } // modechange happend check if cursor is on that monitor and warp it to middle to not place it out of bounds if resolution changed. if (g_pCompositor->getMonitorFromCursor() == monitor) { g_pCompositor->warpCursorTo(POS, true); g_pInputManager->refocus(); } } void CCompositor::onNewMonitor(SP output) { // add it to real auto PNEWMONITOR = g_pCompositor->m_realMonitors.emplace_back(makeShared(output)); if (std::string("HEADLESS-1") == output->name) { g_pCompositor->m_unsafeOutput = PNEWMONITOR; output->name = "FALLBACK"; // we are allowed to do this :) } Debug::log(LOG, "New output with name {}", output->name); PNEWMONITOR->m_name = output->name; PNEWMONITOR->m_self = PNEWMONITOR; const bool FALLBACK = g_pCompositor->m_unsafeOutput ? output == g_pCompositor->m_unsafeOutput->m_output : false; PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->m_isUnsafeFallback = FALLBACK; EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR); if (!FALLBACK) PNEWMONITOR->onConnect(false); if (!PNEWMONITOR->m_enabled || FALLBACK) return; // ready to process if we have a real monitor if ((!g_pHyprRenderer->m_mostHzMonitor || PNEWMONITOR->m_refreshRate > g_pHyprRenderer->m_mostHzMonitor->m_refreshRate) && PNEWMONITOR->m_enabled) g_pHyprRenderer->m_mostHzMonitor = PNEWMONITOR; g_pCompositor->m_readyToProcess = true; g_pConfigManager->m_wantsMonitorReload = true; g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR, IOutput::AQ_SCHEDULE_NEW_MONITOR); checkDefaultCursorWarp(PNEWMONITOR); for (auto const& w : g_pCompositor->m_windows) { if (w->m_monitor == PNEWMONITOR) { w->m_lastSurfaceMonitorID = MONITOR_INVALID; w->updateSurfaceScaleTransformDetails(); } } g_pHyprRenderer->damageMonitor(PNEWMONITOR); PNEWMONITOR->m_frameScheduler->onFrame(); if (PROTO::colorManagement && shouldChangePreferredImageDescription()) { Debug::log(ERR, "FIXME: color management protocol is enabled, need a preferred image description id"); PROTO::colorManagement->onImagePreferredChanged(0); } } SImageDescription CCompositor::getPreferredImageDescription() { if (!PROTO::colorManagement) { Debug::log(ERR, "FIXME: color management protocol is not enabled, returning empty image description"); return SImageDescription{}; } Debug::log(WARN, "FIXME: color management protocol is enabled, determine correct preferred image description"); // should determine some common settings to avoid unnecessary transformations while keeping maximum displayable precision return m_monitors.size() == 1 ? m_monitors[0]->m_imageDescription : SImageDescription{.primaries = NColorPrimaries::BT709}; } bool CCompositor::shouldChangePreferredImageDescription() { Debug::log(WARN, "FIXME: color management protocol is enabled and outputs changed, check preferred image description changes"); return false; } void CCompositor::ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace) { if (!m_lastMonitor) return; for (const auto& rule : rules) { if (!rule.isPersistent) continue; PHLWORKSPACE PWORKSPACE = nullptr; if (pWorkspace) { if (pWorkspace->matchesStaticSelector(rule.workspaceString)) PWORKSPACE = pWorkspace; else continue; } const auto PMONITOR = getMonitorFromString(rule.monitor); if (!rule.monitor.empty() && !PMONITOR) continue; // don't do anything yet, as the monitor is not yet present. if (!PWORKSPACE) { WORKSPACEID id = rule.workspaceId; std::string wsname = rule.workspaceName; if (id == WORKSPACE_INVALID) { const auto R = getWorkspaceIDNameFromString(rule.workspaceString); id = R.id; wsname = R.name; } if (id == WORKSPACE_INVALID) { Debug::log(ERR, "ensurePersistentWorkspacesPresent: couldn't resolve id for workspace {}", rule.workspaceString); continue; } PWORKSPACE = getWorkspaceByID(id); if (!PWORKSPACE) createNewWorkspace(id, PMONITOR ? PMONITOR->m_id : m_lastMonitor->m_id, wsname, false); } if (PWORKSPACE) PWORKSPACE->m_persistent = true; if (!PMONITOR) { Debug::log(ERR, "ensurePersistentWorkspacesPresent: couldn't resolve monitor for {}, skipping", rule.monitor); continue; } if (PWORKSPACE) { if (PWORKSPACE->m_monitor == PMONITOR) { Debug::log(LOG, "ensurePersistentWorkspacesPresent: workspace persistent {} already on {}", rule.workspaceString, PMONITOR->m_name); continue; } Debug::log(LOG, "ensurePersistentWorkspacesPresent: workspace persistent {} not on {}, moving", rule.workspaceString, PMONITOR->m_name); moveWorkspaceToMonitor(PWORKSPACE, PMONITOR); continue; } } // cleanup old sanityCheckWorkspaces(); }