From ecc04e8ba7469fb01c5a066bf1c48d5cd58d1a18 Mon Sep 17 00:00:00 2001
From: mavonarx <75391689+mavonarx@users.noreply.github.com>
Date: Wed, 23 Jul 2025 23:11:07 +0200
Subject: [PATCH] drm: check syncobj timeline support before advertising
 protocol (#11117)

Prevents crashes on systems where DRM driver lacks syncobj timeline
support (e.g., Apple Silicon with Honeykrisp driver). Applications
like Zed and WezTerm would crash with 'Timeline failed importing'
when trying to use explicit sync.

Fixes #8158 #8803

---------

Co-authored-by: mvonarx <matthias.vonarx@sitrox.com>
---
 src/Compositor.cpp                | 15 +++++++++++++++
 src/Compositor.hpp                |  3 +++
 src/helpers/sync/SyncTimeline.cpp |  7 +++++++
 src/managers/ProtocolManager.cpp  | 10 ++++++++--
 4 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 53f790122e6..58292c8a7cf 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -73,6 +73,7 @@
 #include <sys/resource.h>
 #include <malloc.h>
 #include <unistd.h>
+#include <xf86drm.h>
 
 using namespace Hyprutils::String;
 using namespace Aquamarine;
@@ -167,6 +168,10 @@ void CCompositor::restoreNofile() {
         Debug::log(ERR, "Failed restoring NOFILE limits");
 }
 
+bool CCompositor::supportsDrmSyncobjTimeline() const {
+    return m_bDrmSyncobjTimelineSupported;
+}
+
 void CCompositor::setMallocThreshold() {
 #ifdef M_TRIM_THRESHOLD
     // The default is 128 pages,
@@ -354,6 +359,16 @@ void CCompositor::initServer(std::string socketName, int socketFd) {
     m_drmFD = m_aqBackend->drmFD();
     Debug::log(LOG, "Running on DRMFD: {}", m_drmFD);
 
+    if (m_drmFD >= 0) {
+        uint64_t cap                   = 0;
+        int      ret                   = drmGetCap(m_drmFD, DRM_CAP_SYNCOBJ_TIMELINE, &cap);
+        m_bDrmSyncobjTimelineSupported = (ret == 0 && cap != 0);
+        Debug::log(LOG, "DRM syncobj timeline support: {}", m_bDrmSyncobjTimelineSupported ? "yes" : "no");
+    } else {
+        m_bDrmSyncobjTimelineSupported = false;
+        Debug::log(LOG, "DRM syncobj timeline support: no (no DRM FD)");
+    }
+
     if (!socketName.empty() && socketFd != -1) {
         fcntl(socketFd, F_SETFD, FD_CLOEXEC);
         const auto RETVAL = wl_display_add_socket_fd(m_wlDisplay, socketFd);
diff --git a/src/Compositor.hpp b/src/Compositor.hpp
index b4a0c51cbc8..23d1c365f28 100644
--- a/src/Compositor.hpp
+++ b/src/Compositor.hpp
@@ -152,6 +152,7 @@ class CCompositor {
     NColorManagement::SImageDescription getPreferredImageDescription();
     bool                                shouldChangePreferredImageDescription();
 
+    bool                                supportsDrmSyncobjTimeline() const;
     std::string                         m_explicitConfigPath;
 
   private:
@@ -165,6 +166,8 @@ class CCompositor {
     void             removeLockFile();
     void             setMallocThreshold();
 
+    bool             m_bDrmSyncobjTimelineSupported = false;
+
     uint64_t         m_hyprlandPID    = 0;
     wl_event_source* m_critSigSource  = nullptr;
     rlimit           m_originalNofile = {};
diff --git a/src/helpers/sync/SyncTimeline.cpp b/src/helpers/sync/SyncTimeline.cpp
index 1b089caaf4b..614a7c5be50 100644
--- a/src/helpers/sync/SyncTimeline.cpp
+++ b/src/helpers/sync/SyncTimeline.cpp
@@ -1,12 +1,16 @@
 #include "SyncTimeline.hpp"
 #include "../../defines.hpp"
 #include "../../managers/eventLoop/EventLoopManager.hpp"
+#include "../../Compositor.hpp"
 
 #include <xf86drm.h>
 #include <sys/eventfd.h>
 using namespace Hyprutils::OS;
 
 SP<CSyncTimeline> CSyncTimeline::create(int drmFD_) {
+    if (!g_pCompositor->supportsDrmSyncobjTimeline())
+        return nullptr;
+
     auto timeline     = SP<CSyncTimeline>(new CSyncTimeline);
     timeline->m_drmFD = drmFD_;
     timeline->m_self  = timeline;
@@ -20,6 +24,9 @@ SP<CSyncTimeline> CSyncTimeline::create(int drmFD_) {
 }
 
 SP<CSyncTimeline> CSyncTimeline::create(int drmFD_, CFileDescriptor&& drmSyncobjFD) {
+    if (!g_pCompositor->supportsDrmSyncobjTimeline())
+        return nullptr;
+
     auto timeline         = SP<CSyncTimeline>(new CSyncTimeline);
     timeline->m_drmFD     = drmFD_;
     timeline->m_syncobjFD = std::move(drmSyncobjFD);
diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp
index eac6dee0838..ebd90a45b07 100644
--- a/src/managers/ProtocolManager.cpp
+++ b/src/managers/ProtocolManager.cpp
@@ -70,6 +70,7 @@
 #include "content-type-v1.hpp"
 
 #include <aquamarine/buffer/Buffer.hpp>
+#include <xf86drm.h>
 #include <aquamarine/backend/Backend.hpp>
 #include <hyprutils/memory/UniquePtr.hpp>
 
@@ -210,8 +211,13 @@ CProtocolManager::CProtocolManager() {
         else
             lease.reset();
 
-        if (g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext && !PROTO::sync)
-            PROTO::sync = makeUnique<CDRMSyncobjProtocol>(&wp_linux_drm_syncobj_manager_v1_interface, 1, "DRMSyncobj");
+        if (g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext && !PROTO::sync) {
+            if (g_pCompositor->supportsDrmSyncobjTimeline()) {
+                PROTO::sync = makeUnique<CDRMSyncobjProtocol>(&wp_linux_drm_syncobj_manager_v1_interface, 1, "DRMSyncobj");
+                Debug::log(LOG, "DRM Syncobj Timeline support detected, enabling explicit sync protocol");
+            } else
+                Debug::log(WARN, "DRM Syncobj Timeline not supported, skipping explicit sync protocol");
+        }
     }
 
     if (!g_pHyprOpenGL->getDRMFormats().empty()) {
