hyprctl dispatch -- movetoworkspacesilent x,"^kitty$" where X is the current workspace makes kitty stops updating until current workspace is changed while it is on the screen. update the suspend state after it has been moved.
2980 lines
104 KiB
C++
2980 lines
104 KiB
C++
#include "Compositor.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 <random>
|
|
#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/core/Compositor.hpp"
|
|
#include "protocols/core/Subcompositor.hpp"
|
|
#include "desktop/LayerSurface.hpp"
|
|
#include "render/Renderer.hpp"
|
|
#include "xwayland/XWayland.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::cout << "Bailing out, XDG_RUNTIME_DIR is invalid\n";
|
|
throw std::runtime_error("CCompositor() failed");
|
|
}
|
|
|
|
if (!m_szHyprTempDataRoot.starts_with("/run/user"))
|
|
std::cout << "[!!WARNING!!] XDG_RUNTIME_DIR looks non-standard. Proceeding anyways...\n";
|
|
|
|
std::random_device dev;
|
|
std::mt19937 engine(dev());
|
|
std::uniform_int_distribution<> distribution(0, INT32_MAX);
|
|
|
|
m_szInstanceSignature = GIT_COMMIT_HASH + std::string("_") + std::to_string(time(NULL)) + "_" + std::to_string(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::cout << "Bailing out, " << m_szHyprTempDataRoot << " is not a directory\n";
|
|
throw std::runtime_error("CCompositor() failed");
|
|
}
|
|
|
|
m_szInstancePath = m_szHyprTempDataRoot + "/" + m_szInstanceSignature;
|
|
|
|
if (std::filesystem::exists(m_szInstancePath)) {
|
|
std::cout << "Bailing out, " << m_szInstancePath << " exists??\n";
|
|
throw std::runtime_error("CCompositor() failed");
|
|
}
|
|
|
|
if (mkdir(m_szInstancePath.c_str(), S_IRWXU) < 0) {
|
|
std::cout << "Bailing out, couldn't create " << m_szInstancePath << "\n";
|
|
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;
|
|
|
|
//
|
|
void CCompositor::initServer(std::string socketName, int socketFd) {
|
|
|
|
m_sWLDisplay = wl_display_create();
|
|
|
|
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;
|
|
|
|
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& 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& m : m_vMonitors) {
|
|
scheduleFrameForMonitor(m.get());
|
|
g_pHyprRenderer->applyMonitorRule(m.get(), &m->activeMonitorRule, true);
|
|
}
|
|
|
|
g_pConfigManager->m_bWantsMonitorReload = true;
|
|
g_pCursorManager->syncGsettings();
|
|
} else {
|
|
Debug::log(LOG, "Session got deactivated!");
|
|
|
|
m_bSessionActive = false;
|
|
|
|
for (auto& 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& m : m_vMonitors) {
|
|
g_pHyprOpenGL->destroyMonitorResources(m.get());
|
|
|
|
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>();
|
|
} 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& 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();
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromID(const MONITORID& id) {
|
|
for (auto& m : m_vMonitors) {
|
|
if (m->ID == id) {
|
|
return m.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromName(const std::string& name) {
|
|
for (auto& m : m_vMonitors) {
|
|
if (m->szName == name) {
|
|
return m.get();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromDesc(const std::string& desc) {
|
|
for (auto& m : m_vMonitors) {
|
|
if (m->szDescription.starts_with(desc))
|
|
return m.get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromCursor() {
|
|
return getMonitorFromVector(g_pPointerManager->position());
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromVector(const Vector2D& point) {
|
|
SP<CMonitor> mon;
|
|
for (auto& m : m_vMonitors) {
|
|
if (CBox{m->vecPosition, m->vecSize}.containsPoint(point)) {
|
|
mon = m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!mon) {
|
|
float bestDistance = 0.f;
|
|
SP<CMonitor> pBestMon;
|
|
|
|
for (auto& 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().get();
|
|
}
|
|
|
|
return pBestMon.get();
|
|
}
|
|
|
|
return mon.get();
|
|
}
|
|
|
|
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(CMonitor* pMonitor) {
|
|
for (auto& m : m_vRealMonitors) {
|
|
if (m.get() == 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& w : m_vWindows | std::views::reverse) {
|
|
const auto BB = w->getWindowBoxUnified(properties);
|
|
CBox box = BB.copy().expand(w->m_iX11Type != 2 ? BORDER_GRAB_AREA : 0);
|
|
if (w->m_bIsFloating && w->m_bIsMapped && !w->isHidden() && !w->m_bX11ShouldntFocus && w->m_bPinned && !w->m_sWindowData.noFocus.valueOrDefault() &&
|
|
w != pIgnoreWindow) {
|
|
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& w : m_vWindows | std::views::reverse) {
|
|
|
|
if (special && !w->onSpecialWorkspace()) // because special floating may creep up into regular
|
|
continue;
|
|
|
|
const auto BB = w->getWindowBoxUnified(properties);
|
|
const auto PWINDOWMONITOR = getMonitorFromID(w->m_iMonitorID);
|
|
|
|
// to avoid focusing windows behind special workspaces from other monitors
|
|
if (!*PSPECIALFALLTHRU && PWINDOWMONITOR && PWINDOWMONITOR->activeSpecialWorkspace && w->m_pWorkspace != PWINDOWMONITOR->activeSpecialWorkspace &&
|
|
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;
|
|
|
|
CBox box = BB.copy().expand(w->m_iX11Type != 2 ? BORDER_GRAB_AREA : 0);
|
|
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->m_iX11Type != 2)
|
|
continue;
|
|
|
|
if (box.containsPoint(g_pPointerManager->position())) {
|
|
|
|
if (w->m_bIsX11 && w->m_iX11Type == 2 && !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& 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& w : m_vWindows) {
|
|
if (special != w->onSpecialWorkspace())
|
|
continue;
|
|
|
|
CBox box = (properties & USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_vPosition, w->m_vSize};
|
|
if (!w->m_bIsFloating && w->m_bIsMapped && box.containsPoint(pos) && w->workspaceID() == WSPID && !w->isHidden() && !w->m_bX11ShouldntFocus &&
|
|
!w->m_sWindowData.noFocus.valueOrDefault() && w != pIgnoreWindow)
|
|
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};
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromOutput(SP<Aquamarine::IOutput> out) {
|
|
for (auto& m : m_vMonitors) {
|
|
if (m->output == out) {
|
|
return m.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CMonitor* CCompositor::getRealMonitorFromOutput(SP<Aquamarine::IOutput> out) {
|
|
for (auto& m : m_vRealMonitors) {
|
|
if (m->output == out) {
|
|
return m.get();
|
|
}
|
|
}
|
|
|
|
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->m_iX11Type == 2 && !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 = getMonitorFromID(pWindow->m_iMonitorID);
|
|
|
|
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->m_iX11Type == 1)
|
|
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() && !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, CMonitor* monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) {
|
|
for (auto& lsl : monitor->m_aLayerSurfaceLayers | std::views::reverse) {
|
|
for (auto& 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& 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& w : m_vWindows) {
|
|
if ((uint32_t)(((uint64_t)w.get()) & 0xFFFFFFFF) == handle) {
|
|
return w;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
PHLWINDOW CCompositor::getFullscreenWindowOnWorkspace(const WORKSPACEID& ID) {
|
|
for (auto& 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 = getMonitorFromID(w->m_iMonitorID);
|
|
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& 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& 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& 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& w : m_vWindows) {
|
|
if (w->m_bIsMapped && w->m_bIsUrgent)
|
|
return w;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool CCompositor::hasUrgentWindowOnWorkspace(const WORKSPACEID& id) {
|
|
for (auto& 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& 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 = getMonitorFromID(PWORKSPACE->m_iMonitorID);
|
|
|
|
for (auto& 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;
|
|
|
|
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(getMonitorFromID(pw->m_iMonitorID));
|
|
};
|
|
|
|
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& 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& ww : m_vWindowsFadingOut) {
|
|
|
|
auto w = ww.lock();
|
|
|
|
if (w->m_iMonitorID != monid)
|
|
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& lsr : m_vSurfacesFadingOut) {
|
|
|
|
auto ls = lsr.lock();
|
|
|
|
if (!ls) {
|
|
layersDirty = true;
|
|
continue;
|
|
}
|
|
|
|
if (ls->monitorID != monid)
|
|
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& 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 = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID);
|
|
|
|
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& w : m_vWindows) {
|
|
if (w == pWindow || !w->m_bIsMapped || w->isHidden() || (!w->isFullscreen() && w->m_bIsFloating) || !isWorkspaceVisible(w->m_pWorkspace))
|
|
continue;
|
|
|
|
if (pWindow->m_iMonitorID == w->m_iMonitorID && pWindow->m_pWorkspace != w->m_pWorkspace)
|
|
continue;
|
|
|
|
if (PWORKSPACE->m_bHasFullscreenWindow && !w->isFullscreen() && !w->m_bCreatedOverFullscreen)
|
|
continue;
|
|
|
|
if (!*PMONITORFALLBACK && pWindow->m_iMonitorID != w->m_iMonitorID)
|
|
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& w : m_vWindows) {
|
|
if (w == pWindow || !w->m_bIsMapped || w->isHidden() || (!w->isFullscreen() && !w->m_bIsFloating) || !isWorkspaceVisible(w->m_pWorkspace))
|
|
continue;
|
|
|
|
if (pWindow->m_iMonitorID == w->m_iMonitorID && pWindow->m_pWorkspace != w->m_pWorkspace)
|
|
continue;
|
|
|
|
if (PWORKSPACE->m_bHasFullscreenWindow && !w->isFullscreen() && !w->m_bCreatedOverFullscreen)
|
|
continue;
|
|
|
|
if (!*PMONITORFALLBACK && pWindow->m_iMonitorID != w->m_iMonitorID)
|
|
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& 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& 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& 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& 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& 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& 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& 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 CMonitor* 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);
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorInDirection(const char& dir) {
|
|
return this->getMonitorInDirection(m_pLastMonitor.get(), dir);
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorInDirection(CMonitor* pSourceMonitor, const char& dir) {
|
|
if (!pSourceMonitor)
|
|
return nullptr;
|
|
|
|
const auto POSA = pSourceMonitor->vecPosition;
|
|
const auto SIZEA = pSourceMonitor->vecSize;
|
|
|
|
auto longestIntersect = -1;
|
|
CMonitor* longestIntersectMonitor = nullptr;
|
|
|
|
for (auto& 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.get();
|
|
}
|
|
}
|
|
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.get();
|
|
}
|
|
}
|
|
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.get();
|
|
}
|
|
}
|
|
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.get();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (longestIntersect != -1)
|
|
return longestIntersectMonitor;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void CCompositor::updateAllWindowsAnimatedDecorationValues() {
|
|
for (auto& w : m_vWindows) {
|
|
if (!w->m_bIsMapped)
|
|
continue;
|
|
|
|
updateWindowAnimatedDecorationValues(w);
|
|
}
|
|
}
|
|
|
|
void CCompositor::updateWorkspaceWindows(const int64_t& id) {
|
|
for (auto& 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;
|
|
};
|
|
|
|
// 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
|
|
if (pWindow == m_pLastWindow.lock() || pWindow->m_sWindowData.noDim.valueOrDefault() || !*PDIMENABLED) {
|
|
pWindow->m_fDimPercent = 0;
|
|
} else {
|
|
pWindow->m_fDimPercent = *PDIMSTRENGTH;
|
|
}
|
|
|
|
// shadow
|
|
if (pWindow->m_iX11Type != 2 && !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(CMonitor* pMonitorA, CMonitor* pMonitorB) {
|
|
|
|
const auto PWORKSPACEA = pMonitorA->activeWorkspace;
|
|
const auto PWORKSPACEB = pMonitorB->activeWorkspace;
|
|
|
|
PWORKSPACEA->m_iMonitorID = pMonitorB->ID;
|
|
PWORKSPACEA->moveToMonitor(pMonitorB->ID);
|
|
|
|
for (auto& w : m_vWindows) {
|
|
if (w->m_pWorkspace == PWORKSPACEA) {
|
|
if (w->m_bPinned) {
|
|
w->m_pWorkspace = PWORKSPACEB;
|
|
continue;
|
|
}
|
|
|
|
w->m_iMonitorID = pMonitorB->ID;
|
|
|
|
// 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_iMonitorID = pMonitorA->ID;
|
|
PWORKSPACEB->moveToMonitor(pMonitorA->ID);
|
|
|
|
for (auto& w : m_vWindows) {
|
|
if (w->m_pWorkspace == PWORKSPACEB) {
|
|
if (w->m_bPinned) {
|
|
w->m_pWorkspace = PWORKSPACEA;
|
|
continue;
|
|
}
|
|
|
|
w->m_iMonitorID = pMonitorA->ID;
|
|
|
|
// 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}));
|
|
}
|
|
|
|
CMonitor* CCompositor::getMonitorFromString(const std::string& name) {
|
|
if (name == "current")
|
|
return g_pCompositor->m_pLastMonitor.get();
|
|
else if (isDirection(name))
|
|
return getMonitorInDirection(name[0]);
|
|
else if (name[0] == '+' || name[0] == '-') {
|
|
// relative
|
|
|
|
if (m_vMonitors.size() == 1)
|
|
return m_vMonitors.begin()->get();
|
|
|
|
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].get();
|
|
} 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& m : m_vMonitors) {
|
|
if (!m->output)
|
|
continue;
|
|
|
|
if (m->matchesStaticSelector(name)) {
|
|
return m.get();
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, CMonitor* pMonitor, bool noWarpCursor) {
|
|
|
|
// We trust the monitor to be correct.
|
|
|
|
if (pWorkspace->m_iMonitorID == pMonitor->ID)
|
|
return;
|
|
|
|
Debug::log(LOG, "moveWorkspaceToMonitor: Moving {} to monitor {}", pWorkspace->m_iID, pMonitor->ID);
|
|
|
|
const auto POLDMON = getMonitorFromID(pWorkspace->m_iMonitorID);
|
|
|
|
const bool SWITCHINGISACTIVE = POLDMON ? POLDMON->activeWorkspace == pWorkspace : false;
|
|
|
|
// fix old mon
|
|
WORKSPACEID nextWorkspaceOnMonitorID = WORKSPACE_INVALID;
|
|
if (!SWITCHINGISACTIVE)
|
|
nextWorkspaceOnMonitorID = pWorkspace->m_iID;
|
|
else {
|
|
for (auto& w : m_vWorkspaces) {
|
|
if (w->m_iMonitorID == POLDMON->ID && 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_iMonitorID = pMonitor->ID;
|
|
pWorkspace->moveToMonitor(pMonitor->ID);
|
|
|
|
for (auto& w : m_vWindows) {
|
|
if (w->m_pWorkspace == pWorkspace) {
|
|
if (w->m_bPinned) {
|
|
w->m_pWorkspace = g_pCompositor->getWorkspaceByID(nextWorkspaceOnMonitorID);
|
|
continue;
|
|
}
|
|
|
|
w->m_iMonitorID = pMonitor->ID;
|
|
|
|
// 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.get()) { // 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& 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& 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 = getMonitorFromID(pWorkspace->m_iMonitorID);
|
|
|
|
if (pWorkspace->m_iID == PMONITOR->activeWorkspaceID() || pWorkspace->m_iID == PMONITOR->activeSpecialWorkspaceID()) {
|
|
for (auto& 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 = getMonitorFromID(PWINDOW->m_iMonitorID);
|
|
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)
|
|
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();
|
|
PWINDOW->updateWindowDecos();
|
|
updateWindowAnimatedDecorationValues(PWINDOW);
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->m_iMonitorID);
|
|
|
|
// make all windows on the same workspace under the fullscreen window
|
|
for (auto& 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& 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& 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& w : m_vWindows) {
|
|
if (w->workspaceID() != id)
|
|
continue;
|
|
|
|
w->updateWindowData(WORKSPACERULE);
|
|
}
|
|
}
|
|
|
|
void CCompositor::scheduleFrameForMonitor(CMonitor* 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) {
|
|
if (regexp.starts_with("active"))
|
|
return m_pLastWindow.lock();
|
|
|
|
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);
|
|
} 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& 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;
|
|
}
|
|
|
|
for (auto& 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.get())
|
|
setActiveMonitor(PMONITORNEW);
|
|
return;
|
|
}
|
|
|
|
g_pPointerManager->warpTo(pos);
|
|
|
|
const auto PMONITORNEW = getMonitorFromVector(pos);
|
|
if (PMONITORNEW != m_pLastMonitor.get())
|
|
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& 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& 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 isEmtpy) {
|
|
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, monID, NAME, SPECIAL, isEmtpy));
|
|
|
|
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(CMonitor* pMonitor) {
|
|
if (m_pLastMonitor.get() == 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& ws : m_vWorkspaces) {
|
|
if (ws->m_bIsSpecialWorkspace && ws->m_iID > highest) {
|
|
highest = ws->m_iID;
|
|
}
|
|
}
|
|
|
|
return highest + 1;
|
|
}
|
|
|
|
void CCompositor::performUserChecks() {
|
|
; // intentional
|
|
}
|
|
|
|
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);
|
|
|
|
if (!pWindow->m_bIsFloating) {
|
|
g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow);
|
|
pWindow->moveToWorkspace(pWorkspace);
|
|
pWindow->m_iMonitorID = pWorkspace->m_iMonitorID;
|
|
g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
|
|
} else {
|
|
const auto PWINDOWMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID);
|
|
const auto POSTOMON = pWindow->m_vRealPosition.goal() - PWINDOWMONITOR->vecPosition;
|
|
|
|
const auto PWORKSPACEMONITOR = g_pCompositor->getMonitorFromID(pWorkspace->m_iMonitorID);
|
|
|
|
pWindow->moveToWorkspace(pWorkspace);
|
|
pWindow->m_iMonitorID = pWorkspace->m_iMonitorID;
|
|
|
|
pWindow->m_vRealPosition = POSTOMON + PWORKSPACEMONITOR->vecPosition;
|
|
}
|
|
|
|
pWindow->updateToplevel();
|
|
pWindow->updateDynamicRules();
|
|
pWindow->uncacheWindowDecos();
|
|
|
|
if (!pWindow->m_sGroupData.pNextWindow.expired()) {
|
|
PHLWINDOW next = pWindow->m_sGroupData.pNextWindow.lock();
|
|
while (next != pWindow) {
|
|
next->moveToWorkspace(pWorkspace);
|
|
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& 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<CMonitor*> toArrange;
|
|
std::vector<CMonitor*> arranged;
|
|
|
|
for (auto& m : m_vMonitors)
|
|
toArrange.push_back(m.get());
|
|
|
|
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& 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& 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& 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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void CCompositor::leaveUnsafeState() {
|
|
if (!m_bUnsafeState)
|
|
return;
|
|
|
|
Debug::log(LOG, "Leaving unsafe state");
|
|
|
|
m_bUnsafeState = false;
|
|
|
|
CMonitor* pNewMonitor = nullptr;
|
|
for (auto& pMonitor : m_vMonitors) {
|
|
if (pMonitor->output != m_pUnsafeOutput->output) {
|
|
pNewMonitor = pMonitor.get();
|
|
break;
|
|
}
|
|
}
|
|
|
|
RASSERT(pNewMonitor, "Tried to leave unsafe without a monitor");
|
|
|
|
if (m_pUnsafeOutput->m_bEnabled)
|
|
m_pUnsafeOutput->onDisconnect();
|
|
|
|
for (auto& m : m_vMonitors) {
|
|
scheduleFrameForMonitor(m.get());
|
|
}
|
|
}
|
|
|
|
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& 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& w : m_vWindows) {
|
|
if (w.get() != pWindow)
|
|
continue;
|
|
|
|
return w;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void CCompositor::onNewMonitor(SP<Aquamarine::IOutput> output) {
|
|
// add it to real
|
|
auto PNEWMONITOR = g_pCompositor->m_vRealMonitors.emplace_back(makeShared<CMonitor>());
|
|
if (std::string("HEADLESS-1") == output->name) {
|
|
g_pCompositor->m_pUnsafeOutput = PNEWMONITOR.get();
|
|
output->name = "FALLBACK"; // we are allowed to do this :)
|
|
}
|
|
|
|
Debug::log(LOG, "New output with name {}", output->name);
|
|
|
|
PNEWMONITOR->szName = output->name;
|
|
PNEWMONITOR->output = output;
|
|
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.get();
|
|
|
|
g_pCompositor->m_bReadyToProcess = true;
|
|
|
|
g_pConfigManager->m_bWantsMonitorReload = true;
|
|
g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR.get(), IOutput::AQ_SCHEDULE_NEW_MONITOR);
|
|
|
|
for (auto& w : g_pCompositor->m_vWindows) {
|
|
if (w->m_iMonitorID == PNEWMONITOR->ID) {
|
|
w->m_iLastSurfaceMonitorID = MONITOR_INVALID;
|
|
w->updateSurfaceScaleTransformDetails();
|
|
}
|
|
}
|
|
|
|
g_pHyprRenderer->damageMonitor(PNEWMONITOR.get());
|
|
Events::listener_monitorFrame(PNEWMONITOR.get(), nullptr);
|
|
}
|