mirror of
https://github.com/hyprwm/Hyprland
synced 2025-12-20 16:10:10 +01:00
* syncobj: cleanup and use uniqueptrs cleanup a bit missing removals if resource not good, erasing from containers etc. make use of unique ptrs instead. and add default destructors. * syncobj: rework syncobj entirerly remove early buffer release that was breaking explicit sync, the buffer needs to exist until the surface commit event has been emitted and draw calls added egl sync points, move to eventfd signaling instead of stalling sync point checks, and recommit pending commits if waiting on a signal. add a CDRMSyncPointState helper class. move a few weak pointers to shared pointers so they dont destruct before we need to use them. * syncobj: queue pending states for eventfd eventfd requires us to queue pending stats until ready and then apply to current, and also when no ready state exist commit the client commit on the current existing buffer, if there is one. * syncobj: clear current buffer damage clear current buffer damage on current buffer commits. * syncobj: cleanup code and fix hyprlock remove unused code, and ensure we dont commit a empty texture causing locksession protocol and gtk4-layer-shell misbehaving. * syncobj: ensure buffers are cleaned up ensure the containers having the various buffers actually gets cleaned up from their containers, incase the CSignal isnt signaled because of expired smart pointers or just wrong order destruction because mishaps. also move the acquire/point setting to buffer attaching. instead of on precommit. * syncobj: remove unused code, optimize remove unused code and merge sync fds if fence is valid, remove manual directscanout buffer dropping that signals release point on pageflip, it can cause us to signal the release point while still keeping the current buffer and rendering it yet again causing wrong things. * syncobj: delay buffer release on non syncobj delay buffer releases on non syncobj surfaces until next commit, and check on async buffers if syncobj and drop and signal the release point on backend buffer release. * syncobj: ensure we follow protocol ensure we follow protocol by replacing acquire/release points if they arrive late and replace already existing ones. also remove unneded brackets, and dont try to manual lock/release buffers when it comes to explicit protocol. it doesnt care about buffer releases only about acquire and release points and signaling them. * syncobj: lets not complicate things set points in precommit, before checking protocol errors and we catch any pending acquire/release points arriving late. * syncobj: move SSurfaceState to types remove destructor resource destroying, let resources destroys them on their events, and move SSurfaceStates to types/SurfaceState.hpp * syncobj: actually store the merged fd have to actually store the mergedfd to use it. * syncobj: cleanup a bit around fences ensure the current asynchronous buffer is actually released on pageflip not the previous. cleanup a bit FD handling in commitPendingAndDoExplicitSync, and reuse the in fence when syncing surfaces. * syncobjs: ensure fence FD doesnt leak calling resetexplicitfence without properly ensuring the FD is closed before will leak it, store it per monitor and let it close itself with the CFileDescriptor class. * syncobj: ensure buffers are actually released buffers were never being sent released properly. * types: Defer buffer sync releaser until unlock * syncobj: store directscanout fence in monitor ensure the infence fd survives the scope of attemptdirectscanout so it doesnt close before it should have. * syncobj: check if if acquire is expired we might hit a race to finish on exit where the timeline just has destructed but the buffer waiter is still pending. and such we removeAllWaiters null dereferences. * syncobj: code style changes remove quack comment, change to m_foo and use a std::vector and weakpointer in the waiter for removal instead of a std::list. * syncobj: remove unused async buffer drop remove unused async buffer drop, only related to directscanout and is handled elsewhere. --------- Co-authored-by: Lee Bousfield <ljbousfield@gmail.com>
1583 lines
59 KiB
C++
1583 lines
59 KiB
C++
#include "Monitor.hpp"
|
|
#include "MiscFunctions.hpp"
|
|
#include "../macros.hpp"
|
|
#include "math/Math.hpp"
|
|
#include "../protocols/ColorManagement.hpp"
|
|
#include "sync/SyncReleaser.hpp"
|
|
#include "../Compositor.hpp"
|
|
#include "../config/ConfigValue.hpp"
|
|
#include "../protocols/GammaControl.hpp"
|
|
#include "../devices/ITouch.hpp"
|
|
#include "../protocols/LayerShell.hpp"
|
|
#include "../protocols/PresentationTime.hpp"
|
|
#include "../protocols/DRMLease.hpp"
|
|
#include "../protocols/DRMSyncobj.hpp"
|
|
#include "../protocols/core/Output.hpp"
|
|
#include "../protocols/Screencopy.hpp"
|
|
#include "../protocols/ToplevelExport.hpp"
|
|
#include "../managers/PointerManager.hpp"
|
|
#include "../managers/eventLoop/EventLoopManager.hpp"
|
|
#include "../protocols/core/Compositor.hpp"
|
|
#include "../render/Renderer.hpp"
|
|
#include "../managers/EventManager.hpp"
|
|
#include "../managers/LayoutManager.hpp"
|
|
#include "../managers/input/InputManager.hpp"
|
|
#include "sync/SyncTimeline.hpp"
|
|
#include "../desktop/LayerSurface.hpp"
|
|
#include <aquamarine/output/Output.hpp>
|
|
#include "debug/Log.hpp"
|
|
#include "debug/HyprNotificationOverlay.hpp"
|
|
#include <hyprutils/string/String.hpp>
|
|
#include <hyprutils/utils/ScopeGuard.hpp>
|
|
#include <cstring>
|
|
#include <ranges>
|
|
using namespace Hyprutils::String;
|
|
using namespace Hyprutils::Utils;
|
|
using namespace Hyprutils::OS;
|
|
using enum NContentType::eContentType;
|
|
|
|
static int ratHandler(void* data) {
|
|
g_pHyprRenderer->renderMonitor(((CMonitor*)data)->self.lock());
|
|
|
|
return 1;
|
|
}
|
|
|
|
CMonitor::CMonitor(SP<Aquamarine::IOutput> output_) : state(this), output(output_) {
|
|
;
|
|
}
|
|
|
|
CMonitor::~CMonitor() {
|
|
events.destroy.emit();
|
|
}
|
|
|
|
void CMonitor::onConnect(bool noRule) {
|
|
EMIT_HOOK_EVENT("preMonitorAdded", self.lock());
|
|
CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }};
|
|
|
|
g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
|
|
|
|
if (output->supportsExplicit) {
|
|
inTimeline = CSyncTimeline::create(output->getBackend()->drmFD());
|
|
outTimeline = CSyncTimeline::create(output->getBackend()->drmFD());
|
|
}
|
|
|
|
listeners.frame = output->events.frame.registerListener([this](std::any d) { onMonitorFrame(); });
|
|
listeners.commit = output->events.commit.registerListener([this](std::any d) {
|
|
if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
|
|
PROTO::screencopy->onOutputCommit(self.lock());
|
|
PROTO::toplevelExport->onOutputCommit(self.lock());
|
|
}
|
|
});
|
|
listeners.needsFrame =
|
|
output->events.needsFrame.registerListener([this](std::any d) { g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); });
|
|
|
|
listeners.presented = output->events.present.registerListener([this](std::any d) {
|
|
auto E = std::any_cast<Aquamarine::IOutput::SPresentEvent>(d);
|
|
PROTO::presentation->onPresented(self.lock(), E.when, E.refresh, E.seq, E.flags);
|
|
});
|
|
|
|
listeners.destroy = output->events.destroy.registerListener([this](std::any d) {
|
|
Debug::log(LOG, "Destroy called for monitor {}", szName);
|
|
|
|
onDisconnect(true);
|
|
|
|
output = nullptr;
|
|
m_bRenderingInitPassed = false;
|
|
|
|
Debug::log(LOG, "Removing monitor {} from realMonitors", szName);
|
|
|
|
std::erase_if(g_pCompositor->m_vRealMonitors, [&](PHLMONITOR& el) { return el.get() == this; });
|
|
});
|
|
|
|
listeners.state = output->events.state.registerListener([this](std::any d) {
|
|
auto E = std::any_cast<Aquamarine::IOutput::SStateEvent>(d);
|
|
|
|
if (E.size == Vector2D{}) {
|
|
// an indication to re-set state
|
|
// we can't do much for createdByUser displays I think
|
|
if (createdByUser)
|
|
return;
|
|
|
|
Debug::log(LOG, "Reapplying monitor rule for {} from a state request", szName);
|
|
applyMonitorRule(&activeMonitorRule, true);
|
|
return;
|
|
}
|
|
|
|
if (!createdByUser)
|
|
return;
|
|
|
|
const auto SIZE = E.size;
|
|
|
|
forceSize = SIZE;
|
|
|
|
SMonitorRule rule = activeMonitorRule;
|
|
rule.resolution = SIZE;
|
|
|
|
applyMonitorRule(&rule);
|
|
});
|
|
|
|
tearingState.canTear = output->getBackend()->type() == Aquamarine::AQ_BACKEND_DRM;
|
|
|
|
if (m_bEnabled) {
|
|
output->state->resetExplicitFences();
|
|
output->state->setEnabled(true);
|
|
state.commit();
|
|
return;
|
|
}
|
|
|
|
szName = output->name;
|
|
|
|
szDescription = output->description;
|
|
// remove comma character from description. This allow monitor specific rules to work on monitor with comma on their description
|
|
std::erase(szDescription, ',');
|
|
|
|
// field is backwards-compatible with intended usage of `szDescription` but excludes the parenthesized DRM node name suffix
|
|
szShortDescription = trim(std::format("{} {} {}", output->make, output->model, output->serial));
|
|
std::erase(szShortDescription, ',');
|
|
|
|
if (output->getBackend()->type() != Aquamarine::AQ_BACKEND_DRM)
|
|
createdByUser = true; // should be true. WL and Headless backends should be addable / removable
|
|
|
|
// get monitor rule that matches
|
|
SMonitorRule monitorRule = g_pConfigManager->getMonitorRuleFor(self.lock());
|
|
|
|
// if it's disabled, disable and ignore
|
|
if (monitorRule.disabled) {
|
|
|
|
output->state->resetExplicitFences();
|
|
output->state->setEnabled(false);
|
|
|
|
if (!state.commit())
|
|
Debug::log(ERR, "Couldn't commit disabled state on output {}", output->name);
|
|
|
|
m_bEnabled = false;
|
|
|
|
listeners.frame.reset();
|
|
return;
|
|
}
|
|
|
|
if (output->nonDesktop) {
|
|
Debug::log(LOG, "Not configuring non-desktop output");
|
|
if (PROTO::lease)
|
|
PROTO::lease->offer(self.lock());
|
|
|
|
return;
|
|
}
|
|
|
|
PHLMONITOR* thisWrapper = nullptr;
|
|
|
|
// find the wrap
|
|
for (auto& m : g_pCompositor->m_vRealMonitors) {
|
|
if (m->ID == ID) {
|
|
thisWrapper = &m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RASSERT(thisWrapper->get(), "CMonitor::onConnect: Had no wrapper???");
|
|
|
|
if (std::find_if(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_vMonitors.end())
|
|
g_pCompositor->m_vMonitors.push_back(*thisWrapper);
|
|
|
|
m_bEnabled = true;
|
|
|
|
output->state->resetExplicitFences();
|
|
output->state->setEnabled(true);
|
|
|
|
// set mode, also applies
|
|
if (!noRule)
|
|
applyMonitorRule(&monitorRule, true);
|
|
|
|
if (!state.commit())
|
|
Debug::log(WARN, "state.commit() failed in CMonitor::onCommit");
|
|
|
|
damage.setSize(vecTransformedSize);
|
|
|
|
Debug::log(LOG, "Added new monitor with name {} at {:j0} with size {:j0}, pointer {:x}", output->name, vecPosition, vecPixelSize, (uintptr_t)output.get());
|
|
|
|
setupDefaultWS(monitorRule);
|
|
|
|
for (auto const& ws : g_pCompositor->m_vWorkspaces) {
|
|
if (!valid(ws))
|
|
continue;
|
|
|
|
if (ws->m_szLastMonitor == szName || g_pCompositor->m_vMonitors.size() == 1 /* avoid lost workspaces on recover */) {
|
|
g_pCompositor->moveWorkspaceToMonitor(ws, self.lock());
|
|
ws->startAnim(true, true, true);
|
|
ws->m_szLastMonitor = "";
|
|
}
|
|
}
|
|
|
|
scale = monitorRule.scale;
|
|
if (scale < 0.1)
|
|
scale = getDefaultScale();
|
|
|
|
forceFullFrames = 3; // force 3 full frames to make sure there is no blinking due to double-buffering.
|
|
//
|
|
|
|
if (!activeMonitorRule.mirrorOf.empty())
|
|
setMirror(activeMonitorRule.mirrorOf);
|
|
|
|
if (!g_pCompositor->m_pLastMonitor) // set the last monitor if it isnt set yet
|
|
g_pCompositor->setActiveMonitor(self.lock());
|
|
|
|
g_pHyprRenderer->arrangeLayersForMonitor(ID);
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(ID);
|
|
|
|
// ensure VRR (will enable if necessary)
|
|
g_pConfigManager->ensureVRR(self.lock());
|
|
|
|
// verify last mon valid
|
|
bool found = false;
|
|
for (auto const& m : g_pCompositor->m_vMonitors) {
|
|
if (m == g_pCompositor->m_pLastMonitor) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
g_pCompositor->setActiveMonitor(self.lock());
|
|
|
|
renderTimer = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, ratHandler, this);
|
|
|
|
g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEW_MONITOR);
|
|
|
|
PROTO::gamma->applyGammaToState(self.lock());
|
|
|
|
events.connect.emit();
|
|
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", szName});
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", ID, szName, szShortDescription)});
|
|
EMIT_HOOK_EVENT("monitorAdded", self.lock());
|
|
}
|
|
|
|
void CMonitor::onDisconnect(bool destroy) {
|
|
EMIT_HOOK_EVENT("preMonitorRemoved", self.lock());
|
|
CScopeGuard x = {[this]() {
|
|
if (g_pCompositor->m_bIsShuttingDown)
|
|
return;
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", szName});
|
|
EMIT_HOOK_EVENT("monitorRemoved", self.lock());
|
|
g_pCompositor->arrangeMonitors();
|
|
}};
|
|
|
|
if (renderTimer) {
|
|
wl_event_source_remove(renderTimer);
|
|
renderTimer = nullptr;
|
|
}
|
|
|
|
if (!m_bEnabled || g_pCompositor->m_bIsShuttingDown)
|
|
return;
|
|
|
|
Debug::log(LOG, "onDisconnect called for {}", output->name);
|
|
|
|
events.disconnect.emit();
|
|
|
|
// Cleanup everything. Move windows back, snap cursor, shit.
|
|
PHLMONITOR BACKUPMON = nullptr;
|
|
for (auto const& m : g_pCompositor->m_vMonitors) {
|
|
if (m.get() != this) {
|
|
BACKUPMON = m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remove mirror
|
|
if (pMirrorOf) {
|
|
pMirrorOf->mirrors.erase(std::find_if(pMirrorOf->mirrors.begin(), pMirrorOf->mirrors.end(), [&](const auto& other) { return other == self; }));
|
|
|
|
// unlock software for mirrored monitor
|
|
g_pPointerManager->unlockSoftwareForMonitor(pMirrorOf.lock());
|
|
pMirrorOf.reset();
|
|
}
|
|
|
|
if (!mirrors.empty()) {
|
|
for (auto const& m : mirrors) {
|
|
m->setMirror("");
|
|
}
|
|
|
|
g_pConfigManager->m_bWantsMonitorReload = true;
|
|
}
|
|
|
|
listeners.frame.reset();
|
|
listeners.presented.reset();
|
|
listeners.needsFrame.reset();
|
|
listeners.commit.reset();
|
|
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
for (auto const& ls : m_aLayerSurfaceLayers[i]) {
|
|
if (ls->layerSurface && !ls->fadingOut)
|
|
ls->layerSurface->sendClosed();
|
|
}
|
|
m_aLayerSurfaceLayers[i].clear();
|
|
}
|
|
|
|
Debug::log(LOG, "Removed monitor {}!", szName);
|
|
|
|
if (!BACKUPMON) {
|
|
Debug::log(WARN, "Unplugged last monitor, entering an unsafe state. Good luck my friend.");
|
|
g_pCompositor->enterUnsafeState();
|
|
}
|
|
|
|
m_bEnabled = false;
|
|
m_bRenderingInitPassed = false;
|
|
|
|
if (BACKUPMON) {
|
|
// snap cursor
|
|
g_pCompositor->warpCursorTo(BACKUPMON->vecPosition + BACKUPMON->vecTransformedSize / 2.F, true);
|
|
|
|
// move workspaces
|
|
std::vector<PHLWORKSPACE> wspToMove;
|
|
for (auto const& w : g_pCompositor->m_vWorkspaces) {
|
|
if (w->m_pMonitor == self || !w->m_pMonitor)
|
|
wspToMove.push_back(w);
|
|
}
|
|
|
|
for (auto const& w : wspToMove) {
|
|
w->m_szLastMonitor = szName;
|
|
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
|
|
w->startAnim(true, true, true);
|
|
}
|
|
} else {
|
|
g_pCompositor->m_pLastFocus.reset();
|
|
g_pCompositor->m_pLastWindow.reset();
|
|
g_pCompositor->m_pLastMonitor.reset();
|
|
}
|
|
|
|
if (activeWorkspace)
|
|
activeWorkspace->m_bVisible = false;
|
|
activeWorkspace.reset();
|
|
|
|
output->state->resetExplicitFences();
|
|
output->state->setAdaptiveSync(false);
|
|
output->state->setEnabled(false);
|
|
|
|
if (!state.commit())
|
|
Debug::log(WARN, "state.commit() failed in CMonitor::onDisconnect");
|
|
|
|
if (g_pCompositor->m_pLastMonitor == self)
|
|
g_pCompositor->setActiveMonitor(BACKUPMON ? BACKUPMON : g_pCompositor->m_pUnsafeOutput.lock());
|
|
|
|
if (g_pHyprRenderer->m_pMostHzMonitor == self) {
|
|
int mostHz = 0;
|
|
PHLMONITOR pMonitorMostHz = nullptr;
|
|
|
|
for (auto const& m : g_pCompositor->m_vMonitors) {
|
|
if (m->refreshRate > mostHz && m != self) {
|
|
pMonitorMostHz = m;
|
|
mostHz = m->refreshRate;
|
|
}
|
|
}
|
|
|
|
g_pHyprRenderer->m_pMostHzMonitor = pMonitorMostHz;
|
|
}
|
|
std::erase_if(g_pCompositor->m_vMonitors, [&](PHLMONITOR& el) { return el.get() == this; });
|
|
}
|
|
|
|
bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
|
|
|
|
static auto PDISABLESCALECHECKS = CConfigValue<Hyprlang::INT>("debug:disable_scale_checks");
|
|
|
|
Debug::log(LOG, "Applying monitor rule for {}", szName);
|
|
|
|
activeMonitorRule = *pMonitorRule;
|
|
|
|
if (forceSize.has_value())
|
|
activeMonitorRule.resolution = forceSize.value();
|
|
|
|
const auto RULE = &activeMonitorRule;
|
|
|
|
// if it's disabled, disable and ignore
|
|
if (RULE->disabled) {
|
|
if (m_bEnabled)
|
|
onDisconnect();
|
|
|
|
events.modeChanged.emit();
|
|
|
|
return true;
|
|
}
|
|
|
|
// don't touch VR headsets
|
|
if (output->nonDesktop)
|
|
return true;
|
|
|
|
if (!m_bEnabled) {
|
|
onConnect(true); // enable it.
|
|
Debug::log(LOG, "Monitor {} is disabled but is requested to be enabled", szName);
|
|
force = true;
|
|
}
|
|
|
|
// Check if the rule isn't already applied
|
|
// TODO: clean this up lol
|
|
if (!force && DELTALESSTHAN(vecPixelSize.x, RULE->resolution.x, 1) && DELTALESSTHAN(vecPixelSize.y, RULE->resolution.y, 1) &&
|
|
DELTALESSTHAN(refreshRate, RULE->refreshRate, 1) && setScale == RULE->scale &&
|
|
((DELTALESSTHAN(vecPosition.x, RULE->offset.x, 1) && DELTALESSTHAN(vecPosition.y, RULE->offset.y, 1)) || RULE->offset == Vector2D(-INT32_MAX, -INT32_MAX)) &&
|
|
transform == RULE->transform && RULE->enable10bit == enabled10bit && RULE->cmType == cmType && RULE->sdrSaturation == sdrSaturation &&
|
|
RULE->sdrBrightness == sdrBrightness && !std::memcmp(&customDrmMode, &RULE->drmMode, sizeof(customDrmMode))) {
|
|
|
|
Debug::log(LOG, "Not applying a new rule to {} because it's already applied!", szName);
|
|
|
|
setMirror(RULE->mirrorOf);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool autoScale = false;
|
|
|
|
if (RULE->scale > 0.1) {
|
|
scale = RULE->scale;
|
|
} else {
|
|
autoScale = true;
|
|
const auto DEFAULTSCALE = getDefaultScale();
|
|
scale = DEFAULTSCALE;
|
|
}
|
|
|
|
setScale = scale;
|
|
transform = RULE->transform;
|
|
|
|
// accumulate requested modes in reverse order (cause inesrting at front is inefficient)
|
|
std::vector<SP<Aquamarine::SOutputMode>> requestedModes;
|
|
std::string requestedStr = "unknown";
|
|
|
|
// use sortFunc, add best 3 to requestedModes in reverse, since we test in reverse
|
|
auto addBest3Modes = [&](auto const& sortFunc) {
|
|
auto sortedModes = output->modes;
|
|
std::ranges::sort(sortedModes, sortFunc);
|
|
if (sortedModes.size() > 3)
|
|
sortedModes.erase(sortedModes.begin() + 3, sortedModes.end());
|
|
requestedModes.insert(requestedModes.end(), sortedModes.rbegin(), sortedModes.rend());
|
|
};
|
|
|
|
// last fallback is always preferred mode
|
|
if (!output->preferredMode())
|
|
Debug::log(ERR, "Monitor {} has NO PREFERRED MODE", output->name);
|
|
else
|
|
requestedModes.push_back(output->preferredMode());
|
|
|
|
if (RULE->resolution == Vector2D()) {
|
|
requestedStr = "preferred";
|
|
|
|
// fallback to first 3 modes if preferred fails/doesn't exist
|
|
requestedModes = output->modes;
|
|
if (requestedModes.size() > 3)
|
|
requestedModes.erase(requestedModes.begin() + 3, requestedModes.end());
|
|
std::ranges::reverse(requestedModes.begin(), requestedModes.end());
|
|
|
|
if (output->preferredMode())
|
|
requestedModes.push_back(output->preferredMode());
|
|
} else if (RULE->resolution == Vector2D(-1, -1)) {
|
|
requestedStr = "highrr";
|
|
|
|
// sort prioritizing refresh rate 1st and resolution 2nd, then add best 3
|
|
addBest3Modes([](auto const& a, auto const& b) {
|
|
if (std::round(a->refreshRate) > std::round(b->refreshRate))
|
|
return true;
|
|
else if (DELTALESSTHAN((float)a->refreshRate, (float)b->refreshRate, 1.F) && a->pixelSize.x > b->pixelSize.x && a->pixelSize.y > b->pixelSize.y)
|
|
return true;
|
|
return false;
|
|
});
|
|
} else if (RULE->resolution == Vector2D(-1, -2)) {
|
|
requestedStr = "highres";
|
|
|
|
// sort prioritizing resultion 1st and refresh rate 2nd, then add best 3
|
|
addBest3Modes([](auto const& a, auto const& b) {
|
|
if (a->pixelSize.x > b->pixelSize.x && a->pixelSize.y > b->pixelSize.y)
|
|
return true;
|
|
else if (DELTALESSTHAN(a->pixelSize.x, b->pixelSize.x, 1) && DELTALESSTHAN(a->pixelSize.y, b->pixelSize.y, 1) &&
|
|
std::round(a->refreshRate) > std::round(b->refreshRate))
|
|
return true;
|
|
return false;
|
|
});
|
|
} else if (RULE->resolution != Vector2D()) {
|
|
// user requested mode
|
|
requestedStr = std::format("{:X0}@{:.2f}Hz", RULE->resolution, RULE->refreshRate);
|
|
|
|
// sort by closeness to requested, then add best 3
|
|
addBest3Modes([&](auto const& a, auto const& b) {
|
|
if (abs(a->pixelSize.x - RULE->resolution.x) < abs(b->pixelSize.x - RULE->resolution.x))
|
|
return true;
|
|
if (a->pixelSize.x == b->pixelSize.x && abs(a->pixelSize.y - RULE->resolution.y) < abs(b->pixelSize.y - RULE->resolution.y))
|
|
return true;
|
|
if (a->pixelSize == b->pixelSize && abs((a->refreshRate / 1000.f) - RULE->refreshRate) < abs((b->refreshRate / 1000.f) - RULE->refreshRate))
|
|
return true;
|
|
return false;
|
|
});
|
|
|
|
// if the best mode isnt close to requested, then try requested as custom mode first
|
|
if (!requestedModes.empty()) {
|
|
auto bestMode = requestedModes.back();
|
|
if (!DELTALESSTHAN(bestMode->pixelSize.x, RULE->resolution.x, 1) || !DELTALESSTHAN(bestMode->pixelSize.y, RULE->resolution.y, 1) ||
|
|
!DELTALESSTHAN(bestMode->refreshRate / 1000.f, RULE->refreshRate, 1))
|
|
requestedModes.push_back(makeShared<Aquamarine::SOutputMode>(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = RULE->refreshRate * 1000.f}));
|
|
}
|
|
|
|
// then if requested is custom, try custom mode first
|
|
if (RULE->drmMode.type == DRM_MODE_TYPE_USERDEF) {
|
|
if (output->getBackend()->type() != Aquamarine::eBackendType::AQ_BACKEND_DRM)
|
|
Debug::log(ERR, "Tried to set custom modeline on non-DRM output");
|
|
else
|
|
requestedModes.push_back(makeShared<Aquamarine::SOutputMode>(
|
|
Aquamarine::SOutputMode{.pixelSize = {RULE->drmMode.hdisplay, RULE->drmMode.vdisplay}, .refreshRate = RULE->drmMode.vrefresh, .modeInfo = RULE->drmMode}));
|
|
}
|
|
}
|
|
|
|
const auto WAS10B = enabled10bit;
|
|
const auto OLDRES = vecPixelSize;
|
|
bool success = false;
|
|
|
|
// Needed in case we are switching from a custom modeline to a standard mode
|
|
customDrmMode = {};
|
|
currentMode = nullptr;
|
|
|
|
output->state->setFormat(DRM_FORMAT_XRGB8888);
|
|
prevDrmFormat = drmFormat;
|
|
drmFormat = DRM_FORMAT_XRGB8888;
|
|
output->state->resetExplicitFences();
|
|
|
|
if (Debug::trace) {
|
|
Debug::log(TRACE, "Monitor {} requested modes:", szName);
|
|
if (requestedModes.empty())
|
|
Debug::log(TRACE, "| None");
|
|
else {
|
|
for (auto const& mode : requestedModes | std::views::reverse) {
|
|
Debug::log(TRACE, "| {:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto const& mode : requestedModes | std::views::reverse) {
|
|
std::string modeStr = std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f);
|
|
|
|
if (mode->modeInfo.has_value() && mode->modeInfo->type == DRM_MODE_TYPE_USERDEF) {
|
|
output->state->setCustomMode(mode);
|
|
|
|
if (!state.test()) {
|
|
Debug::log(ERR, "Monitor {}: REJECTED custom mode {}!", szName, modeStr);
|
|
continue;
|
|
}
|
|
|
|
customDrmMode = mode->modeInfo.value();
|
|
} else {
|
|
output->state->setMode(mode);
|
|
|
|
if (!state.test()) {
|
|
Debug::log(ERR, "Monitor {}: REJECTED available mode {}!", szName, modeStr);
|
|
if (mode->preferred)
|
|
Debug::log(ERR, "Monitor {}: REJECTED preferred mode!!!", szName);
|
|
continue;
|
|
}
|
|
|
|
customDrmMode = {};
|
|
}
|
|
|
|
refreshRate = mode->refreshRate / 1000.f;
|
|
vecSize = mode->pixelSize;
|
|
currentMode = mode;
|
|
|
|
success = true;
|
|
|
|
if (mode->preferred)
|
|
Debug::log(LOG, "Monitor {}: requested {}, using preferred mode {}", szName, requestedStr, modeStr);
|
|
else if (mode->modeInfo.has_value() && mode->modeInfo->type == DRM_MODE_TYPE_USERDEF)
|
|
Debug::log(LOG, "Monitor {}: requested {}, using custom mode {}", szName, requestedStr, modeStr);
|
|
else
|
|
Debug::log(LOG, "Monitor {}: requested {}, using available mode {}", szName, requestedStr, modeStr);
|
|
|
|
break;
|
|
}
|
|
|
|
// try requested as custom mode jic it works
|
|
if (!success && RULE->resolution != Vector2D() && RULE->resolution != Vector2D(-1, -1) && RULE->resolution != Vector2D(-1, -2)) {
|
|
auto refreshRate = output->getBackend()->type() == Aquamarine::eBackendType::AQ_BACKEND_DRM ? RULE->refreshRate * 1000 : 0;
|
|
auto mode = makeShared<Aquamarine::SOutputMode>(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = refreshRate});
|
|
std::string modeStr = std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f);
|
|
|
|
output->state->setCustomMode(mode);
|
|
|
|
if (state.test()) {
|
|
Debug::log(LOG, "Monitor {}: requested {}, using custom mode {}", szName, requestedStr, modeStr);
|
|
|
|
refreshRate = mode->refreshRate / 1000.f;
|
|
vecSize = mode->pixelSize;
|
|
currentMode = mode;
|
|
customDrmMode = {};
|
|
|
|
success = true;
|
|
} else
|
|
Debug::log(ERR, "Monitor {}: REJECTED custom mode {}!", szName, modeStr);
|
|
}
|
|
|
|
// try any of the modes if none of the above work
|
|
if (!success) {
|
|
for (auto const& mode : output->modes) {
|
|
output->state->setMode(mode);
|
|
|
|
if (!state.test())
|
|
continue;
|
|
|
|
auto errorMessage =
|
|
std::format("Monitor {} failed to set any requested modes, falling back to mode {:X0}@{:.2f}Hz", szName, mode->pixelSize, mode->refreshRate / 1000.f);
|
|
Debug::log(WARN, errorMessage);
|
|
g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING);
|
|
|
|
refreshRate = mode->refreshRate / 1000.f;
|
|
vecSize = mode->pixelSize;
|
|
currentMode = mode;
|
|
customDrmMode = {};
|
|
|
|
success = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
Debug::log(ERR, "Monitor {} has NO FALLBACK MODES, and an INVALID one was requested: {:X0}@{:.2f}Hz", szName, RULE->resolution, RULE->refreshRate);
|
|
return true;
|
|
}
|
|
|
|
vrrActive = output->state->state().adaptiveSync // disabled here, will be tested in CConfigManager::ensureVRR()
|
|
|| createdByUser; // wayland backend doesn't allow for disabling adaptive_sync
|
|
|
|
vecPixelSize = vecSize;
|
|
|
|
// clang-format off
|
|
static const std::array<std::vector<std::pair<std::string, uint32_t>>, 2> formats{
|
|
std::vector<std::pair<std::string, uint32_t>>{ /* 10-bit */
|
|
{"DRM_FORMAT_XRGB2101010", DRM_FORMAT_XRGB2101010}, {"DRM_FORMAT_XBGR2101010", DRM_FORMAT_XBGR2101010}, {"DRM_FORMAT_XRGB8888", DRM_FORMAT_XRGB8888}, {"DRM_FORMAT_XBGR8888", DRM_FORMAT_XBGR8888}
|
|
},
|
|
std::vector<std::pair<std::string, uint32_t>>{ /* 8-bit */
|
|
{"DRM_FORMAT_XRGB8888", DRM_FORMAT_XRGB8888}, {"DRM_FORMAT_XBGR8888", DRM_FORMAT_XBGR8888}
|
|
}
|
|
};
|
|
// clang-format on
|
|
|
|
bool set10bit = false;
|
|
|
|
for (auto const& fmt : formats[(int)!RULE->enable10bit]) {
|
|
output->state->setFormat(fmt.second);
|
|
prevDrmFormat = drmFormat;
|
|
drmFormat = fmt.second;
|
|
|
|
if (!state.test()) {
|
|
Debug::log(ERR, "output {} failed basic test on format {}", szName, fmt.first);
|
|
} else {
|
|
Debug::log(LOG, "output {} succeeded basic test on format {}", szName, fmt.first);
|
|
if (RULE->enable10bit && fmt.first.contains("101010"))
|
|
set10bit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
enabled10bit = set10bit;
|
|
|
|
auto oldImageDescription = imageDescription;
|
|
cmType = RULE->cmType;
|
|
switch (cmType) {
|
|
case CM_AUTO: cmType = enabled10bit && output->parsedEDID.supportsBT2020 ? CM_WIDE : CM_SRGB; break;
|
|
case CM_EDID: cmType = output->parsedEDID.chromaticityCoords.has_value() ? CM_EDID : CM_SRGB; break;
|
|
case CM_HDR:
|
|
case CM_HDR_EDID:
|
|
cmType = output->parsedEDID.supportsBT2020 && output->parsedEDID.hdrMetadata.has_value() && output->parsedEDID.hdrMetadata->supportsPQ ? cmType : CM_SRGB;
|
|
break;
|
|
default: break;
|
|
}
|
|
switch (cmType) {
|
|
case CM_SRGB: imageDescription = {}; break; // assumes SImageDescirption defaults to sRGB
|
|
case CM_WIDE:
|
|
imageDescription = {.primariesNameSet = true,
|
|
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
|
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)};
|
|
break;
|
|
case CM_EDID:
|
|
imageDescription = {.primariesNameSet = false,
|
|
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
|
.primaries = {
|
|
.red = {.x = output->parsedEDID.chromaticityCoords->red.x, .y = output->parsedEDID.chromaticityCoords->red.y},
|
|
.green = {.x = output->parsedEDID.chromaticityCoords->green.x, .y = output->parsedEDID.chromaticityCoords->green.y},
|
|
.blue = {.x = output->parsedEDID.chromaticityCoords->blue.x, .y = output->parsedEDID.chromaticityCoords->blue.y},
|
|
.white = {.x = output->parsedEDID.chromaticityCoords->white.x, .y = output->parsedEDID.chromaticityCoords->white.y},
|
|
}};
|
|
break;
|
|
case CM_HDR:
|
|
imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
|
.primariesNameSet = true,
|
|
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
|
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
|
.luminances = {.min = 0, .max = 10000, .reference = 203}};
|
|
break;
|
|
case CM_HDR_EDID:
|
|
imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
|
.primariesNameSet = false,
|
|
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
|
.primaries = output->parsedEDID.chromaticityCoords.has_value() ?
|
|
NColorManagement::SPCPRimaries{
|
|
.red = {.x = output->parsedEDID.chromaticityCoords->red.x, .y = output->parsedEDID.chromaticityCoords->red.y},
|
|
.green = {.x = output->parsedEDID.chromaticityCoords->green.x, .y = output->parsedEDID.chromaticityCoords->green.y},
|
|
.blue = {.x = output->parsedEDID.chromaticityCoords->blue.x, .y = output->parsedEDID.chromaticityCoords->blue.y},
|
|
.white = {.x = output->parsedEDID.chromaticityCoords->white.x, .y = output->parsedEDID.chromaticityCoords->white.y},
|
|
} :
|
|
NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
|
.luminances = {.min = output->parsedEDID.hdrMetadata->desiredContentMinLuminance,
|
|
.max = output->parsedEDID.hdrMetadata->desiredContentMaxLuminance,
|
|
.reference = output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}};
|
|
|
|
break;
|
|
default: UNREACHABLE();
|
|
}
|
|
if (oldImageDescription != imageDescription)
|
|
PROTO::colorManagement->onMonitorImageDescriptionChanged(self);
|
|
|
|
sdrSaturation = RULE->sdrSaturation;
|
|
sdrBrightness = RULE->sdrBrightness;
|
|
|
|
Vector2D logicalSize = vecPixelSize / scale;
|
|
if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {
|
|
// invalid scale, will produce fractional pixels.
|
|
// find the nearest valid.
|
|
|
|
float searchScale = std::round(scale * 120.0);
|
|
bool found = false;
|
|
|
|
double scaleZero = searchScale / 120.0;
|
|
|
|
Vector2D logicalZero = vecPixelSize / scaleZero;
|
|
if (logicalZero == logicalZero.round())
|
|
scale = scaleZero;
|
|
else {
|
|
for (size_t i = 1; i < 90; ++i) {
|
|
double scaleUp = (searchScale + i) / 120.0;
|
|
double scaleDown = (searchScale - i) / 120.0;
|
|
|
|
Vector2D logicalUp = vecPixelSize / scaleUp;
|
|
Vector2D logicalDown = vecPixelSize / scaleDown;
|
|
|
|
if (logicalUp == logicalUp.round()) {
|
|
found = true;
|
|
searchScale = scaleUp;
|
|
break;
|
|
}
|
|
if (logicalDown == logicalDown.round()) {
|
|
found = true;
|
|
searchScale = scaleDown;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
if (autoScale)
|
|
scale = std::round(scaleZero);
|
|
else {
|
|
Debug::log(ERR, "Invalid scale passed to monitor, {} failed to find a clean divisor", scale);
|
|
g_pConfigManager->addParseError("Invalid scale passed to monitor " + szName + ", failed to find a clean divisor");
|
|
scale = getDefaultScale();
|
|
}
|
|
} else {
|
|
if (!autoScale) {
|
|
Debug::log(ERR, "Invalid scale passed to monitor, {} found suggestion {}", scale, searchScale);
|
|
g_pConfigManager->addParseError(
|
|
std::format("Invalid scale passed to monitor {}, failed to find a clean divisor. Suggested nearest scale: {:5f}", szName, searchScale));
|
|
scale = getDefaultScale();
|
|
} else
|
|
scale = searchScale;
|
|
}
|
|
}
|
|
}
|
|
|
|
output->scheduleFrame();
|
|
|
|
if (!state.commit())
|
|
Debug::log(ERR, "Couldn't commit output named {}", output->name);
|
|
|
|
Vector2D xfmd = transform % 2 == 1 ? Vector2D{vecPixelSize.y, vecPixelSize.x} : vecPixelSize;
|
|
vecSize = (xfmd / scale).round();
|
|
vecTransformedSize = xfmd;
|
|
|
|
if (createdByUser) {
|
|
CBox transformedBox = {0, 0, vecTransformedSize.x, vecTransformedSize.y};
|
|
transformedBox.transform(wlTransformToHyprutils(invertTransform(transform)), vecTransformedSize.x, vecTransformedSize.y);
|
|
|
|
vecPixelSize = Vector2D(transformedBox.width, transformedBox.height);
|
|
}
|
|
|
|
updateMatrix();
|
|
|
|
if (WAS10B != enabled10bit || OLDRES != vecPixelSize)
|
|
g_pHyprOpenGL->destroyMonitorResources(self.lock());
|
|
|
|
g_pCompositor->arrangeMonitors();
|
|
|
|
damage.setSize(vecTransformedSize);
|
|
|
|
// Set scale for all surfaces on this monitor, needed for some clients
|
|
// but not on unsafe state to avoid crashes
|
|
if (!g_pCompositor->m_bUnsafeState) {
|
|
for (auto const& w : g_pCompositor->m_vWindows) {
|
|
w->updateSurfaceScaleTransformDetails();
|
|
}
|
|
}
|
|
// updato us
|
|
g_pHyprRenderer->arrangeLayersForMonitor(ID);
|
|
|
|
// reload to fix mirrors
|
|
g_pConfigManager->m_bWantsMonitorReload = true;
|
|
|
|
Debug::log(LOG, "Monitor {} data dump: res {:X}@{:.2f}Hz, scale {:.2f}, transform {}, pos {:X}, 10b {}", szName, vecPixelSize, refreshRate, scale, (int)transform, vecPosition,
|
|
(int)enabled10bit);
|
|
|
|
EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr);
|
|
|
|
events.modeChanged.emit();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMonitor::addDamage(const pixman_region32_t* rg) {
|
|
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
|
|
if (*PZOOMFACTOR != 1.f && g_pCompositor->getMonitorFromCursor() == self) {
|
|
damage.damageEntire();
|
|
g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);
|
|
} else if (damage.damage(rg))
|
|
g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);
|
|
}
|
|
|
|
void CMonitor::addDamage(const CRegion& rg) {
|
|
addDamage(const_cast<CRegion*>(&rg)->pixman());
|
|
}
|
|
|
|
void CMonitor::addDamage(const CBox& box) {
|
|
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
|
|
if (*PZOOMFACTOR != 1.f && g_pCompositor->getMonitorFromCursor() == self) {
|
|
damage.damageEntire();
|
|
g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);
|
|
}
|
|
|
|
if (damage.damage(box))
|
|
g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);
|
|
}
|
|
|
|
bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {
|
|
static auto PNOBREAK = CConfigValue<Hyprlang::INT>("cursor:no_break_fs_vrr");
|
|
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
|
|
|
|
// skip scheduling extra frames for fullsreen apps with vrr
|
|
const bool shouldSkip = activeWorkspace && activeWorkspace->m_bHasFullscreenWindow && activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN &&
|
|
(*PNOBREAK == 1 || (*PNOBREAK == 2 && activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && output->state->state().adaptiveSync;
|
|
|
|
// keep requested minimum refresh rate
|
|
if (shouldSkip && *PMINRR && lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) {
|
|
// damage whole screen because some previous cursor box damages were skipped
|
|
damage.damageEntire();
|
|
return false;
|
|
}
|
|
|
|
return shouldSkip;
|
|
}
|
|
|
|
bool CMonitor::isMirror() {
|
|
return pMirrorOf != nullptr;
|
|
}
|
|
|
|
bool CMonitor::matchesStaticSelector(const std::string& selector) const {
|
|
if (selector.starts_with("desc:")) {
|
|
// match by description
|
|
const auto DESCRIPTIONSELECTOR = selector.substr(5);
|
|
|
|
return szDescription.starts_with(DESCRIPTIONSELECTOR) || szShortDescription.starts_with(DESCRIPTIONSELECTOR);
|
|
} else {
|
|
// match by selector
|
|
return szName == selector;
|
|
}
|
|
}
|
|
|
|
WORKSPACEID CMonitor::findAvailableDefaultWS() {
|
|
for (WORKSPACEID i = 1; i < LONG_MAX; ++i) {
|
|
if (g_pCompositor->getWorkspaceByID(i))
|
|
continue;
|
|
|
|
if (const auto BOUND = g_pConfigManager->getBoundMonitorStringForWS(std::to_string(i)); !BOUND.empty() && BOUND != szName)
|
|
continue;
|
|
|
|
return i;
|
|
}
|
|
|
|
return LONG_MAX; // shouldn't be reachable
|
|
}
|
|
|
|
void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) {
|
|
// Workspace
|
|
std::string newDefaultWorkspaceName = "";
|
|
int64_t wsID = WORKSPACE_INVALID;
|
|
if (g_pConfigManager->getDefaultWorkspaceFor(szName).empty())
|
|
wsID = findAvailableDefaultWS();
|
|
else {
|
|
const auto ws = getWorkspaceIDNameFromString(g_pConfigManager->getDefaultWorkspaceFor(szName));
|
|
wsID = ws.id;
|
|
newDefaultWorkspaceName = ws.name;
|
|
}
|
|
|
|
if (wsID == WORKSPACE_INVALID || (wsID >= SPECIAL_WORKSPACE_START && wsID <= -2)) {
|
|
wsID = g_pCompositor->m_vWorkspaces.size() + 1;
|
|
newDefaultWorkspaceName = std::to_string(wsID);
|
|
|
|
Debug::log(LOG, "Invalid workspace= directive name in monitor parsing, workspace name \"{}\" is invalid.", g_pConfigManager->getDefaultWorkspaceFor(szName));
|
|
}
|
|
|
|
auto PNEWWORKSPACE = g_pCompositor->getWorkspaceByID(wsID);
|
|
|
|
Debug::log(LOG, "New monitor: WORKSPACEID {}, exists: {}", wsID, (int)(PNEWWORKSPACE != nullptr));
|
|
|
|
if (PNEWWORKSPACE) {
|
|
// workspace exists, move it to the newly connected monitor
|
|
g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, self.lock());
|
|
activeWorkspace = PNEWWORKSPACE;
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(ID);
|
|
PNEWWORKSPACE->startAnim(true, true, true);
|
|
} else {
|
|
if (newDefaultWorkspaceName == "")
|
|
newDefaultWorkspaceName = std::to_string(wsID);
|
|
|
|
PNEWWORKSPACE = g_pCompositor->m_vWorkspaces.emplace_back(CWorkspace::create(wsID, self.lock(), newDefaultWorkspaceName));
|
|
}
|
|
|
|
activeWorkspace = PNEWWORKSPACE;
|
|
|
|
PNEWWORKSPACE->setActive(true);
|
|
PNEWWORKSPACE->m_bVisible = true;
|
|
PNEWWORKSPACE->m_szLastMonitor = "";
|
|
}
|
|
|
|
void CMonitor::setMirror(const std::string& mirrorOf) {
|
|
const auto PMIRRORMON = g_pCompositor->getMonitorFromString(mirrorOf);
|
|
|
|
if (PMIRRORMON == pMirrorOf)
|
|
return;
|
|
|
|
if (PMIRRORMON && PMIRRORMON->isMirror()) {
|
|
Debug::log(ERR, "Cannot mirror a mirror!");
|
|
return;
|
|
}
|
|
|
|
if (PMIRRORMON == self) {
|
|
Debug::log(ERR, "Cannot mirror self!");
|
|
return;
|
|
}
|
|
|
|
if (!PMIRRORMON) {
|
|
// disable mirroring
|
|
|
|
if (pMirrorOf) {
|
|
pMirrorOf->mirrors.erase(std::find_if(pMirrorOf->mirrors.begin(), pMirrorOf->mirrors.end(), [&](const auto& other) { return other == self; }));
|
|
|
|
// unlock software for mirrored monitor
|
|
g_pPointerManager->unlockSoftwareForMonitor(pMirrorOf.lock());
|
|
}
|
|
|
|
pMirrorOf.reset();
|
|
|
|
// set rule
|
|
const auto RULE = g_pConfigManager->getMonitorRuleFor(self.lock());
|
|
|
|
vecPosition = RULE.offset;
|
|
|
|
// push to mvmonitors
|
|
|
|
PHLMONITOR* thisWrapper = nullptr;
|
|
|
|
// find the wrap
|
|
for (auto& m : g_pCompositor->m_vRealMonitors) {
|
|
if (m->ID == ID) {
|
|
thisWrapper = &m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RASSERT(thisWrapper->get(), "CMonitor::setMirror: Had no wrapper???");
|
|
|
|
if (std::find_if(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& other) { return other.get() == this; }) ==
|
|
g_pCompositor->m_vMonitors.end()) {
|
|
g_pCompositor->m_vMonitors.push_back(*thisWrapper);
|
|
}
|
|
|
|
setupDefaultWS(RULE);
|
|
|
|
applyMonitorRule((SMonitorRule*)&RULE, true); // will apply the offset and stuff
|
|
} else {
|
|
PHLMONITOR BACKUPMON = nullptr;
|
|
for (auto const& m : g_pCompositor->m_vMonitors) {
|
|
if (m.get() != this) {
|
|
BACKUPMON = m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// move all the WS
|
|
std::vector<PHLWORKSPACE> wspToMove;
|
|
for (auto const& w : g_pCompositor->m_vWorkspaces) {
|
|
if (w->m_pMonitor == self || !w->m_pMonitor)
|
|
wspToMove.push_back(w);
|
|
}
|
|
|
|
for (auto const& w : wspToMove) {
|
|
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
|
|
w->startAnim(true, true, true);
|
|
}
|
|
|
|
activeWorkspace.reset();
|
|
|
|
vecPosition = PMIRRORMON->vecPosition;
|
|
|
|
pMirrorOf = PMIRRORMON;
|
|
|
|
pMirrorOf->mirrors.push_back(self);
|
|
|
|
// remove from mvmonitors
|
|
std::erase_if(g_pCompositor->m_vMonitors, [&](const auto& other) { return other == self; });
|
|
|
|
g_pCompositor->arrangeMonitors();
|
|
|
|
g_pCompositor->setActiveMonitor(g_pCompositor->m_vMonitors.front());
|
|
|
|
g_pCompositor->sanityCheckWorkspaces();
|
|
|
|
// Software lock mirrored monitor
|
|
g_pPointerManager->lockSoftwareForMonitor(PMIRRORMON);
|
|
}
|
|
|
|
events.modeChanged.emit();
|
|
}
|
|
|
|
float CMonitor::getDefaultScale() {
|
|
if (!m_bEnabled)
|
|
return 1;
|
|
|
|
static constexpr double MMPERINCH = 25.4;
|
|
|
|
const auto DIAGONALPX = sqrt(pow(vecPixelSize.x, 2) + pow(vecPixelSize.y, 2));
|
|
const auto DIAGONALIN = sqrt(pow(output->physicalSize.x / MMPERINCH, 2) + pow(output->physicalSize.y / MMPERINCH, 2));
|
|
|
|
const auto PPI = DIAGONALPX / DIAGONALIN;
|
|
|
|
if (PPI > 200 /* High PPI, 2x*/)
|
|
return 2;
|
|
else if (PPI > 140 /* Medium PPI, 1.5x*/)
|
|
return 1.5;
|
|
return 1;
|
|
}
|
|
|
|
void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bool noMouseMove, bool noFocus) {
|
|
if (!pWorkspace)
|
|
return;
|
|
|
|
if (pWorkspace->m_bIsSpecialWorkspace) {
|
|
if (activeSpecialWorkspace != pWorkspace) {
|
|
Debug::log(LOG, "changeworkspace on special, togglespecialworkspace to id {}", pWorkspace->m_iID);
|
|
setSpecialWorkspace(pWorkspace);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (pWorkspace == activeWorkspace)
|
|
return;
|
|
|
|
const auto POLDWORKSPACE = activeWorkspace;
|
|
if (POLDWORKSPACE)
|
|
POLDWORKSPACE->m_bVisible = false;
|
|
pWorkspace->m_bVisible = true;
|
|
|
|
activeWorkspace = pWorkspace;
|
|
|
|
if (!internal) {
|
|
const auto ANIMTOLEFT = POLDWORKSPACE && pWorkspace->m_iID > POLDWORKSPACE->m_iID;
|
|
if (POLDWORKSPACE)
|
|
POLDWORKSPACE->startAnim(false, ANIMTOLEFT);
|
|
pWorkspace->startAnim(true, ANIMTOLEFT);
|
|
|
|
// move pinned windows
|
|
for (auto const& w : g_pCompositor->m_vWindows) {
|
|
if (w->m_pWorkspace == POLDWORKSPACE && w->m_bPinned)
|
|
w->moveToWorkspace(pWorkspace);
|
|
}
|
|
|
|
if (!noFocus && !g_pCompositor->m_pLastMonitor->activeSpecialWorkspace &&
|
|
!(g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_bPinned && g_pCompositor->m_pLastWindow->m_pMonitor == self)) {
|
|
static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse");
|
|
auto pWindow = pWorkspace->m_bHasFullscreenWindow ? pWorkspace->getFullscreenWindow() : pWorkspace->getLastFocusedWindow();
|
|
|
|
if (!pWindow) {
|
|
if (*PFOLLOWMOUSE == 1)
|
|
pWindow = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING);
|
|
|
|
if (!pWindow)
|
|
pWindow = pWorkspace->getTopLeftWindow();
|
|
|
|
if (!pWindow)
|
|
pWindow = pWorkspace->getFirstWindow();
|
|
}
|
|
|
|
g_pCompositor->focusWindow(pWindow);
|
|
}
|
|
|
|
if (!noMouseMove)
|
|
g_pInputManager->simulateMouseMovement();
|
|
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(ID);
|
|
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_szName});
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_iID, pWorkspace->m_szName)});
|
|
EMIT_HOOK_EVENT("workspace", pWorkspace);
|
|
}
|
|
|
|
g_pHyprRenderer->damageMonitor(self.lock());
|
|
|
|
g_pCompositor->updateFullscreenFadeOnWorkspace(pWorkspace);
|
|
|
|
g_pConfigManager->ensureVRR(self.lock());
|
|
|
|
g_pCompositor->updateSuspendedStates();
|
|
|
|
if (activeSpecialWorkspace)
|
|
g_pCompositor->updateFullscreenFadeOnWorkspace(activeSpecialWorkspace);
|
|
}
|
|
|
|
void CMonitor::changeWorkspace(const WORKSPACEID& id, bool internal, bool noMouseMove, bool noFocus) {
|
|
changeWorkspace(g_pCompositor->getWorkspaceByID(id), internal, noMouseMove, noFocus);
|
|
}
|
|
|
|
void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
|
if (activeSpecialWorkspace == pWorkspace)
|
|
return;
|
|
|
|
g_pHyprRenderer->damageMonitor(self.lock());
|
|
|
|
if (!pWorkspace) {
|
|
// remove special if exists
|
|
if (activeSpecialWorkspace) {
|
|
activeSpecialWorkspace->m_bVisible = false;
|
|
activeSpecialWorkspace->startAnim(false, false);
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + szName});
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + szName});
|
|
}
|
|
activeSpecialWorkspace.reset();
|
|
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(ID);
|
|
|
|
if (!(g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_bPinned && g_pCompositor->m_pLastWindow->m_pMonitor == self)) {
|
|
if (const auto PLAST = activeWorkspace->getLastFocusedWindow(); PLAST)
|
|
g_pCompositor->focusWindow(PLAST);
|
|
else
|
|
g_pInputManager->refocus();
|
|
}
|
|
|
|
g_pCompositor->updateFullscreenFadeOnWorkspace(activeWorkspace);
|
|
|
|
g_pConfigManager->ensureVRR(self.lock());
|
|
|
|
g_pCompositor->updateSuspendedStates();
|
|
|
|
return;
|
|
}
|
|
|
|
if (activeSpecialWorkspace) {
|
|
activeSpecialWorkspace->m_bVisible = false;
|
|
activeSpecialWorkspace->startAnim(false, false);
|
|
}
|
|
|
|
bool animate = true;
|
|
//close if open elsewhere
|
|
const auto PMONITORWORKSPACEOWNER = pWorkspace->m_pMonitor.lock();
|
|
if (const auto PMWSOWNER = pWorkspace->m_pMonitor.lock(); PMWSOWNER && PMWSOWNER->activeSpecialWorkspace == pWorkspace) {
|
|
PMWSOWNER->activeSpecialWorkspace.reset();
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->ID);
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->szName});
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->szName});
|
|
|
|
const auto PACTIVEWORKSPACE = PMWSOWNER->activeWorkspace;
|
|
g_pCompositor->updateFullscreenFadeOnWorkspace(PACTIVEWORKSPACE);
|
|
|
|
animate = false;
|
|
}
|
|
|
|
// open special
|
|
pWorkspace->m_pMonitor = self;
|
|
activeSpecialWorkspace = pWorkspace;
|
|
activeSpecialWorkspace->m_bVisible = true;
|
|
if (animate)
|
|
pWorkspace->startAnim(true, true);
|
|
|
|
for (auto const& w : g_pCompositor->m_vWindows) {
|
|
if (w->m_pWorkspace == pWorkspace) {
|
|
w->m_pMonitor = self;
|
|
w->updateSurfaceScaleTransformDetails();
|
|
w->setAnimationsToMove();
|
|
|
|
const auto MIDDLE = w->middle();
|
|
if (w->m_bIsFloating && !VECINRECT(MIDDLE, vecPosition.x, vecPosition.y, vecPosition.x + vecSize.x, vecPosition.y + vecSize.y) && !w->isX11OverrideRedirect()) {
|
|
// if it's floating and the middle isnt on the current mon, move it to the center
|
|
const auto PMONFROMMIDDLE = g_pCompositor->getMonitorFromVector(MIDDLE);
|
|
Vector2D pos = w->m_vRealPosition->goal();
|
|
if (!VECINRECT(MIDDLE, PMONFROMMIDDLE->vecPosition.x, PMONFROMMIDDLE->vecPosition.y, PMONFROMMIDDLE->vecPosition.x + PMONFROMMIDDLE->vecSize.x,
|
|
PMONFROMMIDDLE->vecPosition.y + PMONFROMMIDDLE->vecSize.y)) {
|
|
// not on any monitor, center
|
|
pos = middle() / 2.f - w->m_vRealSize->goal() / 2.f;
|
|
} else
|
|
pos = pos - PMONFROMMIDDLE->vecPosition + vecPosition;
|
|
|
|
*w->m_vRealPosition = pos;
|
|
w->m_vPosition = pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(ID);
|
|
|
|
if (!(g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_bPinned && g_pCompositor->m_pLastWindow->m_pMonitor == self)) {
|
|
if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)
|
|
g_pCompositor->focusWindow(PLAST);
|
|
else
|
|
g_pInputManager->refocus();
|
|
}
|
|
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", pWorkspace->m_szName + "," + szName});
|
|
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", pWorkspace->m_iID + "," + pWorkspace->m_szName + "," + szName});
|
|
|
|
g_pHyprRenderer->damageMonitor(self.lock());
|
|
|
|
g_pCompositor->updateFullscreenFadeOnWorkspace(pWorkspace);
|
|
|
|
g_pConfigManager->ensureVRR(self.lock());
|
|
|
|
g_pCompositor->updateSuspendedStates();
|
|
}
|
|
|
|
void CMonitor::setSpecialWorkspace(const WORKSPACEID& id) {
|
|
setSpecialWorkspace(g_pCompositor->getWorkspaceByID(id));
|
|
}
|
|
|
|
void CMonitor::moveTo(const Vector2D& pos) {
|
|
vecPosition = pos;
|
|
}
|
|
|
|
SWorkspaceIDName CMonitor::getPrevWorkspaceIDName(const WORKSPACEID id) {
|
|
while (!prevWorkSpaces.empty()) {
|
|
const int PREVID = prevWorkSpaces.top();
|
|
prevWorkSpaces.pop();
|
|
if (PREVID == id) // skip same workspace
|
|
continue;
|
|
|
|
// recheck if previous workspace's was moved to another monitor
|
|
const auto ws = g_pCompositor->getWorkspaceByID(PREVID);
|
|
if (ws && ws->monitorID() == ID)
|
|
return {.id = PREVID, .name = ws->m_szName};
|
|
}
|
|
|
|
return {.id = WORKSPACE_INVALID};
|
|
}
|
|
|
|
void CMonitor::addPrevWorkspaceID(const WORKSPACEID id) {
|
|
if (!prevWorkSpaces.empty() && prevWorkSpaces.top() == id)
|
|
return;
|
|
|
|
prevWorkSpaces.emplace(id);
|
|
}
|
|
|
|
Vector2D CMonitor::middle() {
|
|
return vecPosition + vecSize / 2.f;
|
|
}
|
|
|
|
void CMonitor::updateMatrix() {
|
|
projMatrix = Mat3x3::identity();
|
|
if (transform != WL_OUTPUT_TRANSFORM_NORMAL)
|
|
projMatrix.translate(vecPixelSize / 2.0).transform(wlTransformToHyprutils(transform)).translate(-vecTransformedSize / 2.0);
|
|
}
|
|
|
|
WORKSPACEID CMonitor::activeWorkspaceID() {
|
|
return activeWorkspace ? activeWorkspace->m_iID : 0;
|
|
}
|
|
|
|
WORKSPACEID CMonitor::activeSpecialWorkspaceID() {
|
|
return activeSpecialWorkspace ? activeSpecialWorkspace->m_iID : 0;
|
|
}
|
|
|
|
CBox CMonitor::logicalBox() {
|
|
return {vecPosition, vecSize};
|
|
}
|
|
|
|
void CMonitor::scheduleDone() {
|
|
if (doneScheduled)
|
|
return;
|
|
|
|
doneScheduled = true;
|
|
|
|
g_pEventLoopManager->doLater([M = self] {
|
|
if (!M) // if M is gone, we got destroyed, doesn't matter.
|
|
return;
|
|
|
|
if (!PROTO::outputs.contains(M->szName))
|
|
return;
|
|
|
|
PROTO::outputs.at(M->szName)->sendDone();
|
|
M->doneScheduled = false;
|
|
});
|
|
}
|
|
|
|
void CMonitor::setCTM(const Mat3x3& ctm_) {
|
|
ctm = ctm_;
|
|
ctmUpdated = true;
|
|
g_pCompositor->scheduleFrameForMonitor(self.lock(), Aquamarine::IOutput::scheduleFrameReason::AQ_SCHEDULE_NEEDS_FRAME);
|
|
}
|
|
|
|
bool CMonitor::attemptDirectScanout() {
|
|
if (!mirrors.empty() || isMirror() || g_pHyprRenderer->m_bDirectScanoutBlocked)
|
|
return false; // do not DS if this monitor is being mirrored. Will break the functionality.
|
|
|
|
if (g_pPointerManager->softwareLockedFor(self.lock()))
|
|
return false;
|
|
|
|
const auto PCANDIDATE = solitaryClient.lock();
|
|
|
|
if (!PCANDIDATE)
|
|
return false;
|
|
|
|
const auto PSURFACE = g_pXWaylandManager->getWindowSurface(PCANDIDATE);
|
|
|
|
if (!PSURFACE || !PSURFACE->current.texture || !PSURFACE->current.buffer || PSURFACE->current.buffer->buffer.expired())
|
|
return false;
|
|
|
|
if (PSURFACE->current.bufferSize != vecPixelSize || PSURFACE->current.transform != transform)
|
|
return false;
|
|
|
|
// we can't scanout shm buffers.
|
|
const auto params = PSURFACE->current.buffer->buffer->dmabuf();
|
|
if (!params.success || !PSURFACE->current.texture->m_pEglImage /* dmabuf */)
|
|
return false;
|
|
|
|
Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {}", (uintptr_t)PSURFACE.get(), (uintptr_t)PSURFACE->current.buffer->buffer.get());
|
|
|
|
auto PBUFFER = PSURFACE->current.buffer->buffer.lock();
|
|
if (PBUFFER == output->state->state().buffer)
|
|
return true;
|
|
|
|
// FIXME: make sure the buffer actually follows the available scanout dmabuf formats
|
|
// and comes from the appropriate device. This may implode on multi-gpu!!
|
|
|
|
// entering into scanout, so save monitor format
|
|
if (lastScanout.expired())
|
|
prevDrmFormat = drmFormat;
|
|
|
|
if (drmFormat != params.format) {
|
|
output->state->setFormat(params.format);
|
|
drmFormat = params.format;
|
|
}
|
|
|
|
output->state->setBuffer(PBUFFER);
|
|
output->state->setPresentationMode(tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
|
|
Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);
|
|
|
|
if (!state.test()) {
|
|
Debug::log(TRACE, "attemptDirectScanout: failed basic test");
|
|
return false;
|
|
}
|
|
|
|
timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
PSURFACE->presentFeedback(&now, self.lock());
|
|
|
|
output->state->addDamage(CBox{{}, vecPixelSize});
|
|
output->state->resetExplicitFences();
|
|
|
|
auto cleanup = CScopeGuard([this]() { output->state->resetExplicitFences(); });
|
|
|
|
auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
|
|
|
|
bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->current.buffer && PSURFACE->current.buffer->acquire && explicitOptions.explicitKMSEnabled;
|
|
if (DOEXPLICIT) {
|
|
// wait for surface's explicit fence if present
|
|
inFence = PSURFACE->current.buffer->acquire->exportAsFD();
|
|
if (inFence.isValid()) {
|
|
Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", inFence.get());
|
|
output->state->setExplicitInFence(inFence.get());
|
|
} else {
|
|
Debug::log(TRACE, "attemptDirectScanout: failed to acquire an sync file fd for aq IN_FENCE");
|
|
DOEXPLICIT = false;
|
|
}
|
|
}
|
|
|
|
commitSeq++;
|
|
|
|
bool ok = output->commit();
|
|
|
|
if (!ok && DOEXPLICIT) {
|
|
Debug::log(TRACE, "attemptDirectScanout: EXPLICIT SYNC FAILED: commit() returned false. Resetting fences and retrying, might result in glitches.");
|
|
output->state->resetExplicitFences();
|
|
|
|
ok = output->commit();
|
|
}
|
|
|
|
if (!ok) {
|
|
Debug::log(TRACE, "attemptDirectScanout: failed to scanout surface");
|
|
lastScanout.reset();
|
|
return false;
|
|
}
|
|
|
|
if (lastScanout.expired()) {
|
|
lastScanout = PCANDIDATE;
|
|
Debug::log(LOG, "Entered a direct scanout to {:x}: \"{}\"", (uintptr_t)PCANDIDATE.get(), PCANDIDATE->m_szTitle);
|
|
}
|
|
|
|
if (!PBUFFER->lockedByBackend || PBUFFER->hlEvents.backendRelease)
|
|
return true;
|
|
|
|
// lock buffer while DRM/KMS is using it, then release it when page flip happens since DRM/KMS should be done by then
|
|
// btw buffer's syncReleaser will take care of signaling release point, so we don't do that here
|
|
PBUFFER->lock();
|
|
PBUFFER->onBackendRelease([PBUFFER]() { PBUFFER->unlock(); });
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMonitor::debugLastPresentation(const std::string& message) {
|
|
Debug::log(TRACE, "{} (last presentation {} - {} fps)", message, lastPresentationTimer.getMillis(),
|
|
lastPresentationTimer.getMillis() > 0 ? 1000.0f / lastPresentationTimer.getMillis() : 0.0f);
|
|
}
|
|
|
|
void CMonitor::onMonitorFrame() {
|
|
if ((g_pCompositor->m_pAqBackend->hasSession() && !g_pCompositor->m_pAqBackend->session->active) || !g_pCompositor->m_bSessionActive || g_pCompositor->m_bUnsafeState) {
|
|
Debug::log(WARN, "Attempted to render frame on inactive session!");
|
|
|
|
if (g_pCompositor->m_bUnsafeState && std::ranges::any_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& m) {
|
|
return m->output != g_pCompositor->m_pUnsafeOutput->output;
|
|
})) {
|
|
// restore from unsafe state
|
|
g_pCompositor->leaveUnsafeState();
|
|
}
|
|
|
|
return; // cannot draw on session inactive (different tty)
|
|
}
|
|
|
|
if (!m_bEnabled)
|
|
return;
|
|
|
|
g_pHyprRenderer->recheckSolitaryForMonitor(self.lock());
|
|
|
|
tearingState.busy = false;
|
|
|
|
if (tearingState.activelyTearing && solitaryClient.lock() /* can be invalidated by a recheck */) {
|
|
|
|
if (!tearingState.frameScheduledWhileBusy)
|
|
return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?
|
|
|
|
tearingState.nextRenderTorn = true;
|
|
tearingState.frameScheduledWhileBusy = false;
|
|
}
|
|
|
|
static auto PENABLERAT = CConfigValue<Hyprlang::INT>("misc:render_ahead_of_time");
|
|
static auto PRATSAFE = CConfigValue<Hyprlang::INT>("misc:render_ahead_safezone");
|
|
|
|
lastPresentationTimer.reset();
|
|
|
|
if (*PENABLERAT && !tearingState.nextRenderTorn) {
|
|
if (!RATScheduled) {
|
|
// render
|
|
g_pHyprRenderer->renderMonitor(self.lock());
|
|
}
|
|
|
|
RATScheduled = false;
|
|
|
|
const auto& [avg, max, min] = g_pHyprRenderer->getRenderTimes(self.lock());
|
|
|
|
if (max + *PRATSAFE > 1000.0 / refreshRate)
|
|
return;
|
|
|
|
const auto MSLEFT = 1000.0 / refreshRate - lastPresentationTimer.getMillis();
|
|
|
|
RATScheduled = true;
|
|
|
|
const auto ESTRENDERTIME = std::ceil(avg + *PRATSAFE);
|
|
const auto TIMETOSLEEP = std::floor(MSLEFT - ESTRENDERTIME);
|
|
|
|
if (MSLEFT < 1 || MSLEFT < ESTRENDERTIME || TIMETOSLEEP < 1)
|
|
g_pHyprRenderer->renderMonitor(self.lock());
|
|
else
|
|
wl_event_source_timer_update(renderTimer, TIMETOSLEEP);
|
|
} else
|
|
g_pHyprRenderer->renderMonitor(self.lock());
|
|
}
|
|
|
|
void CMonitor::onCursorMovedOnMonitor() {
|
|
if (!tearingState.activelyTearing || !solitaryClient || !g_pHyprRenderer->shouldRenderCursor())
|
|
return;
|
|
|
|
// submit a frame immediately. This will only update the cursor pos.
|
|
// output->state->setBuffer(output->state->state().buffer);
|
|
// output->state->addDamage(CRegion{});
|
|
// output->state->setPresentationMode(Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE);
|
|
// if (!output->commit())
|
|
// Debug::log(ERR, "onCursorMovedOnMonitor: tearing and wanted to update cursor, failed.");
|
|
|
|
// FIXME: try to do the above. We currently can't just render because drm is a fucking bitch
|
|
// and throws a "nO pRoP cAn Be ChAnGeD dUrInG AsYnC fLiP" on crtc_x
|
|
// this will throw too but fix it if we use sw cursors
|
|
|
|
tearingState.frameScheduledWhileBusy = true;
|
|
}
|
|
|
|
CMonitorState::CMonitorState(CMonitor* owner) : m_pOwner(owner) {
|
|
;
|
|
}
|
|
|
|
void CMonitorState::ensureBufferPresent() {
|
|
const auto STATE = m_pOwner->output->state->state();
|
|
if (!STATE.enabled) {
|
|
Debug::log(TRACE, "CMonitorState::ensureBufferPresent: Ignoring, monitor is not enabled");
|
|
return;
|
|
}
|
|
|
|
if (STATE.buffer) {
|
|
if (const auto params = STATE.buffer->dmabuf(); params.success && params.format == m_pOwner->drmFormat)
|
|
return;
|
|
}
|
|
|
|
// this is required for modesetting being possible and might be missing in case of first tests in the renderer
|
|
// where we test modes and buffers
|
|
Debug::log(LOG, "CMonitorState::ensureBufferPresent: no buffer or mismatched format, attaching one from the swapchain for modeset being possible");
|
|
m_pOwner->output->state->setBuffer(m_pOwner->output->swapchain->next(nullptr));
|
|
m_pOwner->output->swapchain->rollback(); // restore the counter, don't advance the swapchain
|
|
}
|
|
|
|
bool CMonitorState::commit() {
|
|
if (!updateSwapchain())
|
|
return false;
|
|
|
|
EMIT_HOOK_EVENT("preMonitorCommit", m_pOwner->self.lock());
|
|
|
|
ensureBufferPresent();
|
|
|
|
bool ret = m_pOwner->output->commit();
|
|
return ret;
|
|
}
|
|
|
|
bool CMonitorState::test() {
|
|
if (!updateSwapchain())
|
|
return false;
|
|
|
|
ensureBufferPresent();
|
|
|
|
return m_pOwner->output->test();
|
|
}
|
|
|
|
bool CMonitorState::updateSwapchain() {
|
|
auto options = m_pOwner->output->swapchain->currentOptions();
|
|
const auto& STATE = m_pOwner->output->state->state();
|
|
const auto& MODE = STATE.mode ? STATE.mode : STATE.customMode;
|
|
if (!MODE) {
|
|
Debug::log(WARN, "updateSwapchain: No mode?");
|
|
return true;
|
|
}
|
|
options.format = m_pOwner->drmFormat;
|
|
options.scanout = true;
|
|
options.length = 2;
|
|
options.size = MODE->pixelSize;
|
|
return m_pOwner->output->swapchain->reconfigure(options);
|
|
}
|