summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--hyprtester/src/shared.hpp2
-rw-r--r--hyprtester/src/tests/main/snap.cpp1
-rw-r--r--hyprtester/src/tests/main/solitary.cpp76
-rw-r--r--src/Compositor.cpp7
-rw-r--r--src/debug/HyprCtl.cpp107
-rw-r--r--src/debug/HyprCtl.hpp3
-rw-r--r--src/desktop/Popup.cpp6
-rw-r--r--src/desktop/Window.cpp34
-rw-r--r--src/desktop/Window.hpp1
-rw-r--r--src/helpers/Monitor.cpp261
-rw-r--r--src/helpers/Monitor.hpp65
-rw-r--r--src/helpers/MonitorFrameScheduler.cpp2
-rw-r--r--src/render/Renderer.cpp114
-rw-r--r--src/render/Renderer.hpp1
15 files changed, 549 insertions, 135 deletions
diff --git a/Makefile b/Makefile
index 01d1d650..aba363a3 100644
--- a/Makefile
+++ b/Makefile
@@ -92,3 +92,7 @@ asan:
@echo "Hyprland done"
ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf
+
+test:
+ $(MAKE) debug
+ ./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so
diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp
index 8bea4c54..43944c3c 100644
--- a/hyprtester/src/shared.hpp
+++ b/hyprtester/src/shared.hpp
@@ -45,7 +45,7 @@ namespace Colors {
#define EXPECT_CONTAINS(haystack, needle) \
if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \
- NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, EXPECTED, __FILE__, __LINE__, \
+ NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
std::string{haystack}); \
ret = 1; \
TESTS_FAILED++; \
diff --git a/hyprtester/src/tests/main/snap.cpp b/hyprtester/src/tests/main/snap.cpp
index c970c6b7..9c25de11 100644
--- a/hyprtester/src/tests/main/snap.cpp
+++ b/hyprtester/src/tests/main/snap.cpp
@@ -168,6 +168,7 @@ static bool test() {
NLog::log("{}Reloading the config", Colors::YELLOW);
OK(getFromSocket("/reload"));
+ OK(getFromSocket("/dispatch workspace 1"));
return !ret;
}
diff --git a/hyprtester/src/tests/main/solitary.cpp b/hyprtester/src/tests/main/solitary.cpp
new file mode 100644
index 00000000..6f4d1373
--- /dev/null
+++ b/hyprtester/src/tests/main/solitary.cpp
@@ -0,0 +1,76 @@
+#include "tests.hpp"
+#include "../../shared.hpp"
+#include "../../hyprctlCompat.hpp"
+#include <thread>
+#include <chrono>
+#include <hyprutils/os/Process.hpp>
+#include <hyprutils/memory/WeakPtr.hpp>
+#include "../shared.hpp"
+
+static int ret = 0;
+
+using namespace Hyprutils::OS;
+using namespace Hyprutils::Memory;
+
+#define UP CUniquePointer
+#define SP CSharedPointer
+
+static bool test() {
+ NLog::log("{}Testing solitary clients", Colors::GREEN);
+
+ OK(getFromSocket("/keyword general:allow_tearing false"));
+ OK(getFromSocket("/keyword render:direct_scanout 0"));
+ OK(getFromSocket("/keyword cursor:no_hardware_cursors 1"));
+ NLog::log("{}Expecting blocked solitary/DS/tearing", Colors::YELLOW);
+ {
+ auto str = getFromSocket("/monitors");
+ EXPECT_CONTAINS(str, "solitary: 0\n");
+ EXPECT_CONTAINS(str, "solitaryBlockedBy: windowed mode,missing candidate");
+ EXPECT_CONTAINS(str, "activelyTearing: false");
+ EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,user settings,not supported by monitor,missing candidate");
+ EXPECT_CONTAINS(str, "directScanoutTo: 0\n");
+ EXPECT_CONTAINS(str, "directScanoutBlockedBy: user settings,software renders/cursors,missing candidate");
+ }
+
+ // FIXME: need a reliable client with solitary opaque surface in fullscreen. kitty doesn't work all the time
+ // NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
+ // auto kittyProcA = Tests::spawnKitty();
+
+ // if (!kittyProcA) {
+ // NLog::log("{}Error: kitty did not spawn", Colors::RED);
+ // return false;
+ // }
+
+ // OK(getFromSocket("/keyword general:allow_tearing true"));
+ // OK(getFromSocket("/keyword render:direct_scanout 1"));
+ // NLog::log("{}", getFromSocket("/clients"));
+ // OK(getFromSocket("/dispatch fullscreen"));
+ // NLog::log("{}", getFromSocket("/clients"));
+ // std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ // NLog::log("{}Expecting kitty to almost pass for solitary/DS/tearing", Colors::YELLOW);
+ // {
+ // auto str = getFromSocket("/monitors");
+ // EXPECT_NOT_CONTAINS(str, "solitary: 0\n");
+ // EXPECT_CONTAINS(str, "solitaryBlockedBy: null");
+ // EXPECT_CONTAINS(str, "activelyTearing: false");
+ // EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings");
+ // }
+
+ // OK(getFromSocket("/dispatch setprop active immediate 1"));
+ // NLog::log("{}Expecting kitty to almost pass for tearing", Colors::YELLOW);
+ // {
+ // auto str = getFromSocket("/monitors");
+ // EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor\n");
+ // }
+
+ // // kill all
+ // NLog::log("{}Killing all windows", Colors::YELLOW);
+ // Tests::killAllWindows();
+
+ NLog::log("{}Reloading the config", Colors::YELLOW);
+ OK(getFromSocket("/reload"));
+
+ return !ret;
+}
+
+REGISTER_TEST_FN(test)
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 23b2e366..a2733042 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -2400,8 +2400,11 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS
// send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't.
// ignore if DS is disabled.
- if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME))
- g_pHyprRenderer->setSurfaceScanoutMode(PWINDOW->m_wlSurface->resource(), EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr);
+ if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) {
+ auto surf = PWINDOW->getSolitaryResource();
+ if (surf)
+ g_pHyprRenderer->setSurfaceScanoutMode(surf, EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr);
+ }
g_pConfigManager->ensureVRR(PMONITOR);
}
diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp
index 45aeb4f1..5af8ef80 100644
--- a/src/debug/HyprCtl.cpp
+++ b/src/debug/HyprCtl.cpp
@@ -1,4 +1,5 @@
#include "HyprCtl.hpp"
+#include "helpers/Monitor.hpp"
#include <algorithm>
#include <format>
@@ -109,6 +110,91 @@ static std::string availableModesForOutput(PHLMONITOR pMonitor, eHyprCtlOutputFo
return result;
}
+const std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_JSON = {
+ "\"UNKNOWN\"", "\"NOTIFICATION\"", "\"LOCK\"", "\"WORKSPACE\"", "\"WINDOWED\"", "\"DND\"", "\"SPECIAL\"", "\"ALPHA\"",
+ "\"OFFSET\"", "\"CANDIDATE\"", "\"OPAQUE\"", "\"TRANSFORM\"", "\"OVERLAYS\"", "\"FLOAT\"", "\"WORKSPACES\"", "\"SURFACES\"",
+};
+
+const std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_TEXT = {
+ "unknown reason", "notification", "session lock", "invalid workspace", "windowed mode", "dnd active", "special workspace", "alpha channel",
+ "workspace offset", "missing candidate", "not opaque", "surface transformations", "other overlays", "floating windows", "other workspaces", "subsurfaces",
+};
+
+std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
+ const auto reasons = m->isSolitaryBlocked(true);
+ if (!reasons)
+ return "null";
+
+ std::string reasonStr = "";
+ const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? SOLITARY_REASONS_JSON : SOLITARY_REASONS_TEXT;
+
+ for (int i = 0; i < CMonitor::SC_CHECKS_COUNT; i++) {
+ if (reasons & (1 << i)) {
+ if (reasonStr != "")
+ reasonStr += ",";
+ reasonStr += TEXTS[i];
+ }
+ }
+
+ return format == eHyprCtlOutputFormat::FORMAT_JSON ? "[" + reasonStr + "]" : reasonStr;
+}
+
+const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_JSON = {
+ "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"",
+ "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"",
+};
+
+const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_TEXT = {
+ "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors",
+ "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed",
+};
+
+std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
+ const auto reasons = m->isDSBlocked(true);
+ if (!reasons)
+ return "null";
+
+ std::string reasonStr = "";
+ const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? DS_REASONS_JSON : DS_REASONS_TEXT;
+
+ for (int i = 0; i < CMonitor::DS_CHECKS_COUNT; i++) {
+ if (reasons & (1 << i)) {
+ if (reasonStr != "")
+ reasonStr += ",";
+ reasonStr += TEXTS[i];
+ }
+ }
+
+ return format == eHyprCtlOutputFormat::FORMAT_JSON ? "[" + reasonStr + "]" : reasonStr;
+}
+
+const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_JSON = {
+ "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"",
+};
+
+const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_TEXT = {
+ "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings",
+};
+
+std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
+ const auto reasons = m->isTearingBlocked(true);
+ if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing))
+ return "null";
+
+ std::string reasonStr = "";
+ const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? TEARING_REASONS_JSON : TEARING_REASONS_TEXT;
+
+ for (int i = 0; i < CMonitor::TC_CHECKS_COUNT; i++) {
+ if (reasons & (1 << i)) {
+ if (reasonStr != "")
+ reasonStr += ",";
+ reasonStr += TEXTS[i];
+ }
+ }
+
+ return format == eHyprCtlOutputFormat::FORMAT_JSON ? "[" + reasonStr + "]" : reasonStr;
+}
+
std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
std::string result;
if (!m->m_output || m->m_id == -1)
@@ -146,8 +232,11 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor>
"dpmsStatus": {},
"vrr": {},
"solitary": "{:x}",
+ "solitaryBlockedBy": {},
"activelyTearing": {},
+ "tearingBlockedBy": {},
"directScanoutTo": "{:x}",
+ "directScanoutBlockedBy": {},
"disabled": {},
"currentFormat": "{}",
"mirrorOf": "{}",
@@ -155,28 +244,32 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor>
}},)#",
m->m_id, escapeJSONStrings(m->m_name), escapeJSONStrings(m->m_shortDescription), escapeJSONStrings(m->m_output->make), escapeJSONStrings(m->m_output->model),
- escapeJSONStrings(m->m_output->serial), sc<int>(m->m_pixelSize.x), m->m_pixelSize.y, sc<int>(m->m_output->physicalSize.x), sc<int>(m->m_output->physicalSize.y),
- m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->activeWorkspaceID(),
+ escapeJSONStrings(m->m_output->serial), sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), sc<int>(m->m_output->physicalSize.x),
+ sc<int>(m->m_output->physicalSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->activeWorkspaceID(),
(!m->m_activeWorkspace ? "" : escapeJSONStrings(m->m_activeWorkspace->m_name)), m->activeSpecialWorkspaceID(),
escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc<int>(m->m_reservedTopLeft.x), sc<int>(m->m_reservedTopLeft.y),
sc<int>(m->m_reservedBottomRight.x), sc<int>(m->m_reservedBottomRight.y), m->m_scale, sc<int>(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "true" : "false"),
(m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"), rc<uint64_t>(m->m_solitaryClient.get()),
- (m->m_tearingState.activelyTearing ? "true" : "false"), rc<uint64_t>(m->m_lastScanout.get()), (m->m_enabled ? "false" : "true"),
- formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format));
+ getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? "true" : "false"), getTearingBlockedReason(m, format), rc<uint64_t>(m->m_lastScanout.get()),
+ getDSBlockedReason(m, format), (m->m_enabled ? "false" : "true"), formatToString(m->m_output->state->state().drmFormat),
+ m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format));
} else {
result += std::format(
"Monitor {} (ID {}):\n\t{}x{}@{:.5f} at {}x{}\n\tdescription: {}\n\tmake: {}\n\tmodel: {}\n\tphysical size (mm): {}x{}\n\tserial: {}\n\tactive workspace: {} ({})\n\t"
"special workspace: {} ({})\n\treserved: {} {} {} {}\n\tscale: {:.2f}\n\ttransform: {}\n\tfocused: {}\n\t"
- "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tactivelyTearing: {}\n\tdirectScanoutTo: {:x}\n\tdisabled: {}\n\tcurrentFormat: {}\n\tmirrorOf: "
+ "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tsolitaryBlockedBy: {}\n\tactivelyTearing: {}\n\ttearingBlockedBy: {}\n\tdirectScanoutTo: "
+ "{:x}\n\tdirectScanoutBlockedBy: {}\n\tdisabled: "
+ "{}\n\tcurrentFormat: {}\n\tmirrorOf: "
"{}\n\tavailableModes: {}\n\n",
m->m_name, m->m_id, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->m_shortDescription,
m->m_output->make, m->m_output->model, sc<int>(m->m_output->physicalSize.x), sc<int>(m->m_output->physicalSize.y), m->m_output->serial, m->activeWorkspaceID(),
(!m->m_activeWorkspace ? "" : m->m_activeWorkspace->m_name), m->activeSpecialWorkspaceID(), (m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""),
sc<int>(m->m_reservedTopLeft.x), sc<int>(m->m_reservedTopLeft.y), sc<int>(m->m_reservedBottomRight.x), sc<int>(m->m_reservedBottomRight.y), m->m_scale,
sc<int>(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "yes" : "no"), sc<int>(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync,
- rc<uint64_t>(m->m_solitaryClient.get()), m->m_tearingState.activelyTearing, rc<uint64_t>(m->m_lastScanout.get()), !m->m_enabled,
- formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format));
+ rc<uint64_t>(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format),
+ rc<uint64_t>(m->m_lastScanout.get()), getDSBlockedReason(m, format), !m->m_enabled, formatToString(m->m_output->state->state().drmFormat),
+ m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format));
}
return result;
diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp
index b4f3d690..95bb65b8 100644
--- a/src/debug/HyprCtl.hpp
+++ b/src/debug/HyprCtl.hpp
@@ -33,6 +33,9 @@ class CHyprCtl {
static std::string getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format);
static std::string getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format);
+ static std::string getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
+ static std::string getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
+ static std::string getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
static std::string getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
private:
diff --git a/src/desktop/Popup.cpp b/src/desktop/Popup.cpp
index 7dc8ad53..e7a60af2 100644
--- a/src/desktop/Popup.cpp
+++ b/src/desktop/Popup.cpp
@@ -385,6 +385,9 @@ void CPopup::bfHelper(std::vector<WP<CPopup>> const& nodes, std::function<void(W
nodes2.reserve(nodes.size() * 2);
for (auto const& n : nodes) {
+ if (!n)
+ continue;
+
for (auto const& c : n->m_children) {
nodes2.push_back(c->m_self);
}
@@ -395,6 +398,9 @@ void CPopup::bfHelper(std::vector<WP<CPopup>> const& nodes, std::function<void(W
}
void CPopup::breadthfirst(std::function<void(WP<CPopup>, void*)> fn, void* data) {
+ if (!m_self)
+ return;
+
std::vector<WP<CPopup>> popups;
popups.push_back(m_self);
bfHelper(popups, fn, data);
diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp
index c7fd6b0b..f9857438 100644
--- a/src/desktop/Window.cpp
+++ b/src/desktop/Window.cpp
@@ -18,6 +18,7 @@
#include "../managers/ANRManager.hpp"
#include "../protocols/XDGShell.hpp"
#include "../protocols/core/Compositor.hpp"
+#include "../protocols/core/Subcompositor.hpp"
#include "../protocols/ContentType.hpp"
#include "../protocols/FractionalScale.hpp"
#include "../xwayland/XWayland.hpp"
@@ -1180,7 +1181,8 @@ bool CWindow::opaque() {
if (m_isX11 && m_xwaylandSurface && m_xwaylandSurface->m_surface && m_xwaylandSurface->m_surface->m_current.texture)
return m_xwaylandSurface->m_surface->m_current.texture->m_opaque;
- if (!m_wlSurface->resource() || !m_wlSurface->resource()->m_current.texture)
+ auto solitaryResource = getSolitaryResource();
+ if (!solitaryResource || !solitaryResource->m_current.texture)
return false;
// TODO: this is wrong
@@ -1188,7 +1190,7 @@ bool CWindow::opaque() {
if (EXTENTS.w >= m_xdgSurface->m_surface->m_current.bufferSize.x && EXTENTS.h >= m_xdgSurface->m_surface->m_current.bufferSize.y)
return true;
- return m_wlSurface->resource()->m_current.texture->m_opaque;
+ return solitaryResource->m_current.texture->m_opaque;
}
float CWindow::rounding() {
@@ -1324,7 +1326,7 @@ void CWindow::onFocusAnimUpdate() {
}
int CWindow::popupsCount() {
- if (m_isX11)
+ if (m_isX11 || !m_popupHead)
return 0;
int no = -1;
@@ -1870,3 +1872,29 @@ PHLWINDOW CWindow::parent() {
bool CWindow::priorityFocus() {
return !m_isX11 && CAsyncDialogBox::isPriorityDialogBox(getPID());
}
+
+SP<CWLSurfaceResource> CWindow::getSolitaryResource() {
+ if (!m_wlSurface || !m_wlSurface->resource())
+ return nullptr;
+
+ auto res = m_wlSurface->resource();
+ if (m_isX11)
+ return res;
+
+ if (popupsCount())
+ return nullptr;
+
+ if (res->m_subsurfaces.size() == 0)
+ return res;
+
+ if (res->m_subsurfaces.size() == 1) {
+ if (res->m_subsurfaces[0].expired() || res->m_subsurfaces[0]->m_surface.expired())
+ return nullptr;
+ auto surf = res->m_subsurfaces[0]->m_surface.lock();
+ if (!surf || surf->m_subsurfaces.size() != 0 || surf->extends() != res->extends() || !surf->m_current.texture || !surf->m_current.texture->m_opaque)
+ return nullptr;
+ return surf;
+ }
+
+ return nullptr;
+} \ No newline at end of file
diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp
index 7014ac2f..e58d4fb6 100644
--- a/src/desktop/Window.hpp
+++ b/src/desktop/Window.hpp
@@ -413,6 +413,7 @@ class CWindow {
std::optional<std::string> xdgDescription();
PHLWINDOW parent();
bool priorityFocus();
+ SP<CWLSurfaceResource> getSolitaryResource();
CBox getWindowMainSurfaceBox() const {
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};
diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp
index c24ad103..3837a1b3 100644
--- a/src/helpers/Monitor.cpp
+++ b/src/helpers/Monitor.cpp
@@ -18,6 +18,7 @@
#include "../managers/PointerManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../protocols/core/Compositor.hpp"
+#include "../protocols/core/DataDevice.hpp"
#include "../render/Renderer.hpp"
#include "../managers/EventManager.hpp"
#include "../managers/LayoutManager.hpp"
@@ -1491,33 +1492,260 @@ void CMonitor::setCTM(const Mat3x3& ctm_) {
g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::scheduleFrameReason::AQ_SCHEDULE_NEEDS_FRAME);
}
-bool CMonitor::attemptDirectScanout() {
- if (!m_mirrors.empty() || isMirror() || g_pHyprRenderer->m_directScanoutBlocked)
- return false; // do not DS if this monitor is being mirrored. Will break the functionality.
+uint16_t CMonitor::isSolitaryBlocked(bool full) {
+ uint16_t reasons = 0;
- if (g_pPointerManager->softwareLockedFor(m_self.lock()))
- return false;
+ if (g_pHyprNotificationOverlay->hasAny()) {
+ reasons |= SC_NOTIFICATION;
+ if (!full)
+ return reasons;
+ }
- const auto PCANDIDATE = m_solitaryClient.lock();
+ if (g_pSessionLockManager->isSessionLocked()) {
+ reasons |= SC_LOCK;
+ if (!full)
+ return reasons;
+ }
- if (!PCANDIDATE)
- return false;
+ const auto PWORKSPACE = m_activeWorkspace;
+ if (!PWORKSPACE) {
+ reasons |= SC_WORKSPACE;
+ return reasons;
+ }
- const auto PSURFACE = g_pXWaylandManager->getWindowSurface(PCANDIDATE);
+ if (!PWORKSPACE->m_hasFullscreenWindow) {
+ reasons |= SC_WINDOWED;
+ if (!full)
+ return reasons;
+ }
- if (!PSURFACE || !PSURFACE->m_current.texture || !PSURFACE->m_current.buffer)
- return false;
+ if (PROTO::data->dndActive()) {
+ reasons |= SC_DND;
+ if (!full)
+ return reasons;
+ }
- if (PSURFACE->m_current.bufferSize != m_pixelSize || PSURFACE->m_current.transform != m_transform)
- return false;
+ if (m_activeSpecialWorkspace) {
+ reasons |= SC_SPECIAL;
+ if (!full)
+ return reasons;
+ }
+
+ if (PWORKSPACE->m_alpha->value() != 1.f) {
+ reasons |= SC_ALPHA;
+ if (!full)
+ return reasons;
+ }
+
+ if (PWORKSPACE->m_renderOffset->value() != Vector2D{}) {
+ reasons |= SC_OFFSET;
+ if (!full)
+ return reasons;
+ }
+
+ const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow();
+
+ if (!PCANDIDATE) {
+ reasons |= SC_CANDIDATE;
+ return reasons;
+ }
+
+ if (!PCANDIDATE->opaque()) {
+ reasons |= SC_OPAQUE;
+ if (!full)
+ return reasons;
+ }
+
+ if (PCANDIDATE->m_realSize->value() != m_size || PCANDIDATE->m_realPosition->value() != m_position || PCANDIDATE->m_realPosition->isBeingAnimated() ||
+ PCANDIDATE->m_realSize->isBeingAnimated()) {
+ reasons |= SC_TRANSFORM;
+ if (!full)
+ return reasons;
+ }
+
+ if (!m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) {
+ reasons |= SC_OVERLAYS;
+ if (!full)
+ return reasons;
+ }
+
+ for (auto const& topls : m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {
+ if (topls->m_alpha->value() != 0.f) {
+ reasons |= SC_OVERLAYS;
+ if (!full)
+ return reasons;
+ }
+ }
+
+ for (auto const& w : g_pCompositor->m_windows) {
+ if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || w->isHidden())
+ continue;
+
+ if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->m_createdOverFullscreen && w->visibleOnMonitor(m_self.lock())) {
+ reasons |= SC_FLOAT;
+ if (!full)
+ return reasons;
+ }
+ }
+
+ for (auto const& ws : g_pCompositor->getWorkspaces()) {
+ if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace || ws->m_monitor != m_self)
+ continue;
+
+ reasons |= SC_WORKSPACES;
+ if (!full)
+ return reasons;
+ }
+
+ // check if it did not open any subsurfaces or shit
+ if (!PCANDIDATE->getSolitaryResource())
+ reasons |= SC_SURFACES;
+
+ return reasons;
+}
+
+void CMonitor::recheckSolitary() {
+ m_solitaryClient.reset(); // reset it, if we find one it will be set.
+ if (isSolitaryBlocked())
+ return;
+
+ m_solitaryClient = m_activeWorkspace->getFullscreenWindow();
+}
+
+uint8_t CMonitor::isTearingBlocked(bool full) {
+ uint8_t reasons = 0;
+
+ static auto PTEARINGENABLED = CConfigValue<Hyprlang::INT>("general:allow_tearing");
+
+ if (!m_tearingState.nextRenderTorn) {
+ reasons |= TC_NOT_TORN;
+ if (!full)
+ return reasons;
+ }
+
+ if (!*PTEARINGENABLED) {
+ Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring");
+ reasons |= TC_USER;
+ if (!full)
+ return reasons;
+ }
+
+ if (g_pHyprOpenGL->m_renderData.mouseZoomFactor != 1.0) {
+ Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring");
+ reasons |= TC_ZOOM;
+ if (!full)
+ return reasons;
+ }
+
+ if (!m_tearingState.canTear) {
+ Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring");
+ reasons |= TC_SUPPORT;
+ if (!full)
+ return reasons;
+ }
+
+ if (m_solitaryClient.expired()) {
+ reasons |= TC_CANDIDATE;
+ return reasons;
+ }
+
+ if (!m_solitaryClient->canBeTorn())
+ reasons |= TC_WINDOW;
+
+ return reasons;
+}
+
+bool CMonitor::updateTearing() {
+ m_tearingState.activelyTearing = !isTearingBlocked();
+ m_tearingState.nextRenderTorn = false;
+ return m_tearingState.activelyTearing;
+}
+
+uint16_t CMonitor::isDSBlocked(bool full) {
+ uint16_t reasons = 0;
+ static auto PDIRECTSCANOUT = CConfigValue<Hyprlang::INT>("render:direct_scanout");
+
+ if (*PDIRECTSCANOUT == 0) {
+ reasons |= DS_BLOCK_USER;
+ if (!full)
+ return reasons;
+ }
+
+ if (*PDIRECTSCANOUT == 2) {
+ if (!m_activeWorkspace || !m_activeWorkspace->m_hasFullscreenWindow || m_activeWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN) {
+ reasons |= DS_BLOCK_WINDOWED;
+ if (!full)
+ return reasons;
+ } else if (m_activeWorkspace->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) {
+ reasons |= DS_BLOCK_CONTENT;
+ if (!full)
+ return reasons;
+ }
+ }
+
+ if (m_tearingState.activelyTearing) {
+ reasons |= DS_BLOCK_TEARING;
+ if (!full)
+ return reasons;
+ }
+
+ if (!m_mirrors.empty() || isMirror()) {
+ reasons |= DS_BLOCK_MIRROR;
+ if (!full)
+ return reasons;
+ }
+
+ if (g_pHyprRenderer->m_directScanoutBlocked) {
+ reasons |= DS_BLOCK_RECORD;
+ if (!full)
+ return reasons;
+ }
+
+ if (g_pPointerManager->softwareLockedFor(m_self.lock())) {
+ reasons |= DS_BLOCK_SW;
+ if (!full)
+ return reasons;
+ }
+
+ const auto PCANDIDATE = m_solitaryClient.lock();
+ if (!PCANDIDATE) {
+ reasons |= DS_BLOCK_CANDIDATE;
+ return reasons;
+ }
+
+ const auto PSURFACE = PCANDIDATE->getSolitaryResource();
+ if (!PSURFACE || !PSURFACE->m_current.texture || !PSURFACE->m_current.buffer) {
+ reasons |= DS_BLOCK_SURFACE;
+ return reasons;
+ }
+
+ if (PSURFACE->m_current.bufferSize != m_pixelSize || PSURFACE->m_current.transform != m_transform) {
+ reasons |= DS_BLOCK_TRANSFORM;
+ if (!full)
+ return reasons;
+ }
// we can't scanout shm buffers.
const auto params = PSURFACE->m_current.buffer->dmabuf();
if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */)
+ reasons |= DS_BLOCK_DMA;
+
+ return reasons;
+}
+
+bool CMonitor::attemptDirectScanout() {
+ const auto blockedReason = isDSBlocked();
+ if (blockedReason) {
+ Debug::log(TRACE, "attemptDirectScanout: blocked by {}", blockedReason);
return false;
+ }
+
+ const auto PCANDIDATE = m_solitaryClient.lock();
+ const auto PSURFACE = PCANDIDATE->getSolitaryResource();
+ const auto params = PSURFACE->m_current.buffer->dmabuf();
- Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {}", rc<uintptr_t>(PSURFACE.get()),
- rc<uintptr_t>(PSURFACE->m_current.buffer.m_buffer.get()));
+ Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {} fmt: {} -> {} (mod {})", rc<uintptr_t>(PSURFACE.get()),
+ rc<uintptr_t>(PSURFACE->m_current.buffer.m_buffer.get()), m_drmFormat, params.format, params.modifier);
auto PBUFFER = PSURFACE->m_current.buffer.m_buffer;
@@ -1526,7 +1754,7 @@ bool CMonitor::attemptDirectScanout() {
if (m_scanoutNeedsCursorUpdate) {
if (!m_state.test()) {
- Debug::log(TRACE, "attemptDirectScanout: failed basic test");
+ Debug::log(TRACE, "attemptDirectScanout: failed basic test on cursor update");
return false;
}
@@ -1555,6 +1783,7 @@ bool CMonitor::attemptDirectScanout() {
}
m_output->state->setBuffer(PBUFFER);
+ Debug::log(TRACE, "attemptDirectScanout: setting presentation mode");
m_output->state->setPresentationMode(m_tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);
diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp
index 7614264f..75bd8750 100644
--- a/src/helpers/Monitor.hpp
+++ b/src/helpers/Monitor.hpp
@@ -212,6 +212,66 @@ class CMonitor {
std::array<std::vector<PHLLSREF>, 4> m_layerSurfaceLayers;
+ // keep in sync with HyprCtl
+ enum eDSBlockReason : uint16_t {
+ DS_OK = 0,
+
+ DS_BLOCK_UNKNOWN = (1 << 0),
+ DS_BLOCK_USER = (1 << 1),
+ DS_BLOCK_WINDOWED = (1 << 2),
+ DS_BLOCK_CONTENT = (1 << 3),
+ DS_BLOCK_MIRROR = (1 << 4),
+ DS_BLOCK_RECORD = (1 << 5),
+ DS_BLOCK_SW = (1 << 6),
+ DS_BLOCK_CANDIDATE = (1 << 7),
+ DS_BLOCK_SURFACE = (1 << 8),
+ DS_BLOCK_TRANSFORM = (1 << 9),
+ DS_BLOCK_DMA = (1 << 10),
+ DS_BLOCK_TEARING = (1 << 11),
+ DS_BLOCK_FAILED = (1 << 12),
+
+ DS_CHECKS_COUNT = 13,
+ };
+
+ // keep in sync with HyprCtl
+ enum eSolitaryCheck : uint16_t {
+ SC_OK = 0,
+
+ SC_UNKNOWN = (1 << 0),
+ SC_NOTIFICATION = (1 << 1),
+ SC_LOCK = (1 << 2),
+ SC_WORKSPACE = (1 << 3),
+ SC_WINDOWED = (1 << 4),
+ SC_DND = (1 << 5),
+ SC_SPECIAL = (1 << 6),
+ SC_ALPHA = (1 << 7),
+ SC_OFFSET = (1 << 8),
+ SC_CANDIDATE = (1 << 9),
+ SC_OPAQUE = (1 << 10),
+ SC_TRANSFORM = (1 << 11),
+ SC_OVERLAYS = (1 << 12),
+ SC_FLOAT = (1 << 13),
+ SC_WORKSPACES = (1 << 14),
+ SC_SURFACES = (1 << 15),
+
+ SC_CHECKS_COUNT = 16,
+ };
+
+ // keep in sync with HyprCtl
+ enum eTearingCheck : uint8_t {
+ TC_OK = 0,
+
+ TC_UNKNOWN = (1 << 0),
+ TC_NOT_TORN = (1 << 1),
+ TC_USER = (1 << 2),
+ TC_ZOOM = (1 << 3),
+ TC_SUPPORT = (1 << 4),
+ TC_CANDIDATE = (1 << 5),
+ TC_WINDOW = (1 << 6),
+
+ TC_CHECKS_COUNT = 7,
+ };
+
// methods
void onConnect(bool noRule);
void onDisconnect(bool destroy = false);
@@ -236,6 +296,11 @@ class CMonitor {
WORKSPACEID activeSpecialWorkspaceID();
CBox logicalBox();
void scheduleDone();
+ uint16_t isSolitaryBlocked(bool full = false);
+ void recheckSolitary();
+ uint8_t isTearingBlocked(bool full = false);
+ bool updateTearing();
+ uint16_t isDSBlocked(bool full = false);
bool attemptDirectScanout();
void setCTM(const Mat3x3& ctm);
void onCursorMovedOnMonitor();
diff --git a/src/helpers/MonitorFrameScheduler.cpp b/src/helpers/MonitorFrameScheduler.cpp
index 877e486f..1e8a81e5 100644
--- a/src/helpers/MonitorFrameScheduler.cpp
+++ b/src/helpers/MonitorFrameScheduler.cpp
@@ -80,7 +80,7 @@ void CMonitorFrameScheduler::onFrame() {
if (!canRender())
return;
- g_pHyprRenderer->recheckSolitaryForMonitor(m_monitor.lock());
+ m_monitor->recheckSolitary();
m_monitor->m_tearingState.busy = false;
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index 1d152122..14bf5cac 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -1188,9 +1188,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>("debug:overlay");
static auto PDAMAGETRACKINGMODE = CConfigValue<Hyprlang::INT>("debug:damage_tracking");
static auto PDAMAGEBLINK = CConfigValue<Hyprlang::INT>("debug:damage_blink");
- static auto PDIRECTSCANOUT = CConfigValue<Hyprlang::INT>("render:direct_scanout");
static auto PVFR = CConfigValue<Hyprlang::INT>("misc:vfr");
- static auto PTEARINGENABLED = CConfigValue<Hyprlang::INT>("general:allow_tearing");
static int damageBlinkCleanup = 0; // because double-buffered
@@ -1230,47 +1228,19 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
return;
// tearing and DS first
- bool shouldTear = false;
- if (pMonitor->m_tearingState.nextRenderTorn) {
- pMonitor->m_tearingState.nextRenderTorn = false;
+ bool shouldTear = pMonitor->updateTearing();
- if (!*PTEARINGENABLED) {
- Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring");
- return;
- }
-
- if (g_pHyprOpenGL->m_renderData.mouseZoomFactor != 1.0) {
- Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring");
- return;
- }
-
- if (!pMonitor->m_tearingState.canTear) {
- Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring");
- return;
- }
-
- if (!pMonitor->m_solitaryClient.expired())
- shouldTear = true;
- }
-
- pMonitor->m_tearingState.activelyTearing = shouldTear;
-
- if ((*PDIRECTSCANOUT == 1 ||
- (*PDIRECTSCANOUT == 2 && pMonitor->m_activeWorkspace && pMonitor->m_activeWorkspace->m_hasFullscreenWindow &&
- pMonitor->m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN && pMonitor->m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) &&
- !shouldTear) {
- if (pMonitor->attemptDirectScanout()) {
- return;
- } else if (!pMonitor->m_lastScanout.expired()) {
- Debug::log(LOG, "Left a direct scanout.");
- pMonitor->m_lastScanout.reset();
+ if (pMonitor->attemptDirectScanout()) {
+ return;
+ } else if (!pMonitor->m_lastScanout.expired()) {
+ Debug::log(LOG, "Left a direct scanout.");
+ pMonitor->m_lastScanout.reset();
- // reset DRM format, but only if needed since it might modeset
- if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat)
- pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat);
+ // reset DRM format, but only if needed since it might modeset
+ if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat)
+ pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat);
- pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat;
- }
+ pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat;
}
EMIT_HOOK_EVENT("preRender", pMonitor);
@@ -2143,70 +2113,6 @@ void CHyprRenderer::initiateManualCrash() {
**PDT = 0;
}
-void CHyprRenderer::recheckSolitaryForMonitor(PHLMONITOR pMonitor) {
- pMonitor->m_solitaryClient.reset(); // reset it, if we find one it will be set.
-
- if (g_pHyprNotificationOverlay->hasAny() || g_pSessionLockManager->isSessionLocked())
- return;
-
- const auto PWORKSPACE = pMonitor->m_activeWorkspace;
-
- if (!PWORKSPACE || !PWORKSPACE->m_hasFullscreenWindow || PROTO::data->dndActive() || pMonitor->m_activeSpecialWorkspace || PWORKSPACE->m_alpha->value() != 1.f ||
- PWORKSPACE->m_renderOffset->value() != Vector2D{})
- return;
-
- const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow();
-
- if (!PCANDIDATE)
- return; // ????
-
- if (!PCANDIDATE->opaque())
- return;
-
- if (PCANDIDATE->m_realSize->value() != pMonitor->m_size || PCANDIDATE->m_realPosition->value() != pMonitor->m_position || PCANDIDATE->m_realPosition->isBeingAnimated() ||
- PCANDIDATE->m_realSize->isBeingAnimated())
- return;
-
- if (!pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty())
- return;
-
- for (auto const& topls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {
- if (topls->m_alpha->value() != 0.f)
- return;
- }
-
- for (auto const& w : g_pCompositor->m_windows) {
- if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || w->isHidden())
- continue;
-
- if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->m_createdOverFullscreen && w->visibleOnMonitor(pMonitor))
- return;
- }
-
- if (pMonitor->m_activeSpecialWorkspace)
- return;
-
- for (auto const& ws : g_pCompositor->getWorkspaces()) {
- if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace || ws->m_monitor != pMonitor)
- continue;
-
- return;
- }
-
- // check if it did not open any subsurfaces or shit
- int surfaceCount = 0;
- if (PCANDIDATE->m_isX11)
- surfaceCount = 1;
- else
- surfaceCount = PCANDIDATE->popupsCount() + PCANDIDATE->surfacesCount();
-
- if (surfaceCount > 1)
- return;
-
- // found one!
- pMonitor->m_solitaryClient = PCANDIDATE;
-}
-
SP<CRenderbuffer> CHyprRenderer::getOrCreateRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {
auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; });
diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp
index b898e37c..1e184174 100644
--- a/src/render/Renderer.hpp
+++ b/src/render/Renderer.hpp
@@ -69,7 +69,6 @@ class CHyprRenderer {
bool fixMisalignedFSV1 = false);
std::tuple<float, float, float> getRenderTimes(PHLMONITOR pMonitor); // avg max min
void renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry);
- void recheckSolitaryForMonitor(PHLMONITOR pMonitor);
void setCursorSurface(SP<CWLSurface> surf, int hotspotX, int hotspotY, bool force = false);
void setCursorFromName(const std::string& name, bool force = false);
void onRenderbufferDestroy(CRenderbuffer* rb);