Hyprland/src/Compositor.cpp
Tom Englund 7c7a84ff60
internal: more profiling less calls and local copies (#8300)
* compositor: reduce amount of window box copies

mousemoveunified can call this very frequently, the cbox copying
actually shows up as an impact in such cases, move it down in the scope
and only do it when necessery.

* core: constify and reference frequent calls

profiling shows these as frequent called functions try to reduce the
amount of copies with references and const the variables.

* pointermgr: remove not used local copy, const ref

remove unneded local copies and const ref cursorsize.

* inputmgr: reduce amount of calls to vectortowindow

the amount of calls to g_pCompositor->vectorToWindowUnified fast ramps
up in cpu usage with enough windows existing and moving the mouse, move
the PWINDOWIDEAL up and reuse it if its already the same.

* protocol: compositor remove unused local copy

remove unused local copy of accumulateCurrentBufferDamage and const
previousBuffer.

* renderer: reduce scope of variables and refactor

move a few variables down in their scopes to reduce the amount of calls
and copies when not needed, also add one more for loop in
renderWorkspaceWindows and store the windows in a vector with
weakpointers that should be rendered, this adds a loop but reduces the
amount of repeated calls to shouldRenderWindow and also makes the rest
of the loops go over way smaller vector when many windows exist.
2024-10-30 23:20:32 +00:00

3092 lines
109 KiB
C++

#include "Compositor.hpp"
#include "debug/Log.hpp"
#include "helpers/Splashes.hpp"
#include "config/ConfigValue.hpp"
#include "managers/CursorManager.hpp"
#include "managers/TokenManager.hpp"
#include "managers/PointerManager.hpp"
#include "managers/SeatManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp"
#include <aquamarine/output/Output.hpp>
#include <bit>
#include <ctime>
#include <random>
#include <print>
#include <cstring>
#include <filesystem>
#include <unordered_set>
#include "debug/HyprCtl.hpp"
#include "debug/CrashReporter.hpp"
#ifdef USES_SYSTEMD
#include <helpers/SdDaemon.hpp> // for SdNotify
#endif
#include <ranges>
#include "helpers/varlist/VarList.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/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 <hyprutils/string/String.hpp>
#include <aquamarine/input/Input.hpp>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
using namespace Hyprutils::String;
using namespace Aquamarine;
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;
}
void handleUnrecoverableSignal(int sig) {
// remove our handlers
signal(SIGABRT, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
if (g_pHookSystem && g_pHookSystem->m_bCurrentEventPlugin) {
longjmp(g_pHookSystem->m_jbHookFaultJumpBuf, 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);
CrashReporter::createAndSaveCrash(sig);
abort();
}
void handleUserSignal(int sig) {
if (sig == SIGUSR1) {
// means we have to unwind a timed out event
throw std::exception();
}
}
static LogLevel 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;
}
void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) {
Debug::log(aqLevelToHl(level), "[AQ] {}", msg);
}
void CCompositor::bumpNofile() {
if (!getrlimit(RLIMIT_NOFILE, &m_sOriginalNofile))
Debug::log(LOG, "Old rlimit: soft -> {}, hard -> {}", m_sOriginalNofile.rlim_cur, m_sOriginalNofile.rlim_max);
else {
Debug::log(ERR, "Failed to get NOFILE rlimits");
m_sOriginalNofile.rlim_max = 0;
return;
}
rlimit newLimit = m_sOriginalNofile;
newLimit.rlim_cur = newLimit.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &newLimit) < 0) {
Debug::log(ERR, "Failed bumping NOFILE limits higher");
m_sOriginalNofile.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_sOriginalNofile.rlim_max <= 0)
return;
if (setrlimit(RLIMIT_NOFILE, &m_sOriginalNofile) < 0)
Debug::log(ERR, "Failed restoring NOFILE limits");
}
CCompositor::CCompositor() {
m_iHyprlandPID = getpid();
m_szHyprTempDataRoot = std::string{getenv("XDG_RUNTIME_DIR")} + "/hypr";
if (m_szHyprTempDataRoot.starts_with("/hypr")) {
std::println("Bailing out, $XDG_RUNTIME_DIR is invalid");
throw std::runtime_error("CCompositor() failed");
}
if (!m_szHyprTempDataRoot.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_szInstanceSignature = std::format("{}_{}_{}", GIT_COMMIT_HASH, std::time(NULL), distribution(engine));
setenv("HYPRLAND_INSTANCE_SIGNATURE", m_szInstanceSignature.c_str(), true);
if (!std::filesystem::exists(m_szHyprTempDataRoot))
mkdir(m_szHyprTempDataRoot.c_str(), S_IRWXU);
else if (!std::filesystem::is_directory(m_szHyprTempDataRoot)) {
std::println("Bailing out, {} is not a directory", m_szHyprTempDataRoot);
throw std::runtime_error("CCompositor() failed");
}
m_szInstancePath = m_szHyprTempDataRoot + "/" + m_szInstanceSignature;
if (std::filesystem::exists(m_szInstancePath)) {
std::println("Bailing out, {} exists??", m_szInstancePath);
throw std::runtime_error("CCompositor() failed");
}
if (mkdir(m_szInstancePath.c_str(), S_IRWXU) < 0) {
std::println("Bailing out, couldn't create {}", m_szInstancePath);
throw std::runtime_error("CCompositor() failed");
}
Debug::init(m_szInstancePath);
Debug::log(LOG, "Instance Signature: {}", m_szInstanceSignature);
Debug::log(LOG, "Runtime directory: {}", m_szInstancePath);
Debug::log(LOG, "Hyprland PID: {}", m_iHyprlandPID);
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.hyprland.org/Crashes-and-Bugs/\n\n");
setRandomSplash();
Debug::log(LOG, "\nCurrent splash: {}\n\n", m_szCurrentSplash);
bumpNofile();
}
CCompositor::~CCompositor() {
if (!m_bIsShuttingDown)
cleanup();
}
void CCompositor::setRandomSplash() {
std::random_device dev;
std::mt19937 engine(dev());
std::uniform_int_distribution<> distribution(0, SPLASHES.size() - 1);
m_szCurrentSplash = SPLASHES[distribution(engine)];
}
static std::vector<SP<Aquamarine::IOutput>> 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) {
m_sWLDisplay = wl_display_create();
wl_display_set_global_filter(m_sWLDisplay, ::filterGlobals, nullptr);
m_sWLEventLoop = wl_display_get_event_loop(m_sWLDisplay);
// register crit signal handler
m_critSigSource = wl_event_loop_add_signal(m_sWLEventLoop, 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::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_sWLDisplay, 1_MB);
Aquamarine::SBackendOptions options;
options.logFunction = aqLog;
std::vector<Aquamarine::SBackendImplementationOptions> 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_pAqBackend = CBackend::create(implementations, options);
if (!m_pAqBackend) {
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_pAqBackend->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_bInitialized = true;
m_iDRMFD = m_pAqBackend->drmFD();
Debug::log(LOG, "Running on DRMFD: {}", m_iDRMFD);
if (!socketName.empty() && socketFd != -1) {
fcntl(socketFd, F_SETFD, FD_CLOEXEC);
const auto RETVAL = wl_display_add_socket_fd(m_sWLDisplay, socketFd);
if (RETVAL >= 0) {
m_szWLDisplaySocket = 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_sWLDisplay, CANDIDATESTR.c_str());
if (RETVAL >= 0) {
m_szWLDisplaySocket = 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_szWLDisplaySocket.empty()) {
Debug::log(WARN, "All candidates failed, trying wl_display_add_socket_auto");
const auto SOCKETSTR = wl_display_add_socket_auto(m_sWLDisplay);
if (SOCKETSTR)
m_szWLDisplaySocket = SOCKETSTR;
}
if (m_szWLDisplaySocket.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_szWLDisplaySocket.c_str(), 1);
setenv("XDG_SESSION_TYPE", "wayland", 1);
if (!getenv("XDG_CURRENT_DESKTOP")) {
setenv("XDG_CURRENT_DESKTOP", "Hyprland", 1);
m_bDesktopEnvSet = true;
}
initManagers(STAGE_BASICINIT);
initManagers(STAGE_LATE);
for (auto const& o : pendingOutputs) {
onNewMonitor(o);
}
pendingOutputs.clear();
}
void CCompositor::initAllSignals() {
m_pAqBackend->events.newOutput.registerStaticListener(
[this](void* p, std::any data) {
auto output = std::any_cast<SP<Aquamarine::IOutput>>(data);
Debug::log(LOG, "New aquamarine output with name {}", output->name);
if (m_bInitialized)
onNewMonitor(output);
else
pendingOutputs.emplace_back(output);
},
nullptr);
m_pAqBackend->events.newPointer.registerStaticListener(
[](void* data, std::any d) {
auto dev = std::any_cast<SP<Aquamarine::IPointer>>(d);
Debug::log(LOG, "New aquamarine pointer with name {}", dev->getName());
g_pInputManager->newMouse(dev);
g_pInputManager->updateCapabilities();
},
nullptr);
m_pAqBackend->events.newKeyboard.registerStaticListener(
[](void* data, std::any d) {
auto dev = std::any_cast<SP<Aquamarine::IKeyboard>>(d);
Debug::log(LOG, "New aquamarine keyboard with name {}", dev->getName());
g_pInputManager->newKeyboard(dev);
g_pInputManager->updateCapabilities();
},
nullptr);
m_pAqBackend->events.newTouch.registerStaticListener(
[](void* data, std::any d) {
auto dev = std::any_cast<SP<Aquamarine::ITouch>>(d);
Debug::log(LOG, "New aquamarine touch with name {}", dev->getName());
g_pInputManager->newTouchDevice(dev);
g_pInputManager->updateCapabilities();
},
nullptr);
m_pAqBackend->events.newSwitch.registerStaticListener(
[](void* data, std::any d) {
auto dev = std::any_cast<SP<Aquamarine::ISwitch>>(d);
Debug::log(LOG, "New aquamarine switch with name {}", dev->getName());
g_pInputManager->newSwitch(dev);
},
nullptr);
m_pAqBackend->events.newTablet.registerStaticListener(
[](void* data, std::any d) {
auto dev = std::any_cast<SP<Aquamarine::ITablet>>(d);
Debug::log(LOG, "New aquamarine tablet with name {}", dev->getName());
g_pInputManager->newTablet(dev);
},
nullptr);
m_pAqBackend->events.newTabletPad.registerStaticListener(
[](void* data, std::any d) {
auto dev = std::any_cast<SP<Aquamarine::ITabletPad>>(d);
Debug::log(LOG, "New aquamarine tablet pad with name {}", dev->getName());
g_pInputManager->newTabletPad(dev);
},
nullptr);
if (m_pAqBackend->hasSession()) {
m_pAqBackend->session->events.changeActive.registerStaticListener(
[this](void*, std::any) {
if (m_pAqBackend->session->active) {
Debug::log(LOG, "Session got activated!");
m_bSessionActive = true;
for (auto const& m : m_vMonitors) {
scheduleFrameForMonitor(m);
g_pHyprRenderer->applyMonitorRule(m, &m->activeMonitorRule, true);
}
g_pConfigManager->m_bWantsMonitorReload = true;
g_pCursorManager->syncGsettings();
} else {
Debug::log(LOG, "Session got deactivated!");
m_bSessionActive = false;
for (auto const& m : m_vMonitors) {
m->noFrameSchedule = true;
m->framesToSkip = 1;
}
}
},
nullptr);
}
}
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_bDesktopEnvSet)
unsetenv("XDG_CURRENT_DESKTOP");
if (m_pAqBackend->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";
g_pKeybindManager->spawn(CMD);
}
}
void CCompositor::stopCompositor() {
Debug::log(LOG, "Hyprland is stopping!");
// this stops the wayland loop, wl_display_run
wl_display_terminate(m_sWLDisplay);
m_bIsShuttingDown = true;
}
void CCompositor::cleanup() {
if (!m_sWLDisplay)
return;
signal(SIGABRT, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
removeLockFile();
m_bIsShuttingDown = true;
Debug::shuttingDown = true;
#ifdef USES_SYSTEMD
if (Systemd::SdBooted() > 0 && !envEnabled("HYPRLAND_NO_SD_NOTIFY"))
Systemd::SdNotify(0, "STOPPING=1");
#endif
cleanEnvironment();
// unload all remaining plugins while the compositor is
// still in a normal working state.
g_pPluginSystem->unloadAllPlugins();
m_pLastFocus.reset();
m_pLastWindow.reset();
m_vWorkspaces.clear();
m_vWindows.clear();
for (auto const& m : m_vMonitors) {
g_pHyprOpenGL->destroyMonitorResources(m);
m->output->state->setEnabled(false);
m->state.commit();
}
g_pXWayland.reset();
m_vMonitors.clear();
wl_display_destroy_clients(g_pCompositor->m_sWLDisplay);
removeAllSignals();
g_pInputManager.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_pThreadManager.reset();
g_pConfigManager.reset();
g_pLayoutManager.reset();
g_pHyprError.reset();
g_pConfigManager.reset();
g_pAnimationManager.reset();
g_pKeybindManager.reset();
g_pHookSystem.reset();
g_pWatchdog.reset();
g_pXWaylandManager.reset();
g_pPointerManager.reset();
g_pSeatManager.reset();
g_pHyprCtl.reset();
g_pEventLoopManager.reset();
if (m_pAqBackend)
m_pAqBackend.reset();
if (m_critSigSource)
wl_event_source_remove(m_critSigSource);
// this frees all wayland resources, including sockets
wl_display_destroy(m_sWLDisplay);
Debug::close();
}
void CCompositor::initManagers(eManagersInitStage stage) {
switch (stage) {
case STAGE_PRIORITY: {
Debug::log(LOG, "Creating the EventLoopManager!");
g_pEventLoopManager = std::make_unique<CEventLoopManager>(m_sWLDisplay, m_sWLEventLoop);
Debug::log(LOG, "Creating the HookSystem!");
g_pHookSystem = std::make_unique<CHookSystemManager>();
Debug::log(LOG, "Creating the KeybindManager!");
g_pKeybindManager = std::make_unique<CKeybindManager>();
Debug::log(LOG, "Creating the AnimationManager!");
g_pAnimationManager = std::make_unique<CAnimationManager>();
Debug::log(LOG, "Creating the ConfigManager!");
g_pConfigManager = std::make_unique<CConfigManager>();
Debug::log(LOG, "Creating the CHyprError!");
g_pHyprError = std::make_unique<CHyprError>();
Debug::log(LOG, "Creating the LayoutManager!");
g_pLayoutManager = std::make_unique<CLayoutManager>();
Debug::log(LOG, "Creating the TokenManager!");
g_pTokenManager = std::make_unique<CTokenManager>();
g_pConfigManager->init();
g_pWatchdog = std::make_unique<CWatchdog>(); // requires config
// wait for watchdog to initialize to not hit data races in reading config values.
while (!g_pWatchdog->m_bWatchdogInitialized) {
std::this_thread::yield();
}
Debug::log(LOG, "Creating the PointerManager!");
g_pPointerManager = std::make_unique<CPointerManager>();
Debug::log(LOG, "Creating the EventManager!");
g_pEventManager = std::make_unique<CEventManager>();
} break;
case STAGE_BASICINIT: {
Debug::log(LOG, "Creating the CHyprOpenGLImpl!");
g_pHyprOpenGL = std::make_unique<CHyprOpenGLImpl>();
Debug::log(LOG, "Creating the ProtocolManager!");
g_pProtocolManager = std::make_unique<CProtocolManager>();
Debug::log(LOG, "Creating the SeatManager!");
g_pSeatManager = std::make_unique<CSeatManager>();
} break;
case STAGE_LATE: {
Debug::log(LOG, "Creating the ThreadManager!");
g_pThreadManager = std::make_unique<CThreadManager>();
Debug::log(LOG, "Creating CHyprCtl");
g_pHyprCtl = std::make_unique<CHyprCtl>();
Debug::log(LOG, "Creating the InputManager!");
g_pInputManager = std::make_unique<CInputManager>();
Debug::log(LOG, "Creating the HyprRenderer!");
g_pHyprRenderer = std::make_unique<CHyprRenderer>();
Debug::log(LOG, "Creating the XWaylandManager!");
g_pXWaylandManager = std::make_unique<CHyprXWaylandManager>();
Debug::log(LOG, "Creating the SessionLockManager!");
g_pSessionLockManager = std::make_unique<CSessionLockManager>();
Debug::log(LOG, "Creating the HyprDebugOverlay!");
g_pDebugOverlay = std::make_unique<CHyprDebugOverlay>();
Debug::log(LOG, "Creating the HyprNotificationOverlay!");
g_pHyprNotificationOverlay = std::make_unique<CHyprNotificationOverlay>();
Debug::log(LOG, "Creating the PluginSystem!");
g_pPluginSystem = std::make_unique<CPluginSystem>();
g_pConfigManager->handlePluginLoads();
Debug::log(LOG, "Creating the DecorationPositioner!");
g_pDecorationPositioner = std::make_unique<CDecorationPositioner>();
Debug::log(LOG, "Creating the CursorManager!");
g_pCursorManager = std::make_unique<CCursorManager>();
Debug::log(LOG, "Starting XWayland");
g_pXWayland = std::make_unique<CXWayland>(g_pCompositor->m_bEnableXwayland);
} break;
default: UNREACHABLE();
}
}
void CCompositor::createLockFile() {
const auto PATH = m_szInstancePath + "/hyprland.lock";
std::ofstream ofs(PATH, std::ios::trunc);
ofs << m_iHyprlandPID << "\n" << m_szWLDisplaySocket << "\n";
ofs.close();
}
void CCompositor::removeLockFile() {
const auto PATH = m_szInstancePath + "/hyprland.lock";
if (std::filesystem::exists(PATH))
std::filesystem::remove(PATH);
}
void CCompositor::prepareFallbackOutput() {
// create a backup monitor
SP<Aquamarine::IBackendImplementation> headless;
for (auto const& impl : m_pAqBackend->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_pAqBackend->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";
g_pKeybindManager->spawn(CMD);
}
Debug::log(LOG, "Running on WAYLAND_DISPLAY: {}", m_szWLDisplaySocket);
prepareFallbackOutput();
g_pHyprRenderer->setCursorFromName("left_ptr");
#ifdef USES_SYSTEMD
if (Systemd::SdBooted() > 0) {
// tell systemd that we are ready so it can start other bond, following, related units
if (!envEnabled("HYPRLAND_NO_SD_NOTIFY"))
Systemd::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_vMonitors) {
if (m->ID == id) {
return m;
}
}
return nullptr;
}
PHLMONITOR CCompositor::getMonitorFromName(const std::string& name) {
for (auto const& m : m_vMonitors) {
if (m->szName == name) {
return m;
}
}
return nullptr;
}
PHLMONITOR CCompositor::getMonitorFromDesc(const std::string& desc) {
for (auto const& m : m_vMonitors) {
if (m->szDescription.starts_with(desc))
return m;
}
return nullptr;
}
PHLMONITOR CCompositor::getMonitorFromCursor() {
return getMonitorFromVector(g_pPointerManager->position());
}
PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) {
PHLMONITOR mon;
for (auto const& m : m_vMonitors) {
if (CBox{m->vecPosition, m->vecSize}.containsPoint(point)) {
mon = m;
break;
}
}
if (!mon) {
float bestDistance = 0.f;
PHLMONITOR pBestMon;
for (auto const& m : m_vMonitors) {
float dist = vecToRectDistanceSquared(point, m->vecPosition, m->vecPosition + m->vecSize);
if (dist < bestDistance || !pBestMon) {
bestDistance = dist;
pBestMon = m;
}
}
if (!pBestMon) { // ?????
Debug::log(WARN, "getMonitorFromVector no close mon???");
return m_vMonitors.front();
}
return pBestMon;
}
return mon;
}
void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) {
if (!pWindow->m_bFadingOut) {
EMIT_HOOK_EVENT("destroyWindow", pWindow);
std::erase_if(m_vWindows, [&](SP<CWindow>& el) { return el == pWindow; });
std::erase_if(m_vWindowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; });
}
}
bool CCompositor::monitorExists(PHLMONITOR pMonitor) {
for (auto const& m : m_vRealMonitors) {
if (m == pMonitor)
return true;
}
return false;
}
PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) {
const auto PMONITOR = getMonitorFromVector(pos);
static auto PRESIZEONBORDER = CConfigValue<Hyprlang::INT>("general:resize_on_border");
static auto PBORDERSIZE = CConfigValue<Hyprlang::INT>("general:border_size");
static auto PBORDERGRABEXTEND = CConfigValue<Hyprlang::INT>("general:extend_border_grab_area");
static auto PSPECIALFALLTHRU = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0;
// pinned windows on top of floating regardless
if (properties & ALLOW_FLOATING) {
for (auto const& w : m_vWindows | std::views::reverse) {
if (w->m_bIsFloating && w->m_bIsMapped && !w->isHidden() && !w->m_bX11ShouldntFocus && w->m_bPinned && !w->m_sWindowData.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_bIsX11) {
if (w->hasPopupAt(pos))
return w;
}
}
}
}
auto windowForWorkspace = [&](bool special) -> PHLWINDOW {
auto floating = [&](bool aboveFullscreen) -> PHLWINDOW {
for (auto const& w : m_vWindows | std::views::reverse) {
if (special && !w->onSpecialWorkspace()) // because special floating may creep up into regular
continue;
const auto PWINDOWMONITOR = w->m_pMonitor.lock();
// to avoid focusing windows behind special workspaces from other monitors
if (!*PSPECIALFALLTHRU && PWINDOWMONITOR && PWINDOWMONITOR->activeSpecialWorkspace && w->m_pWorkspace != PWINDOWMONITOR->activeSpecialWorkspace) {
const auto BB = w->getWindowBoxUnified(properties);
if (BB.x >= PWINDOWMONITOR->vecPosition.x && BB.y >= PWINDOWMONITOR->vecPosition.y &&
BB.x + BB.width <= PWINDOWMONITOR->vecPosition.x + PWINDOWMONITOR->vecSize.x &&
BB.y + BB.height <= PWINDOWMONITOR->vecPosition.y + PWINDOWMONITOR->vecSize.y)
continue;
}
if (w->m_bIsFloating && w->m_bIsMapped && isWorkspaceVisible(w->m_pWorkspace) && !w->isHidden() && !w->m_bPinned && !w->m_sWindowData.noFocus.valueOrDefault() &&
w != pIgnoreWindow && (!aboveFullscreen || w->m_bCreatedOverFullscreen)) {
// OR windows should add focus to parent
if (w->m_bX11ShouldntFocus && !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_bIsX11 && w->isX11OverrideRedirect() && !w->m_pXWaylandSurface->wantsFocus()) {
// Override Redirect
return g_pCompositor->m_pLastWindow.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_bIsX11) {
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_bHasFullscreenWindow)
return getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID);
auto found = floating(false);
if (found)
return found;
// for windows, we need to check their extensions too, first.
for (auto const& w : m_vWindows) {
if (special != w->onSpecialWorkspace())
continue;
if (!w->m_bIsX11 && !w->m_bIsFloating && w->m_bIsMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_bX11ShouldntFocus &&
!w->m_sWindowData.noFocus.valueOrDefault() && w != pIgnoreWindow) {
if (w->hasPopupAt(pos))
return w;
}
}
for (auto const& w : m_vWindows) {
if (special != w->onSpecialWorkspace())
continue;
if (!w->m_bIsFloating && w->m_bIsMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_bX11ShouldntFocus && !w->m_sWindowData.noFocus.valueOrDefault() &&
w != pIgnoreWindow) {
CBox box = (properties & USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_vPosition, w->m_vSize};
if (box.containsPoint(pos))
return w;
}
}
return nullptr;
};
// special workspace
if (PMONITOR->activeSpecialWorkspace && !*PSPECIALFALLTHRU)
return windowForWorkspace(true);
if (PMONITOR->activeSpecialWorkspace) {
const auto PWINDOW = windowForWorkspace(true);
if (PWINDOW)
return PWINDOW;
}
return windowForWorkspace(false);
}
SP<CWLSurfaceResource> CCompositor::vectorWindowToSurface(const Vector2D& pos, PHLWINDOW pWindow, Vector2D& sl) {
if (!validMapped(pWindow))
return nullptr;
RASSERT(!pWindow->m_bIsX11, "Cannot call vectorWindowToSurface on an X11 window!");
// try popups first
const auto PPOPUP = pWindow->m_pPopupHead->at(pos);
if (PPOPUP) {
const auto OFF = PPOPUP->coordsRelativeToParent();
sl = pos - pWindow->m_vRealPosition.goal() - OFF;
return PPOPUP->m_pWLSurface->resource();
}
auto [surf, local] = pWindow->m_pWLSurface->resource()->at(pos - pWindow->m_vRealPosition.goal(), true);
if (surf) {
sl = local;
return surf;
}
return nullptr;
}
Vector2D CCompositor::vectorToSurfaceLocal(const Vector2D& vec, PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface) {
if (!validMapped(pWindow))
return {};
if (pWindow->m_bIsX11)
return vec - pWindow->m_vRealPosition.goal();
const auto PPOPUP = pWindow->m_pPopupHead->at(vec);
if (PPOPUP)
return vec - PPOPUP->coordsGlobal();
std::tuple<SP<CWLSurfaceResource>, Vector2D> iterData = {pSurface, {-1337, -1337}};
pWindow->m_pWLSurface->resource()->breadthfirst(
[](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) {
const auto PDATA = (std::tuple<SP<CWLSurfaceResource>, Vector2D>*)data;
if (surf == std::get<0>(*PDATA))
std::get<1>(*PDATA) = offset;
},
&iterData);
CBox geom = pWindow->m_pXDGSurface->current.geometry;
if (std::get<1>(iterData) == Vector2D{-1337, -1337})
return vec - pWindow->m_vRealPosition.goal();
return vec - pWindow->m_vRealPosition.goal() - std::get<1>(iterData) + Vector2D{geom.x, geom.y};
}
PHLMONITOR CCompositor::getMonitorFromOutput(SP<Aquamarine::IOutput> out) {
for (auto const& m : m_vMonitors) {
if (m->output == out) {
return m;
}
}
return nullptr;
}
PHLMONITOR CCompositor::getRealMonitorFromOutput(SP<Aquamarine::IOutput> out) {
for (auto const& m : m_vRealMonitors) {
if (m->output == out) {
return m;
}
}
return nullptr;
}
void CCompositor::focusWindow(PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface) {
static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse");
static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
if (g_pSessionLockManager->isSessionLocked()) {
Debug::log(LOG, "Refusing a keyboard focus to a window because of a sessionlock");
return;
}
if (!g_pInputManager->m_dExclusiveLSes.empty()) {
Debug::log(LOG, "Refusing a keyboard focus to a window because of an exclusive ls");
return;
}
if (pWindow && pWindow->m_bIsX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_pXWaylandSurface->wantsFocus())
return;
g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow);
if (!pWindow || !validMapped(pWindow)) {
if (m_pLastWindow.expired() && !pWindow)
return;
const auto PLASTWINDOW = m_pLastWindow.lock();
m_pLastWindow.reset();
if (PLASTWINDOW && PLASTWINDOW->m_bIsMapped) {
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_pLastFocus.reset();
g_pInputManager->recheckIdleInhibitorStatus();
return;
}
if (pWindow->m_sWindowData.noFocus.valueOrDefault()) {
Debug::log(LOG, "Ignoring focus to nofocus window!");
return;
}
if (m_pLastWindow.lock() == pWindow && g_pSeatManager->state.keyboardFocus == pSurface)
return;
if (pWindow->m_bPinned)
pWindow->m_pWorkspace = m_pLastMonitor->activeWorkspace;
const auto PMONITOR = pWindow->m_pMonitor.lock();
if (!isWorkspaceVisible(pWindow->m_pWorkspace)) {
const auto PWORKSPACE = pWindow->m_pWorkspace;
// This is to fix incorrect feedback on the focus history.
PWORKSPACE->m_pLastFocusedWindow = pWindow;
PWORKSPACE->rememberPrevWorkspace(m_pLastMonitor->activeWorkspace);
if (PWORKSPACE->m_bIsSpecialWorkspace)
m_pLastMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor
else
PMONITOR->changeWorkspace(PWORKSPACE, false, true);
// changeworkspace already calls focusWindow
return;
}
const auto PLASTWINDOW = m_pLastWindow.lock();
m_pLastWindow = 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->activeSpecialWorkspace && PMONITOR->activeSpecialWorkspace != pWindow->m_pWorkspace && !pWindow->m_bPinned && !*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_bIsMapped) {
PLASTWINDOW->updateDynamicRules();
updateWindowAnimatedDecorationValues(PLASTWINDOW);
if (!pWindow->m_bIsX11 || !pWindow->isX11OverrideRedirect())
g_pXWaylandManager->activateWindow(PLASTWINDOW, false);
}
m_pLastWindow = PLASTWINDOW;
const auto PWINDOWSURFACE = pSurface ? pSurface : pWindow->m_pWLSurface->resource();
focusSurface(PWINDOWSURFACE, pWindow);
g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow
pWindow->updateDynamicRules();
updateWindowAnimatedDecorationValues(pWindow);
if (pWindow->m_bIsUrgent)
pWindow->m_bIsUrgent = false;
// Send an event
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", pWindow->m_szClass + "," + pWindow->m_szTitle});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", std::format("{:x}", (uintptr_t)pWindow.get())});
EMIT_HOOK_EVENT("activeWindow", pWindow);
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow);
g_pInputManager->recheckIdleInhibitorStatus();
// move to front of the window history
const auto HISTORYPIVOT = std::find_if(m_vWindowFocusHistory.begin(), m_vWindowFocusHistory.end(), [&](const auto& other) { return other.lock() == pWindow; });
if (HISTORYPIVOT == m_vWindowFocusHistory.end()) {
Debug::log(ERR, "BUG THIS: {} has no pivot in history", pWindow);
} else {
std::rotate(m_vWindowFocusHistory.begin(), HISTORYPIVOT, HISTORYPIVOT + 1);
}
if (*PFOLLOWMOUSE == 0)
g_pInputManager->sendMotionEventsToFocused();
}
void CCompositor::focusSurface(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWindowOwner) {
if (g_pSeatManager->state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->state.keyboardFocus == pWindowOwner->m_pWLSurface->resource()))
return; // Don't focus when already focused on this.
if (g_pSessionLockManager->isSessionLocked() && pSurface && !g_pSessionLockManager->isSurfaceSessionLock(pSurface))
return;
if (g_pSeatManager->seatGrab && !g_pSeatManager->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_pLastFocus.lock();
// Unfocus last surface if should
if (m_pLastFocus && !pWindowOwner)
g_pXWaylandManager->activateSurface(m_pLastFocus.lock(), false);
if (!pSurface) {
g_pSeatManager->setKeyboardFocus(nullptr);
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); // unfocused
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
EMIT_HOOK_EVENT("keyboardFocus", (SP<CWLSurfaceResource>)nullptr);
m_pLastFocus.reset();
return;
}
if (g_pSeatManager->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_pLastFocus = 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<CWLSurfaceResource> CCompositor::vectorToLayerPopupSurface(const Vector2D& pos, PHLMONITOR monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) {
for (auto const& lsl : monitor->m_aLayerSurfaceLayers | std::views::reverse) {
for (auto const& ls : lsl | std::views::reverse) {
if (ls->fadingOut || !ls->layerSurface || (ls->layerSurface && !ls->layerSurface->mapped) || ls->alpha.value() == 0.f)
continue;
auto SURFACEAT = ls->popupHead->at(pos, true);
if (SURFACEAT) {
*ppLayerSurfaceFound = ls.lock();
*sCoords = pos - SURFACEAT->coordsGlobal();
return SURFACEAT->m_pWLSurface->resource();
}
}
}
return nullptr;
}
SP<CWLSurfaceResource> CCompositor::vectorToLayerSurface(const Vector2D& pos, std::vector<PHLLSREF>* layerSurfaces, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) {
for (auto const& ls : *layerSurfaces | std::views::reverse) {
if (ls->fadingOut || !ls->layerSurface || (ls->layerSurface && !ls->layerSurface->surface->mapped) || ls->alpha.value() == 0.f)
continue;
auto [surf, local] = ls->layerSurface->surface->at(pos - ls->geometry.pos(), true);
if (surf) {
if (surf->current.input.empty())
continue;
*ppLayerSurfaceFound = ls.lock();
*sCoords = local;
return surf;
}
}
return nullptr;
}
PHLWINDOW CCompositor::getWindowFromSurface(SP<CWLSurfaceResource> pSurface) {
if (!pSurface || !pSurface->hlSurface)
return nullptr;
return pSurface->hlSurface->getWindow();
}
PHLWINDOW CCompositor::getWindowFromHandle(uint32_t handle) {
for (auto const& w : m_vWindows) {
if ((uint32_t)(((uint64_t)w.get()) & 0xFFFFFFFF) == handle) {
return w;
}
}
return nullptr;
}
PHLWINDOW CCompositor::getFullscreenWindowOnWorkspace(const WORKSPACEID& ID) {
for (auto const& w : m_vWindows) {
if (w->workspaceID() == ID && w->isFullscreen())
return w;
}
return nullptr;
}
bool CCompositor::isWorkspaceVisible(PHLWORKSPACE w) {
return valid(w) && w->m_bVisible;
}
bool CCompositor::isWorkspaceVisibleNotCovered(PHLWORKSPACE w) {
if (!valid(w))
return false;
const auto PMONITOR = w->m_pMonitor.lock();
if (PMONITOR->activeSpecialWorkspace)
return PMONITOR->activeSpecialWorkspace->m_iID == w->m_iID;
return PMONITOR->activeWorkspace->m_iID == w->m_iID;
}
PHLWORKSPACE CCompositor::getWorkspaceByID(const WORKSPACEID& id) {
for (auto const& w : m_vWorkspaces) {
if (w->m_iID == id && !w->inert())
return w;
}
return nullptr;
}
void CCompositor::sanityCheckWorkspaces() {
auto it = m_vWorkspaces.begin();
while (it != m_vWorkspaces.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_bPersistent && WORKSPACE.strongRef() == 1) {
it = m_vWorkspaces.erase(it);
continue;
}
++it;
}
}
int CCompositor::getWindowsOnWorkspace(const WORKSPACEID& id, std::optional<bool> onlyTiled, std::optional<bool> onlyVisible) {
int no = 0;
for (auto const& w : m_vWindows) {
if (w->workspaceID() != id || !w->m_bIsMapped)
continue;
if (onlyTiled.has_value() && w->m_bIsFloating == onlyTiled.value())
continue;
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
continue;
no++;
}
return no;
}
int CCompositor::getGroupsOnWorkspace(const WORKSPACEID& id, std::optional<bool> onlyTiled, std::optional<bool> onlyVisible) {
int no = 0;
for (auto const& w : m_vWindows) {
if (w->workspaceID() != id || !w->m_bIsMapped)
continue;
if (!w->m_sGroupData.head)
continue;
if (onlyTiled.has_value() && w->m_bIsFloating == onlyTiled.value())
continue;
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
continue;
no++;
}
return no;
}
PHLWINDOW CCompositor::getUrgentWindow() {
for (auto const& w : m_vWindows) {
if (w->m_bIsMapped && w->m_bIsUrgent)
return w;
}
return nullptr;
}
bool CCompositor::hasUrgentWindowOnWorkspace(const WORKSPACEID& id) {
for (auto const& w : m_vWindows) {
if (w->workspaceID() == id && w->m_bIsMapped && w->m_bIsUrgent)
return true;
}
return false;
}
PHLWINDOW CCompositor::getFirstWindowOnWorkspace(const WORKSPACEID& id) {
for (auto const& w : m_vWindows) {
if (w->workspaceID() == id && w->m_bIsMapped && !w->isHidden())
return w;
}
return nullptr;
}
PHLWINDOW CCompositor::getTopLeftWindowOnWorkspace(const WORKSPACEID& id) {
const auto PWORKSPACE = getWorkspaceByID(id);
if (!PWORKSPACE)
return nullptr;
const auto PMONITOR = PWORKSPACE->m_pMonitor.lock();
for (auto const& w : m_vWindows) {
if (w->workspaceID() != id || !w->m_bIsMapped || w->isHidden())
continue;
const auto WINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved();
if (WINDOWIDEALBB.x <= PMONITOR->vecPosition.x + 1 && WINDOWIDEALBB.y <= PMONITOR->vecPosition.y + 1)
return w;
}
return nullptr;
}
bool CCompositor::isWindowActive(PHLWINDOW pWindow) {
if (m_pLastWindow.expired() && !m_pLastFocus)
return false;
if (!pWindow->m_bIsMapped)
return false;
const auto PSURFACE = pWindow->m_pWLSurface->resource();
return PSURFACE == m_pLastFocus || pWindow == m_pLastWindow.lock();
}
void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) {
if (!validMapped(pWindow))
return;
if (pWindow == (top ? m_vWindows.back() : m_vWindows.front()))
return;
auto moveToZ = [&](PHLWINDOW pw, bool top) -> void {
if (top) {
for (auto it = m_vWindows.begin(); it != m_vWindows.end(); ++it) {
if (*it == pw) {
std::rotate(it, it + 1, m_vWindows.end());
break;
}
}
} else {
for (auto it = m_vWindows.rbegin(); it != m_vWindows.rend(); ++it) {
if (*it == pw) {
std::rotate(it, it + 1, m_vWindows.rend());
break;
}
}
}
if (pw->m_bIsMapped)
g_pHyprRenderer->damageMonitor(pw->m_pMonitor.lock());
};
if (top)
pWindow->m_bCreatedOverFullscreen = true;
if (!pWindow->m_bIsX11)
moveToZ(pWindow, top);
else {
// move X11 window stack
std::deque<PHLWINDOW> toMove;
auto x11Stack = [&](PHLWINDOW pw, bool top, auto&& x11Stack) -> void {
if (top)
toMove.emplace_back(pw);
else
toMove.emplace_front(pw);
for (auto const& w : m_vWindows) {
if (w->m_bIsMapped && !w->isHidden() && w->m_bIsX11 && w->X11TransientFor() == pw && w != pw && std::find(toMove.begin(), toMove.end(), w) == toMove.end()) {
x11Stack(w, top, x11Stack);
}
}
};
x11Stack(pWindow, top, x11Stack);
for (auto it : toMove) {
moveToZ(it, top);
}
}
}
void CCompositor::cleanupFadingOut(const MONITORID& monid) {
for (auto const& ww : m_vWindowsFadingOut) {
auto w = ww.lock();
if (w->monitorID() != monid && w->m_pMonitor)
continue;
if (!w->m_bFadingOut || w->m_fAlpha.value() == 0.f) {
w->m_bFadingOut = false;
if (!w->m_bReadyToDelete)
continue;
removeWindowFromVectorSafe(w);
w.reset();
Debug::log(LOG, "Cleanup: destroyed a window");
return;
}
}
bool layersDirty = false;
for (auto const& lsr : m_vSurfacesFadingOut) {
auto ls = lsr.lock();
if (!ls) {
layersDirty = true;
continue;
}
if (ls->monitorID() != monid && ls->monitor)
continue;
// mark blur for recalc
if (ls->layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ls->layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM)
g_pHyprOpenGL->markBlurDirtyForMonitor(getMonitorFromID(monid));
if (ls->fadingOut && ls->readyToDelete && ls->isFadedOut()) {
for (auto const& m : m_vMonitors) {
for (auto& lsl : m->m_aLayerSurfaceLayers) {
if (!lsl.empty() && std::find_if(lsl.begin(), lsl.end(), [&](auto& other) { return other == ls; }) != lsl.end()) {
std::erase_if(lsl, [&](auto& other) { return other == ls || !other; });
}
}
}
std::erase_if(m_vSurfacesFadingOut, [ls](const auto& el) { return el.lock() == ls; });
std::erase_if(m_vLayers, [ls](const auto& el) { return el == ls; });
ls.reset();
Debug::log(LOG, "Cleanup: destroyed a layersurface");
glFlush(); // to free mem NOW.
return;
}
}
if (layersDirty)
std::erase_if(m_vSurfacesFadingOut, [](const auto& el) { return el.expired(); });
}
void CCompositor::addToFadingOutSafe(PHLLS pLS) {
const auto FOUND = std::find_if(m_vSurfacesFadingOut.begin(), m_vSurfacesFadingOut.end(), [&](auto& other) { return other.lock() == pLS; });
if (FOUND != m_vSurfacesFadingOut.end())
return; // if it's already added, don't add it.
m_vSurfacesFadingOut.emplace_back(pLS);
}
void CCompositor::removeFromFadingOutSafe(PHLLS ls) {
std::erase(m_vSurfacesFadingOut, ls);
}
void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) {
const auto FOUND = std::find_if(m_vWindowsFadingOut.begin(), m_vWindowsFadingOut.end(), [&](PHLWINDOWREF& other) { return other.lock() == pWindow; });
if (FOUND != m_vWindowsFadingOut.end())
return; // if it's already added, don't add it.
m_vWindowsFadingOut.emplace_back(pWindow);
}
PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
if (!isDirection(dir))
return nullptr;
// 0 -> history, 1 -> shared length
static auto PMETHOD = CConfigValue<Hyprlang::INT>("binds:focus_preferred_method");
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
const auto PMONITOR = pWindow->m_pMonitor.lock();
if (!PMONITOR)
return nullptr; // ??
const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{PMONITOR->vecPosition, PMONITOR->vecSize} : pWindow->getWindowIdealBoundingBoxIgnoreReserved();
const auto POSA = Vector2D(WINDOWIDEALBB.x, WINDOWIDEALBB.y);
const auto SIZEA = Vector2D(WINDOWIDEALBB.width, WINDOWIDEALBB.height);
const auto PWORKSPACE = pWindow->m_pWorkspace;
auto leaderValue = -1;
PHLWINDOW leaderWindow = nullptr;
if (!pWindow->m_bIsFloating) {
// for tiled windows, we calc edges
for (auto const& w : m_vWindows) {
if (w == pWindow || !w->m_bIsMapped || w->isHidden() || (!w->isFullscreen() && w->m_bIsFloating) || !isWorkspaceVisible(w->m_pWorkspace))
continue;
if (pWindow->m_pMonitor == w->m_pMonitor && pWindow->m_pWorkspace != w->m_pWorkspace)
continue;
if (PWORKSPACE->m_bHasFullscreenWindow && !w->isFullscreen() && !w->m_bCreatedOverFullscreen)
continue;
if (!*PMONITORFALLBACK && pWindow->m_pMonitor != w->m_pMonitor)
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_vWindowFocusHistory.size(); ++i) {
if (g_pCompositor->m_vWindowFocusHistory[i].lock() == w) {
windowIDX = i;
break;
}
}
windowIDX = g_pCompositor->m_vWindowFocusHistory.size() - windowIDX;
if (windowIDX > leaderValue) {
leaderValue = windowIDX;
leaderWindow = w;
}
}
} else /* length */ {
if (intersectLength > leaderValue) {
leaderValue = intersectLength;
leaderWindow = w;
}
}
}
} else {
// for floating windows, we calculate best distance and angle.
// if there is a window with angle better than THRESHOLD, only distance counts
if (dir == 'u')
dir = 't';
if (dir == 'd')
dir = 'b';
static const std::unordered_map<char, Vector2D> VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}};
//
auto vectorAngles = [](Vector2D a, 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_vWindows) {
if (w == pWindow || !w->m_bIsMapped || w->isHidden() || (!w->isFullscreen() && !w->m_bIsFloating) || !isWorkspaceVisible(w->m_pWorkspace))
continue;
if (pWindow->m_pMonitor == w->m_pMonitor && pWindow->m_pWorkspace != w->m_pWorkspace)
continue;
if (PWORKSPACE->m_bHasFullscreenWindow && !w->isFullscreen() && !w->m_bCreatedOverFullscreen)
continue;
if (!*PMONITORFALLBACK && pWindow->m_pMonitor != w->m_pMonitor)
continue;
const auto DIST = w->middle().distance(pWindow->middle());
const auto ANGLE = vectorAngles(Vector2D{w->middle() - pWindow->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_bHasFullscreenWindow)
leaderWindow = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID);
}
if (leaderValue != -1)
return leaderWindow;
return nullptr;
}
PHLWINDOW CCompositor::getNextWindowOnWorkspace(PHLWINDOW pWindow, bool focusableOnly, std::optional<bool> floating) {
bool gotToWindow = false;
for (auto const& w : m_vWindows) {
if (w != pWindow && !gotToWindow)
continue;
if (w == pWindow) {
gotToWindow = true;
continue;
}
if (floating.has_value() && w->m_bIsFloating != floating.value())
continue;
if (w->m_pWorkspace == pWindow->m_pWorkspace && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_sWindowData.noFocus.valueOrDefault()))
return w;
}
for (auto const& w : m_vWindows) {
if (floating.has_value() && w->m_bIsFloating != floating.value())
continue;
if (w != pWindow && w->m_pWorkspace == pWindow->m_pWorkspace && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_sWindowData.noFocus.valueOrDefault()))
return w;
}
return nullptr;
}
PHLWINDOW CCompositor::getPrevWindowOnWorkspace(PHLWINDOW pWindow, bool focusableOnly, std::optional<bool> floating) {
bool gotToWindow = false;
for (auto const& w : m_vWindows | std::views::reverse) {
if (w != pWindow && !gotToWindow)
continue;
if (w == pWindow) {
gotToWindow = true;
continue;
}
if (floating.has_value() && w->m_bIsFloating != floating.value())
continue;
if (w->m_pWorkspace == pWindow->m_pWorkspace && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_sWindowData.noFocus.valueOrDefault()))
return w;
}
for (auto const& w : m_vWindows | std::views::reverse) {
if (floating.has_value() && w->m_bIsFloating != floating.value())
continue;
if (w != pWindow && w->m_pWorkspace == pWindow->m_pWorkspace && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_sWindowData.noFocus.valueOrDefault()))
return w;
}
return nullptr;
}
WORKSPACEID CCompositor::getNextAvailableNamedWorkspace() {
WORKSPACEID lowest = -1337 + 1;
for (auto const& w : m_vWorkspaces) {
if (w->m_iID < -1 && w->m_iID < lowest)
lowest = w->m_iID;
}
return lowest - 1;
}
PHLWORKSPACE CCompositor::getWorkspaceByName(const std::string& name) {
for (auto const& w : m_vWorkspaces) {
if (w->m_szName == 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) {
for (auto const& m : m_vMonitors) {
if (VECINRECT(point, m->vecPosition.x, m->vecPosition.y, m->vecSize.x + m->vecPosition.x, m->vecSize.y + m->vecPosition.y))
return true;
}
return false;
}
bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR pMonitor) {
const auto PMONITOR = pMonitor ? pMonitor : getMonitorFromVector(point);
const auto XY1 = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft;
const auto XY2 = PMONITOR->vecPosition + PMONITOR->vecSize - PMONITOR->vecReservedBottomRight;
return !VECINRECT(point, XY1.x, XY1.y, XY2.x, XY2.y);
}
PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) {
return getMonitorInDirection(m_pLastMonitor.lock(), dir);
}
PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) {
if (!pSourceMonitor)
return nullptr;
const auto POSA = pSourceMonitor->vecPosition;
const auto SIZEA = pSourceMonitor->vecSize;
auto longestIntersect = -1;
PHLMONITOR longestIntersectMonitor = nullptr;
for (auto const& m : m_vMonitors) {
if (m == m_pLastMonitor)
continue;
const auto POSB = m->vecPosition;
const auto SIZEB = m->vecSize;
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_vWindows) {
if (!w->m_bIsMapped)
continue;
updateWindowAnimatedDecorationValues(w);
}
}
void CCompositor::updateWorkspaceWindows(const int64_t& id) {
for (auto const& w : m_vWindows) {
if (!w->m_bIsMapped || w->workspaceID() != id)
continue;
w->updateDynamicRules();
}
}
void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) {
// optimization
static auto PACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.active_border");
static auto PINACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.inactive_border");
static auto PNOGROUPACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.nogroup_border_active");
static auto PNOGROUPINACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.nogroup_border");
static auto PGROUPACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_active");
static auto PGROUPINACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_inactive");
static auto PGROUPACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_active");
static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_inactive");
static auto PINACTIVEALPHA = CConfigValue<Hyprlang::FLOAT>("decoration:inactive_opacity");
static auto PACTIVEALPHA = CConfigValue<Hyprlang::FLOAT>("decoration:active_opacity");
static auto PFULLSCREENALPHA = CConfigValue<Hyprlang::FLOAT>("decoration:fullscreen_opacity");
static auto PSHADOWCOL = CConfigValue<Hyprlang::INT>("decoration:col.shadow");
static auto PSHADOWCOLINACTIVE = CConfigValue<Hyprlang::INT>("decoration:col.shadow_inactive");
static auto PDIMSTRENGTH = CConfigValue<Hyprlang::FLOAT>("decoration:dim_strength");
static auto PDIMENABLED = CConfigValue<Hyprlang::INT>("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_cRealBorderColor)
return;
pWindow->m_cRealBorderColorPrevious = pWindow->m_cRealBorderColor;
pWindow->m_cRealBorderColor = grad;
pWindow->m_fBorderFadeAnimationProgress.setValueAndWarp(0.f);
pWindow->m_fBorderFadeAnimationProgress = 1.f;
};
const bool IS_SHADOWED_BY_MODAL = pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel && pWindow->m_pXDGSurface->toplevel->anyChildModal();
// border
const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(pWindow);
if (RENDERDATA.isBorderGradient)
setBorderColor(*RENDERDATA.borderGradient);
else {
const bool GROUPLOCKED = pWindow->m_sGroupData.pNextWindow.lock() ? pWindow->getGroupHead()->m_sGroupData.locked : false;
if (pWindow == m_pLastWindow) {
const auto* const ACTIVECOLOR =
!pWindow->m_sGroupData.pNextWindow.lock() ? (!pWindow->m_sGroupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
setBorderColor(pWindow->m_sWindowData.activeBorderColor.valueOr(*ACTIVECOLOR));
} else {
const auto* const INACTIVECOLOR = !pWindow->m_sGroupData.pNextWindow.lock() ? (!pWindow->m_sGroupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) :
(GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
setBorderColor(pWindow->m_sWindowData.inactiveBorderColor.valueOr(*INACTIVECOLOR));
}
}
// tick angle if it's not running (aka dead)
if (!pWindow->m_fBorderAngleAnimationProgress.isBeingAnimated())
pWindow->m_fBorderAngleAnimationProgress.setValueAndWarp(0.f);
// opacity
const auto PWORKSPACE = pWindow->m_pWorkspace;
if (pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) {
pWindow->m_fActiveInactiveAlpha = pWindow->m_sWindowData.alphaFullscreen.valueOrDefault().applyAlpha(*PFULLSCREENALPHA);
} else {
if (pWindow == m_pLastWindow)
pWindow->m_fActiveInactiveAlpha = pWindow->m_sWindowData.alpha.valueOrDefault().applyAlpha(*PACTIVEALPHA);
else
pWindow->m_fActiveInactiveAlpha = pWindow->m_sWindowData.alphaInactive.valueOrDefault().applyAlpha(*PINACTIVEALPHA);
}
// dim
float goalDim = 1.F;
if (pWindow == m_pLastWindow.lock() || pWindow->m_sWindowData.noDim.valueOrDefault() || !*PDIMENABLED)
goalDim = 0;
else
goalDim = *PDIMSTRENGTH;
if (IS_SHADOWED_BY_MODAL)
goalDim += (1.F - goalDim) / 2.F;
pWindow->m_fDimPercent = goalDim;
// shadow
if (!pWindow->isX11OverrideRedirect() && !pWindow->m_bX11DoesntWantBorders) {
if (pWindow == m_pLastWindow) {
pWindow->m_cRealShadowColor = CColor(*PSHADOWCOL);
} else {
pWindow->m_cRealShadowColor = CColor(*PSHADOWCOLINACTIVE != INT_MAX ? *PSHADOWCOLINACTIVE : *PSHADOWCOL);
}
} else {
pWindow->m_cRealShadowColor.setValueAndWarp(CColor(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_mMonitorIDMap.contains(name) && !std::any_of(m_vRealMonitors.begin(), m_vRealMonitors.end(), [&](auto m) { return m->ID == m_mMonitorIDMap[name]; }))
return m_mMonitorIDMap[name];
// otherwise, find minimum available ID that is not in the map
std::unordered_set<MONITORID> usedIDs;
for (auto const& monitor : m_vRealMonitors) {
usedIDs.insert(monitor->ID);
}
MONITORID nextID = 0;
while (usedIDs.count(nextID) > 0) {
nextID++;
}
m_mMonitorIDMap[name] = nextID;
return nextID;
}
void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitorB) {
const auto PWORKSPACEA = pMonitorA->activeWorkspace;
const auto PWORKSPACEB = pMonitorB->activeWorkspace;
PWORKSPACEA->m_pMonitor = pMonitorB;
PWORKSPACEA->moveToMonitor(pMonitorB->ID);
for (auto const& w : m_vWindows) {
if (w->m_pWorkspace == PWORKSPACEA) {
if (w->m_bPinned) {
w->m_pWorkspace = PWORKSPACEB;
continue;
}
w->m_pMonitor = pMonitorB;
// additionally, move floating and fs windows manually
if (w->m_bIsFloating)
w->m_vRealPosition = w->m_vRealPosition.goal() - pMonitorA->vecPosition + pMonitorB->vecPosition;
if (w->isFullscreen()) {
w->m_vRealPosition = pMonitorB->vecPosition;
w->m_vRealSize = pMonitorB->vecSize;
}
w->updateToplevel();
}
}
PWORKSPACEB->m_pMonitor = pMonitorA;
PWORKSPACEB->moveToMonitor(pMonitorA->ID);
for (auto const& w : m_vWindows) {
if (w->m_pWorkspace == PWORKSPACEB) {
if (w->m_bPinned) {
w->m_pWorkspace = PWORKSPACEA;
continue;
}
w->m_pMonitor = pMonitorA;
// additionally, move floating and fs windows manually
if (w->m_bIsFloating)
w->m_vRealPosition = w->m_vRealPosition.goal() - pMonitorB->vecPosition + pMonitorA->vecPosition;
if (w->isFullscreen()) {
w->m_vRealPosition = pMonitorA->vecPosition;
w->m_vRealSize = pMonitorA->vecSize;
}
w->updateToplevel();
}
}
pMonitorA->activeWorkspace = PWORKSPACEB;
pMonitorB->activeWorkspace = PWORKSPACEA;
PWORKSPACEA->rememberPrevWorkspace(PWORKSPACEB);
PWORKSPACEB->rememberPrevWorkspace(PWORKSPACEA);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->ID);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->ID);
updateFullscreenFadeOnWorkspace(PWORKSPACEB);
updateFullscreenFadeOnWorkspace(PWORKSPACEA);
if (pMonitorA->ID == g_pCompositor->m_pLastMonitor->ID || pMonitorB->ID == g_pCompositor->m_pLastMonitor->ID) {
const auto LASTWIN = pMonitorA->ID == g_pCompositor->m_pLastMonitor->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->ID == g_pCompositor->m_pLastMonitor->ID ? PWORKSPACEB : PWORKSPACEA;
g_pEventManager->postEvent(SHyprIPCEvent{"workspace", PNEWWORKSPACE->m_szName});
g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", PNEWWORKSPACE->m_iID, PNEWWORKSPACE->m_szName)});
EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE);
}
// event
g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspace", PWORKSPACEA->m_szName + "," + pMonitorB->szName});
g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspacev2", std::format("{},{},{}", PWORKSPACEA->m_iID, PWORKSPACEA->m_szName, pMonitorB->szName)});
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEA, pMonitorB}));
g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspace", PWORKSPACEB->m_szName + "," + pMonitorA->szName});
g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspacev2", std::format("{},{},{}", PWORKSPACEB->m_iID, PWORKSPACEB->m_szName, pMonitorA->szName)});
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEB, pMonitorA}));
}
PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {
if (name == "current")
return g_pCompositor->m_pLastMonitor.lock();
else if (isDirection(name))
return getMonitorInDirection(name[0]);
else if (name[0] == '+' || name[0] == '-') {
// relative
if (m_vMonitors.size() == 1)
return *m_vMonitors.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_vMonitors.size()) : offsetLeft % m_vMonitors.size();
int currentPlace = 0;
for (int i = 0; i < (int)m_vMonitors.size(); i++) {
if (m_vMonitors[i] == m_pLastMonitor) {
currentPlace = i;
break;
}
}
currentPlace += offsetLeft;
if (currentPlace < 0) {
currentPlace = m_vMonitors.size() + currentPlace;
} else {
currentPlace = currentPlace % m_vMonitors.size();
}
if (currentPlace != std::clamp(currentPlace, 0, (int)m_vMonitors.size() - 1)) {
Debug::log(WARN, "Error in getMonitorFromString: Vaxry's code sucks.");
currentPlace = std::clamp(currentPlace, 0, (int)m_vMonitors.size() - 1);
}
return m_vMonitors[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_vMonitors.size()) {
return getMonitorFromID(monID);
} else {
Debug::log(ERR, "Error in getMonitorFromString: invalid arg 1");
return nullptr;
}
} else {
for (auto const& m : m_vMonitors) {
if (!m->output)
continue;
if (m->matchesStaticSelector(name)) {
return m;
}
}
}
return nullptr;
}
void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor, bool noWarpCursor) {
// We trust the monitor to be correct.
if (pWorkspace->m_pMonitor == pMonitor)
return;
Debug::log(LOG, "moveWorkspaceToMonitor: Moving {} to monitor {}", pWorkspace->m_iID, pMonitor->ID);
const auto POLDMON = pWorkspace->m_pMonitor.lock();
const bool SWITCHINGISACTIVE = POLDMON ? POLDMON->activeWorkspace == pWorkspace : false;
// fix old mon
WORKSPACEID nextWorkspaceOnMonitorID = WORKSPACE_INVALID;
if (!SWITCHINGISACTIVE)
nextWorkspaceOnMonitorID = pWorkspace->m_iID;
else {
for (auto const& w : m_vWorkspaces) {
if (w->m_pMonitor == POLDMON && w->m_iID != pWorkspace->m_iID && !w->m_bIsSpecialWorkspace) {
nextWorkspaceOnMonitorID = w->m_iID;
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);
g_pCompositor->createNewWorkspace(nextWorkspaceOnMonitorID, POLDMON->ID);
}
Debug::log(LOG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID);
POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, true);
}
// move the workspace
pWorkspace->m_pMonitor = pMonitor;
pWorkspace->moveToMonitor(pMonitor->ID);
for (auto const& w : m_vWindows) {
if (w->m_pWorkspace == pWorkspace) {
if (w->m_bPinned) {
w->m_pWorkspace = g_pCompositor->getWorkspaceByID(nextWorkspaceOnMonitorID);
continue;
}
w->m_pMonitor = pMonitor;
// additionally, move floating and fs windows manually
if (w->m_bIsMapped && !w->isHidden()) {
if (POLDMON) {
if (w->m_bIsFloating)
w->m_vRealPosition = w->m_vRealPosition.goal() - POLDMON->vecPosition + pMonitor->vecPosition;
if (w->isFullscreen()) {
w->m_vRealPosition = pMonitor->vecPosition;
w->m_vRealSize = pMonitor->vecSize;
}
} else {
w->m_vRealPosition = Vector2D{(int)w->m_vRealPosition.goal().x % (int)pMonitor->vecSize.x, (int)w->m_vRealPosition.goal().y % (int)pMonitor->vecSize.y};
}
}
w->updateToplevel();
}
}
if (SWITCHINGISACTIVE && POLDMON == g_pCompositor->m_pLastMonitor) { // if it was active, preserve its' status. If it wasn't, don't.
Debug::log(LOG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_iID);
if (valid(pMonitor->activeWorkspace)) {
pMonitor->activeWorkspace->m_bVisible = false;
pMonitor->activeWorkspace->startAnim(false, false);
}
setActiveMonitor(pMonitor);
pMonitor->activeWorkspace = pWorkspace;
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->ID);
pWorkspace->startAnim(true, true, true);
pWorkspace->m_bVisible = true;
if (!noWarpCursor)
g_pPointerManager->warpTo(pMonitor->vecPosition + pMonitor->vecTransformedSize / 2.F);
g_pInputManager->sendMotionEventsToFocused();
}
// finalize
if (POLDMON) {
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->ID);
updateFullscreenFadeOnWorkspace(POLDMON->activeWorkspace);
updateSuspendedStates();
}
updateFullscreenFadeOnWorkspace(pWorkspace);
updateSuspendedStates();
// event
g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspace", pWorkspace->m_szName + "," + pMonitor->szName});
g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspacev2", std::format("{},{},{}", pWorkspace->m_iID, pWorkspace->m_szName, pMonitor->szName)});
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{pWorkspace, pMonitor}));
}
bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) {
WORKSPACEID lowestID = INT64_MAX;
WORKSPACEID highestID = INT64_MIN;
for (auto const& w : m_vWorkspaces) {
if (w->m_bIsSpecialWorkspace)
continue;
if (w->m_iID < lowestID)
lowestID = w->m_iID;
if (w->m_iID > highestID)
highestID = w->m_iID;
}
return std::clamp(id, lowestID, highestID) != id;
}
void CCompositor::updateFullscreenFadeOnWorkspace(PHLWORKSPACE pWorkspace) {
const auto FULLSCREEN = pWorkspace->m_bHasFullscreenWindow;
for (auto const& w : g_pCompositor->m_vWindows) {
if (w->m_pWorkspace == pWorkspace) {
if (w->m_bFadingOut || w->m_bPinned || w->isFullscreen())
continue;
if (!FULLSCREEN)
w->m_fAlpha = 1.f;
else if (!w->isFullscreen())
w->m_fAlpha = !w->m_bCreatedOverFullscreen ? 0.f : 1.f;
}
}
const auto PMONITOR = pWorkspace->m_pMonitor.lock();
if (pWorkspace->m_iID == PMONITOR->activeWorkspaceID() || pWorkspace->m_iID == PMONITOR->activeSpecialWorkspaceID()) {
for (auto const& ls : PMONITOR->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {
if (!ls->fadingOut)
ls->alpha = FULLSCREEN && pWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f;
}
}
}
void CCompositor::changeWindowFullscreenModeInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON) {
setWindowFullscreenInternal(
PWINDOW, (eFullscreenMode)(ON ? (uint8_t)PWINDOW->m_sFullscreenState.internal | (uint8_t)MODE : ((uint8_t)PWINDOW->m_sFullscreenState.internal & (uint8_t)~MODE)));
}
void CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON) {
setWindowFullscreenClient(PWINDOW,
(eFullscreenMode)(ON ? (uint8_t)PWINDOW->m_sFullscreenState.client | (uint8_t)MODE : ((uint8_t)PWINDOW->m_sFullscreenState.client & (uint8_t)~MODE)));
}
void CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) {
if (PWINDOW->m_sWindowData.syncFullscreen.valueOrDefault())
setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = MODE, .client = MODE});
else
setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = MODE, .client = PWINDOW->m_sFullscreenState.client});
}
void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) {
if (PWINDOW->m_sWindowData.syncFullscreen.valueOrDefault())
setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = MODE, .client = MODE});
else
setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = PWINDOW->m_sFullscreenState.internal, .client = MODE});
}
void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, sFullscreenState state) {
static auto PDIRECTSCANOUT = CConfigValue<Hyprlang::INT>("render:direct_scanout");
if (!validMapped(PWINDOW) || g_pCompositor->m_bUnsafeState)
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_pMonitor.lock();
const auto PWORKSPACE = PWINDOW->m_pWorkspace;
const eFullscreenMode CURRENT_EFFECTIVE_MODE = (eFullscreenMode)std::bit_floor((uint8_t)PWINDOW->m_sFullscreenState.internal);
const eFullscreenMode EFFECTIVE_MODE = (eFullscreenMode)std::bit_floor((uint8_t)state.internal);
const bool CHANGEINTERNAL = !(PWINDOW->m_bPinned || CURRENT_EFFECTIVE_MODE == EFFECTIVE_MODE || (PWORKSPACE->m_bHasFullscreenWindow && !PWINDOW->isFullscreen()));
// TODO: update the state on syncFullscreen changes
if (!CHANGEINTERNAL && PWINDOW->m_sWindowData.syncFullscreen.valueOrDefault())
return;
PWINDOW->m_sFullscreenState.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_sFullscreenState.internal = state.internal;
PWORKSPACE->m_efFullscreenMode = EFFECTIVE_MODE;
PWORKSPACE->m_bHasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
g_pEventManager->postEvent(SHyprIPCEvent{"fullscreen", 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_vWindows) {
if (w->m_pWorkspace == PWORKSPACE && !w->isFullscreen() && !w->m_bFadingOut && !w->m_bPinned)
w->m_bCreatedOverFullscreen = false;
}
updateFullscreenFadeOnWorkspace(PWORKSPACE);
g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goal(), true);
forceReportSizesToWindowsOnWorkspace(PWINDOW->workspaceID());
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)
g_pHyprRenderer->setSurfaceScanoutMode(PWINDOW->m_pWLSurface->resource(), EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->self.lock() : nullptr);
g_pConfigManager->ensureVRR(PMONITOR);
}
PHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) {
if (!pWindow->m_bIsX11)
return nullptr;
for (auto const& w : m_vWindows) {
if (!w->m_bIsX11)
continue;
if (w->m_pXWaylandSurface == pWindow->m_pXWaylandSurface->parent)
return w;
}
return nullptr;
}
void CCompositor::updateWorkspaceWindowDecos(const WORKSPACEID& id) {
for (auto const& w : m_vWindows) {
if (w->workspaceID() != id)
continue;
w->updateWindowDecos();
}
}
void CCompositor::updateWorkspaceWindowData(const WORKSPACEID& id) {
const auto PWORKSPACE = getWorkspaceByID(id);
const auto WORKSPACERULE = PWORKSPACE ? g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE) : SWorkspaceRule{};
for (auto const& w : m_vWindows) {
if (w->workspaceID() != id)
continue;
w->updateWindowData(WORKSPACERULE);
}
}
void CCompositor::scheduleFrameForMonitor(PHLMONITOR pMonitor, IOutput::scheduleFrameReason reason) {
if ((m_pAqBackend->hasSession() && !m_pAqBackend->session->active) || !m_bSessionActive)
return;
if (!pMonitor->m_bEnabled)
return;
if (pMonitor->renderingActive)
pMonitor->pendingFrame = true;
pMonitor->output->scheduleFrame(reason);
}
PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
auto regexp = trim(regexp_);
if (regexp.starts_with("active"))
return m_pLastWindow.lock();
else if (regexp.starts_with("floating") || regexp.starts_with("tiled")) {
// first floating on the current ws
if (!valid(m_pLastWindow))
return nullptr;
const bool FLOAT = regexp.starts_with("floating");
for (auto const& w : m_vWindows) {
if (!w->m_bIsMapped || w->m_bIsFloating != FLOAT || w->m_pWorkspace != m_pLastWindow->m_pWorkspace || w->isHidden())
continue;
return w;
}
return nullptr;
}
eFocusWindowMode mode = MODE_CLASS_REGEX;
std::regex regexCheck(regexp_);
std::string matchCheck;
if (regexp.starts_with("class:")) {
regexCheck = std::regex(regexp.substr(6));
} else if (regexp.starts_with("initialclass:")) {
mode = MODE_INITIAL_CLASS_REGEX;
regexCheck = std::regex(regexp.substr(13));
} else if (regexp.starts_with("title:")) {
mode = MODE_TITLE_REGEX;
regexCheck = std::regex(regexp.substr(6));
} else if (regexp.starts_with("initialtitle:")) {
mode = MODE_INITIAL_TITLE_REGEX;
regexCheck = std::regex(regexp.substr(13));
} 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_vWindows) {
if (!w->m_bIsMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w)))
continue;
switch (mode) {
case MODE_CLASS_REGEX: {
const auto windowClass = w->m_szClass;
if (!std::regex_search(windowClass, regexCheck))
continue;
break;
}
case MODE_INITIAL_CLASS_REGEX: {
const auto initialWindowClass = w->m_szInitialClass;
if (!std::regex_search(initialWindowClass, regexCheck))
continue;
break;
}
case MODE_TITLE_REGEX: {
const auto windowTitle = w->m_szTitle;
if (!std::regex_search(windowTitle, regexCheck))
continue;
break;
}
case MODE_INITIAL_TITLE_REGEX: {
const auto initialWindowTitle = w->m_szInitialTitle;
if (!std::regex_search(initialWindowTitle, regexCheck))
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<Hyprlang::INT>("cursor:no_warps");
if (*PNOWARPS && !force) {
const auto PMONITORNEW = getMonitorFromVector(pos);
if (PMONITORNEW != m_pLastMonitor)
setActiveMonitor(PMONITORNEW);
return;
}
g_pPointerManager->warpTo(pos);
const auto PMONITORNEW = getMonitorFromVector(pos);
if (PMONITORNEW != m_pLastMonitor)
setActiveMonitor(PMONITORNEW);
}
void CCompositor::closeWindow(PHLWINDOW pWindow) {
if (pWindow && validMapped(pWindow)) {
g_pXWaylandManager->sendCloseWindow(pWindow);
}
}
PHLLS CCompositor::getLayerSurfaceFromSurface(SP<CWLSurfaceResource> pSurface) {
std::pair<SP<CWLSurfaceResource>, bool> result = {pSurface, false};
for (auto const& ls : m_vLayers) {
if (ls->layerSurface && ls->layerSurface->surface == pSurface)
return ls;
if (!ls->layerSurface || !ls->mapped)
continue;
ls->layerSurface->surface->breadthfirst(
[](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) {
if (surf == ((std::pair<SP<CWLSurfaceResource>, bool>*)data)->first) {
*(bool*)data = true;
return;
}
},
&result);
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_pLastMonitor;
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->vecSize.x : std::stoi(x);
Y = yIsPercent ? std::stof(y) * 0.01 * PMONITOR->vecSize.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);
}
void CCompositor::forceReportSizesToWindowsOnWorkspace(const WORKSPACEID& wid) {
for (auto const& w : m_vWindows) {
if (w->workspaceID() == wid && w->m_bIsMapped && !w->isHidden()) {
g_pXWaylandManager->setWindowSize(w, w->m_vRealSize.value(), true);
}
}
}
PHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITORID& monid, const std::string& name, bool isEmpty) {
const auto NAME = name == "" ? std::to_string(id) : name;
auto monID = monid;
// check if bound
if (const auto PMONITOR = g_pConfigManager->getBoundMonitorForWS(NAME); PMONITOR)
monID = PMONITOR->ID;
const bool SPECIAL = id >= SPECIAL_WORKSPACE_START && id <= -2;
const auto PWORKSPACE = m_vWorkspaces.emplace_back(CWorkspace::create(id, getMonitorFromID(monID), NAME, SPECIAL, isEmpty));
PWORKSPACE->m_fAlpha.setValueAndWarp(0);
return PWORKSPACE;
}
void CCompositor::renameWorkspace(const WORKSPACEID& id, const std::string& name) {
const auto PWORKSPACE = getWorkspaceByID(id);
if (!PWORKSPACE)
return;
if (isWorkspaceSpecial(id))
return;
Debug::log(LOG, "renameWorkspace: Renaming workspace {} to '{}'", id, name);
PWORKSPACE->m_szName = name;
g_pEventManager->postEvent({"renameworkspace", std::to_string(PWORKSPACE->m_iID) + "," + PWORKSPACE->m_szName});
}
void CCompositor::setActiveMonitor(PHLMONITOR pMonitor) {
if (m_pLastMonitor == pMonitor)
return;
if (!pMonitor) {
m_pLastMonitor.reset();
return;
}
const auto PWORKSPACE = pMonitor->activeWorkspace;
g_pEventManager->postEvent(SHyprIPCEvent{"focusedmon", pMonitor->szName + "," + (PWORKSPACE ? PWORKSPACE->m_szName : "?")});
EMIT_HOOK_EVENT("focusedMon", pMonitor);
m_pLastMonitor = pMonitor->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_vWorkspaces) {
if (ws->m_bIsSpecialWorkspace && ws->m_iID > highest) {
highest = ws->m_iID;
}
}
return highest + 1;
}
void CCompositor::performUserChecks() {
static auto PNOCHECKXDG = CConfigValue<Hyprlang::INT>("misc:disable_xdg_env_checks");
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"),
CColor{}, 15000, ICON_WARNING);
}
}
}
void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) {
if (!pWindow || !pWorkspace)
return;
if (pWindow->m_bPinned && pWorkspace->m_bIsSpecialWorkspace)
return;
const bool FULLSCREEN = pWindow->isFullscreen();
const auto FULLSCREENMODE = pWindow->m_sFullscreenState.internal;
if (FULLSCREEN)
setWindowFullscreenInternal(pWindow, FSMODE_NONE);
const PHLWINDOW pFirstWindowOnWorkspace = g_pCompositor->getFirstWindowOnWorkspace(pWorkspace->m_iID);
const int visibleWindowsOnWorkspace = g_pCompositor->getWindowsOnWorkspace(pWorkspace->m_iID, std::nullopt, true);
const auto PWINDOWMONITOR = pWindow->m_pMonitor.lock();
const auto POSTOMON = pWindow->m_vRealPosition.goal() - PWINDOWMONITOR->vecPosition;
const auto PWORKSPACEMONITOR = pWorkspace->m_pMonitor.lock();
if (!pWindow->m_bIsFloating)
g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow);
pWindow->moveToWorkspace(pWorkspace);
pWindow->m_pMonitor = pWorkspace->m_pMonitor;
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace");
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow &&
pFirstWindowOnWorkspace->m_sGroupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) {
pWindow->m_bIsFloating = pFirstWindowOnWorkspace->m_bIsFloating; // match the floating state. Needed to group tiled into floated and vice versa.
if (!pWindow->m_sGroupData.pNextWindow.expired()) {
PHLWINDOW next = pWindow->m_sGroupData.pNextWindow.lock();
while (next != pWindow) {
next->m_bIsFloating = pFirstWindowOnWorkspace->m_bIsFloating; // match the floating state of group members
next = next->m_sGroupData.pNextWindow.lock();
}
}
static auto USECURRPOS = CConfigValue<Hyprlang::INT>("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(std::make_unique<CHyprGroupBarDecoration>(pWindow));
} else {
if (!pWindow->m_bIsFloating)
g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
if (pWindow->m_bIsFloating)
pWindow->m_vRealPosition = POSTOMON + PWORKSPACEMONITOR->vecPosition;
}
pWindow->updateToplevel();
pWindow->updateDynamicRules();
pWindow->uncacheWindowDecos();
pWindow->updateGroupOutputs();
if (!pWindow->m_sGroupData.pNextWindow.expired()) {
PHLWINDOW next = pWindow->m_sGroupData.pNextWindow.lock();
while (next != pWindow) {
next->updateToplevel();
next = next->m_sGroupData.pNextWindow.lock();
}
}
if (FULLSCREEN)
setWindowFullscreenInternal(pWindow, FULLSCREENMODE);
g_pCompositor->updateWorkspaceWindows(pWorkspace->m_iID);
g_pCompositor->updateWorkspaceWindows(pWindow->workspaceID());
g_pCompositor->updateSuspendedStates();
}
PHLWINDOW CCompositor::getForceFocus() {
for (auto const& w : m_vWindows) {
if (!w->m_bIsMapped || w->isHidden() || !isWorkspaceVisible(w->m_pWorkspace))
continue;
if (!w->m_bStayFocused)
continue;
return w;
}
return nullptr;
}
void CCompositor::arrangeMonitors() {
static auto* const PXWLFORCESCALEZERO = (Hyprlang::INT* const*)g_pConfigManager->getConfigValuePtr("xwayland:force_zero_scaling");
std::vector<PHLMONITOR> toArrange;
std::vector<PHLMONITOR> arranged;
for (auto const& m : m_vMonitors)
toArrange.push_back(m);
Debug::log(LOG, "arrangeMonitors: {} to arrange", toArrange.size());
for (auto it = toArrange.begin(); it != toArrange.end();) {
auto m = *it;
if (m->activeMonitorRule.offset != Vector2D{-INT32_MAX, -INT32_MAX}) {
// explicit.
Debug::log(LOG, "arrangeMonitors: {} explicit {:j}", m->szName, m->activeMonitorRule.offset);
m->moveTo(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;
// Finds the max and min values of explicitely placed monitors.
for (auto const& m : arranged) {
if (m->vecPosition.x + m->vecSize.x > maxXOffsetRight)
maxXOffsetRight = m->vecPosition.x + m->vecSize.x;
if (m->vecPosition.x < maxXOffsetLeft)
maxXOffsetLeft = m->vecPosition.x;
if (m->vecPosition.y + m->vecSize.y > maxYOffsetDown)
maxYOffsetDown = m->vecPosition.y + m->vecSize.y;
if (m->vecPosition.y < maxYOffsetUp)
maxYOffsetUp = m->vecPosition.y;
}
// Iterates through all non-explicitly placed monitors.
for (auto const& m : toArrange) {
// 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->activeMonitorRule.autoDir) {
case eAutoDirs::DIR_AUTO_UP:
newPosition.y = maxYOffsetUp - m->vecSize.y;
maxYOffsetUp = newPosition.y;
break;
case eAutoDirs::DIR_AUTO_DOWN:
newPosition.y = maxYOffsetDown;
maxYOffsetDown += m->vecSize.y;
break;
case eAutoDirs::DIR_AUTO_LEFT:
newPosition.x = maxXOffsetLeft - m->vecSize.x;
maxXOffsetLeft = newPosition.x;
break;
case eAutoDirs::DIR_AUTO_RIGHT:
case eAutoDirs::DIR_AUTO_NONE:
newPosition.x = maxXOffsetRight;
maxXOffsetRight += m->vecSize.x;
break;
default: UNREACHABLE();
}
Debug::log(LOG, "arrangeMonitors: {} auto {:j}", m->szName, m->vecPosition);
m->moveTo(newPosition);
}
// reset maxXOffsetRight (reuse)
// and set xwayland positions aka auto for all
maxXOffsetRight = 0;
for (auto const& m : m_vMonitors) {
Debug::log(LOG, "arrangeMonitors: {} xwayland [{}, {}]", m->szName, maxXOffsetRight, 0);
m->vecXWaylandPosition = {maxXOffsetRight, 0};
maxXOffsetRight += (*PXWLFORCESCALEZERO ? m->vecTransformedSize.x : m->vecSize.x);
if (*PXWLFORCESCALEZERO)
m->xwaylandScale = m->scale;
else
m->xwaylandScale = 1.f;
}
PROTO::xdgOutput->updateAllOutputs();
}
void CCompositor::enterUnsafeState() {
if (m_bUnsafeState)
return;
Debug::log(LOG, "Entering unsafe state");
if (!m_pUnsafeOutput->m_bEnabled)
m_pUnsafeOutput->onConnect(false);
m_bUnsafeState = true;
setActiveMonitor(m_pUnsafeOutput.lock());
}
void CCompositor::leaveUnsafeState() {
if (!m_bUnsafeState)
return;
Debug::log(LOG, "Leaving unsafe state");
m_bUnsafeState = false;
PHLMONITOR pNewMonitor = nullptr;
for (auto const& pMonitor : m_vMonitors) {
if (pMonitor->output != m_pUnsafeOutput->output) {
pNewMonitor = pMonitor;
break;
}
}
RASSERT(pNewMonitor, "Tried to leave unsafe without a monitor");
if (m_pUnsafeOutput->m_bEnabled)
m_pUnsafeOutput->onDisconnect();
for (auto const& m : m_vMonitors) {
scheduleFrameForMonitor(m);
}
}
void CCompositor::setPreferredScaleForSurface(SP<CWLSurfaceResource> 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_fLastScale = scale;
PSURFACE->m_iLastScale = static_cast<int32_t>(std::ceil(scale));
}
void CCompositor::setPreferredTransformForSurface(SP<CWLSurfaceResource> 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_eLastTransform = transform;
}
void CCompositor::updateSuspendedStates() {
for (auto const& w : g_pCompositor->m_vWindows) {
if (!w->m_bIsMapped)
continue;
w->setSuspended(w->isHidden() || !isWorkspaceVisible(w->m_pWorkspace));
}
}
PHLWINDOW CCompositor::windowForCPointer(CWindow* pWindow) {
for (auto const& w : m_vWindows) {
if (w.get() != pWindow)
continue;
return w;
}
return {};
}
static void checkDefaultCursorWarp(PHLMONITOR monitor) {
static auto PCURSORMONITOR = CConfigValue<std::string>("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->szName) {
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<Aquamarine::IOutput> output) {
// add it to real
auto PNEWMONITOR = g_pCompositor->m_vRealMonitors.emplace_back(makeShared<CMonitor>(output));
if (std::string("HEADLESS-1") == output->name) {
g_pCompositor->m_pUnsafeOutput = PNEWMONITOR;
output->name = "FALLBACK"; // we are allowed to do this :)
}
Debug::log(LOG, "New output with name {}", output->name);
PNEWMONITOR->szName = output->name;
PNEWMONITOR->self = PNEWMONITOR;
const bool FALLBACK = g_pCompositor->m_pUnsafeOutput ? output == g_pCompositor->m_pUnsafeOutput->output : false;
PNEWMONITOR->ID = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name);
PNEWMONITOR->isUnsafeFallback = FALLBACK;
EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR);
if (!FALLBACK)
PNEWMONITOR->onConnect(false);
if (!PNEWMONITOR->m_bEnabled || FALLBACK)
return;
// ready to process if we have a real monitor
if ((!g_pHyprRenderer->m_pMostHzMonitor || PNEWMONITOR->refreshRate > g_pHyprRenderer->m_pMostHzMonitor->refreshRate) && PNEWMONITOR->m_bEnabled)
g_pHyprRenderer->m_pMostHzMonitor = PNEWMONITOR;
g_pCompositor->m_bReadyToProcess = true;
g_pConfigManager->m_bWantsMonitorReload = true;
g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR, IOutput::AQ_SCHEDULE_NEW_MONITOR);
checkDefaultCursorWarp(PNEWMONITOR);
for (auto const& w : g_pCompositor->m_vWindows) {
if (w->m_pMonitor == PNEWMONITOR) {
w->m_iLastSurfaceMonitorID = MONITOR_INVALID;
w->updateSurfaceScaleTransformDetails();
}
}
g_pHyprRenderer->damageMonitor(PNEWMONITOR);
PNEWMONITOR->onMonitorFrame();
}