UwU 2 сар өмнө
parent
commit
80db93a0ea

+ 2 - 0
CMakeLists.txt

@@ -224,6 +224,8 @@ if(WAYLAND_SUPPORT_FOUND)
         list(APPEND WAYLAND_SOURCES
             src/WallpaperEngine/Render/Drivers/Detectors/KDEWaylandFullScreenDetector.cpp
             src/WallpaperEngine/Render/Drivers/Detectors/KDEWaylandFullScreenDetector.h
+            src/WallpaperEngine/Input/Drivers/KDECursorInput.cpp
+            src/WallpaperEngine/Input/Drivers/KDECursorInput.h
             ${WAYLAND_OUTPUT_DIR}/plasma-shell-protocol.c
             ${WAYLAND_OUTPUT_DIR}/plasma-shell-protocol.h)
     endif()

+ 261 - 0
src/WallpaperEngine/Input/Drivers/KDECursorInput.cpp

@@ -0,0 +1,261 @@
+#include "KDECursorInput.h"
+
+#include "WallpaperEngine/Logging/Log.h"
+
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <unistd.h>
+
+namespace WallpaperEngine::Input::Drivers {
+
+KDECursorInput::KDECursorInput () {
+    if (!initializeDBus ()) {
+	sLog.out ("KDE cursor input could not initialize DBus, mouse-reactive effects may not work on this session");
+	return;
+    }
+
+    if (!loadKWinScript ()) {
+	sLog.out ("KDE cursor input could not load its KWin script, mouse-reactive effects may not work on this session");
+	stopDBus ();
+    }
+}
+
+KDECursorInput::~KDECursorInput () {
+    unloadKWinScript ();
+    stopDBus ();
+}
+
+bool KDECursorInput::initializeDBus () {
+    DBusError error;
+    dbus_error_init (&error);
+
+    m_connection = dbus_bus_get (DBUS_BUS_SESSION, &error);
+    if (dbus_error_is_set (&error)) {
+	sLog.error ("Failed to connect to the session DBus: ", error.message);
+	dbus_error_free (&error);
+	return false;
+    }
+
+    if (m_connection == nullptr) {
+	sLog.error ("Failed to connect to the session DBus: unknown error");
+	return false;
+    }
+
+    dbus_connection_set_exit_on_disconnect (m_connection, false);
+
+    dbus_error_init (&error);
+    const std::string serviceName = std::string (kServiceName) + "." + std::to_string (getpid ());
+    const auto requestResult = dbus_bus_request_name (m_connection, serviceName.c_str (), DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
+
+    if (dbus_error_is_set (&error)) {
+	sLog.error ("Failed to request DBus service ", serviceName, ": ", error.message);
+	dbus_error_free (&error);
+	dbus_connection_unref (m_connection);
+	m_connection = nullptr;
+	return false;
+    }
+
+    if (requestResult != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER && requestResult != DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
+	sLog.error ("DBus service ", serviceName, " is already owned by another process");
+	dbus_connection_unref (m_connection);
+	m_connection = nullptr;
+	return false;
+    }
+
+    static constexpr DBusObjectPathVTable objectVTable = {
+	.unregister_function = nullptr,
+	.message_function = &KDECursorInput::handleMessage,
+    };
+
+    if (!dbus_connection_register_object_path (m_connection, kObjectPath, &objectVTable, this)) {
+	sLog.error ("Failed to register DBus object path for KDE cursor input");
+	dbus_connection_unref (m_connection);
+	m_connection = nullptr;
+	return false;
+    }
+
+    return true;
+}
+
+void KDECursorInput::stopDBus () {
+    if (m_connection != nullptr) {
+	dbus_connection_unregister_object_path (m_connection, kObjectPath);
+	dbus_connection_unref (m_connection);
+	m_connection = nullptr;
+    }
+}
+
+bool KDECursorInput::loadKWinScript () {
+    const char* runtimeDir = std::getenv ("XDG_RUNTIME_DIR");
+    if (runtimeDir == nullptr) {
+	sLog.error ("KDE cursor input: XDG_RUNTIME_DIR is not set");
+	return false;
+    }
+
+    const std::string serviceName = std::string (kServiceName) + "." + std::to_string (getpid ());
+
+    std::ostringstream script;
+    script << "function lweReportCursorPos() {\n"
+	   << "    var p = workspace.cursorPos;\n"
+	   << "    callDBus(\"" << serviceName << "\", \"" << kObjectPath << "\", \"" << serviceName << "\", \""
+	   << kMethodName << "\", p.x, p.y);\n"
+	   << "}\n"
+	   << "workspace.cursorPosChanged.connect(lweReportCursorPos);\n"
+	   << "lweReportCursorPos();\n";
+
+    m_scriptPath = std::string (runtimeDir) + "/linux-wallpaperengine-cursor-" + std::to_string (getpid ()) + ".js";
+
+    std::ofstream out (m_scriptPath, std::ios::trunc);
+    if (!out.is_open ()) {
+	sLog.error ("KDE cursor input: could not write KWin script to ", m_scriptPath);
+	return false;
+    }
+    out << script.str ();
+    out.close ();
+
+    m_pluginName = "lwe-cursor-input-" + std::to_string (getpid ());
+
+    DBusMessage* loadCall = dbus_message_new_method_call ("org.kde.KWin", "/Scripting", "org.kde.kwin.Scripting", "loadScript");
+    if (loadCall == nullptr) {
+	return false;
+    }
+
+    const char* pathArg = m_scriptPath.c_str ();
+    const char* nameArg = m_pluginName.c_str ();
+    dbus_message_append_args (
+	loadCall, DBUS_TYPE_STRING, &pathArg, DBUS_TYPE_STRING, &nameArg, DBUS_TYPE_INVALID
+    );
+
+    DBusError error;
+    dbus_error_init (&error);
+    DBusMessage* reply = dbus_connection_send_with_reply_and_block (m_connection, loadCall, 2000, &error);
+    dbus_message_unref (loadCall);
+
+    if (dbus_error_is_set (&error)) {
+	sLog.error ("KDE cursor input: failed to load KWin script: ", error.message);
+	dbus_error_free (&error);
+	std::remove (m_scriptPath.c_str ());
+	return false;
+    }
+
+    dbus_int32_t scriptId = -1;
+    if (reply != nullptr) {
+	dbus_message_get_args (reply, nullptr, DBUS_TYPE_INT32, &scriptId, DBUS_TYPE_INVALID);
+	dbus_message_unref (reply);
+    }
+
+    if (scriptId < 0) {
+	sLog.error ("KDE cursor input: KWin refused to load the cursor tracking script");
+	std::remove (m_scriptPath.c_str ());
+	return false;
+    }
+
+    const std::string scriptObjectPath = "/Scripting/Script" + std::to_string (scriptId);
+    DBusMessage* runCall = dbus_message_new_method_call ("org.kde.KWin", scriptObjectPath.c_str (), "org.kde.kwin.Script", "run");
+    if (runCall != nullptr) {
+	dbus_error_init (&error);
+	DBusMessage* runReply = dbus_connection_send_with_reply_and_block (m_connection, runCall, 2000, &error);
+	dbus_message_unref (runCall);
+	if (runReply != nullptr) {
+	    dbus_message_unref (runReply);
+	}
+	if (dbus_error_is_set (&error)) {
+	    sLog.error ("KDE cursor input: failed to start the cursor tracking script: ", error.message);
+	    dbus_error_free (&error);
+	}
+    }
+
+    m_scriptLoaded = true;
+    return true;
+}
+
+void KDECursorInput::unloadKWinScript () {
+    if (m_scriptLoaded && m_connection != nullptr) {
+	DBusMessage* unloadCall
+	    = dbus_message_new_method_call ("org.kde.KWin", "/Scripting", "org.kde.kwin.Scripting", "unloadScript");
+	if (unloadCall != nullptr) {
+	    const char* nameArg = m_pluginName.c_str ();
+	    dbus_message_append_args (unloadCall, DBUS_TYPE_STRING, &nameArg, DBUS_TYPE_INVALID);
+
+	    DBusError error;
+	    dbus_error_init (&error);
+	    DBusMessage* reply = dbus_connection_send_with_reply_and_block (m_connection, unloadCall, 2000, &error);
+	    dbus_message_unref (unloadCall);
+	    if (reply != nullptr) {
+		dbus_message_unref (reply);
+	    }
+	    if (dbus_error_is_set (&error)) {
+		dbus_error_free (&error);
+	    }
+	}
+    }
+
+    if (!m_scriptPath.empty ()) {
+	std::remove (m_scriptPath.c_str ());
+    }
+
+    m_scriptLoaded = false;
+}
+
+DBusHandlerResult KDECursorInput::handleMessage (DBusConnection* connection, DBusMessage* message, void* userData) {
+    const auto input = static_cast<KDECursorInput*> (userData);
+    return input->handleMessage (message);
+}
+
+DBusHandlerResult KDECursorInput::handleMessage (DBusMessage* message) {
+    if (message == nullptr || dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL) {
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    return handleMethodCall (message) ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+bool KDECursorInput::handleMethodCall (DBusMessage* message) {
+    const auto* member = dbus_message_get_member (message);
+    if (member == nullptr || std::strcmp (member, kMethodName) != 0) {
+	return false;
+    }
+
+    DBusError error;
+    dbus_error_init (&error);
+
+    dbus_int32_t x = 0;
+    dbus_int32_t y = 0;
+
+    if (!dbus_message_get_args (message, &error, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INVALID)) {
+	dbus_error_free (&error);
+	return false;
+    }
+
+    m_pos = glm::dvec2 { static_cast<double> (x), static_cast<double> (y) };
+
+    if (m_connection != nullptr) {
+	DBusMessage* reply = dbus_message_new_method_return (message);
+	if (reply != nullptr) {
+	    dbus_connection_send (m_connection, reply, nullptr);
+	    dbus_message_unref (reply);
+	}
+    }
+
+    return true;
+}
+
+std::optional<glm::dvec2> KDECursorInput::position () {
+    if (m_connection != nullptr) {
+	if (!dbus_connection_read_write_dispatch (m_connection, 0)) {
+	    sLog.error ("KDE cursor input: DBus connection dropped unexpectedly");
+	    m_scriptLoaded = false;
+	    dbus_connection_unregister_object_path (m_connection, kObjectPath);
+	    dbus_connection_unref (m_connection);
+	    m_connection = nullptr;
+	}
+    }
+
+    return m_pos;
+}
+
+bool KDECursorInput::isInitialized () const { return m_connection != nullptr && m_scriptLoaded; }
+
+} // namespace WallpaperEngine::Input::Drivers

+ 68 - 0
src/WallpaperEngine/Input/Drivers/KDECursorInput.h

@@ -0,0 +1,68 @@
+#pragma once
+
+#ifdef ENABLE_WAYLAND
+#ifdef ENABLE_KDE_EXPERIMENTAL_FEATURES
+
+#include <dbus/dbus.h>
+#include <glm/vec2.hpp>
+#include <optional>
+#include <string>
+
+namespace WallpaperEngine::Input::Drivers {
+
+/**
+ * @brief Live global cursor position on KDE Plasma Wayland, fed by a small KWin script.
+ *
+ * Background layer-shell surfaces never receive real pointer motion events, and there is
+ * no portable Wayland protocol to ask the compositor "where is the cursor right now". KWin
+ * itself always knows (workspace.cursorPos in its scripting API), so this class loads a
+ * short script into the running KWin instance via its Scripting D-Bus interface. The script
+ * connects to workspace.cursorPosChanged and forwards every update to a small D-Bus service
+ * hosted by this class.
+ *
+ * If D-Bus initialization or script loading fails, @c isInitialized() returns @c false and
+ * the caller is expected to fall back to another cursor source (Hyprland IPC, XWayland, ...).
+ */
+class KDECursorInput {
+public:
+    KDECursorInput ();
+    ~KDECursorInput ();
+
+    KDECursorInput (const KDECursorInput&) = delete;
+    KDECursorInput& operator= (const KDECursorInput&) = delete;
+
+    /**
+     * Pumps the D-Bus connection and returns the last known global cursor position, if any
+     * has been reported yet.
+     */
+    std::optional<glm::dvec2> position ();
+
+    [[nodiscard]] bool isInitialized () const;
+
+private:
+    bool initializeDBus ();
+    void stopDBus ();
+
+    bool loadKWinScript ();
+    void unloadKWinScript ();
+
+    static DBusHandlerResult handleMessage (DBusConnection* connection, DBusMessage* message, void* userData);
+    DBusHandlerResult handleMessage (DBusMessage* message);
+    bool handleMethodCall (DBusMessage* message);
+
+    DBusConnection* m_connection = nullptr;
+    std::optional<glm::dvec2> m_pos;
+
+    std::string m_scriptPath;
+    std::string m_pluginName;
+    bool m_scriptLoaded = false;
+
+    static constexpr const char* kServiceName = "org.linuxwallpaperengine.CursorInput";
+    static constexpr const char* kObjectPath = "/org/linuxwallpaperengine/CursorInput";
+    static constexpr const char* kMethodName = "OnCursorMoved";
+};
+
+} // namespace WallpaperEngine::Input::Drivers
+
+#endif /* ENABLE_KDE_EXPERIMENTAL_FEATURES */
+#endif /* ENABLE_WAYLAND */

+ 32 - 11
src/WallpaperEngine/Input/Drivers/WaylandMouseInput.cpp

@@ -39,6 +39,17 @@ void WaylandMouseInput::update () {
 	return;
     }
 
+#ifdef ENABLE_KDE_EXPERIMENTAL_FEATURES
+    if (const auto kdeCursor = this->m_kdeCursor.position (); kdeCursor.has_value ()) {
+	if (this->matchViewport (*kdeCursor, "kde", shouldLog)) {
+	    if (shouldLog) {
+		lastDebugLog = std::chrono::steady_clock::now ();
+	    }
+	    return;
+	}
+    }
+#endif /* ENABLE_KDE_EXPERIMENTAL_FEATURES */
+
     const auto now = std::chrono::steady_clock::now ();
     if (now - this->m_lastGlobalCursorQuery < std::chrono::milliseconds (16)) {
 	return;
@@ -62,9 +73,24 @@ void WaylandMouseInput::update () {
 	return;
     }
 
+    if (this->matchViewport (*globalCursor, source, shouldLog)) {
+	if (shouldLog) {
+	    lastDebugLog = now;
+	}
+	return;
+    }
+
+    if (shouldLog) {
+	lastDebugLog = now;
+	sLog.out ("[mouse-debug] global cursor (source=", source, ") did not match any viewport bounds");
+    }
+    this->m_pos = { 0, 0 };
+}
+
+bool WaylandMouseInput::matchViewport (const glm::dvec2& globalCursor, const char* source, bool shouldLog) {
     if (shouldLog) {
 	sLog.out (
-	    "[mouse-debug] source=", source, " global cursor = ", globalCursor->x, ",", globalCursor->y,
+	    "[mouse-debug] source=", source, " global cursor = ", globalCursor.x, ",", globalCursor.y,
 	    " screens=", this->m_waylandDriver.m_screens.size ()
 	);
 	for (const auto* viewport : this->m_waylandDriver.m_screens) {
@@ -83,25 +109,20 @@ void WaylandMouseInput::update () {
 	    continue;
 	}
 
-	const double localX = globalCursor->x - viewport->position.x;
-	const double localY = globalCursor->y - viewport->position.y;
+	const double localX = globalCursor.x - viewport->position.x;
+	const double localY = globalCursor.y - viewport->position.y;
 	if (localX < 0.0 || localY < 0.0 || localX > viewport->size.x || localY > viewport->size.y) {
 	    continue;
 	}
 
 	this->m_pos = { localX * viewport->scale, (viewport->size.y - localY) * viewport->scale };
 	if (shouldLog) {
-	    lastDebugLog = now;
-	    sLog.out ("[mouse-debug] matched viewport, m_pos = ", this->m_pos.x, ",", this->m_pos.y);
+	    sLog.out ("[mouse-debug] matched viewport (source=", source, "), m_pos = ", this->m_pos.x, ",", this->m_pos.y);
 	}
-	return;
+	return true;
     }
 
-    if (shouldLog) {
-	lastDebugLog = now;
-	sLog.out ("[mouse-debug] global cursor did not match any viewport bounds");
-    }
-    this->m_pos = { 0, 0 };
+    return false;
 }
 
 glm::dvec2 WaylandMouseInput::position () const {

+ 13 - 0
src/WallpaperEngine/Input/Drivers/WaylandMouseInput.h

@@ -8,6 +8,10 @@
 #include <glm/vec2.hpp>
 #include <optional>
 
+#ifdef ENABLE_KDE_EXPERIMENTAL_FEATURES
+#include "KDECursorInput.h"
+#endif /* ENABLE_KDE_EXPERIMENTAL_FEATURES */
+
 namespace WallpaperEngine::Render::Drivers {
 namespace Output {
     class WaylandOutputViewport;
@@ -53,6 +57,11 @@ private:
      */
     [[nodiscard]] std::optional<glm::dvec2> queryX11CursorPosition () const;
 #endif /* ENABLE_X11 */
+    /**
+     * Converts a global (compositor-space) cursor position into local viewport coordinates and
+     * stores it into m_pos if it falls within one of the tracked outputs
+     */
+    bool matchViewport (const glm::dvec2& globalCursor, const char* source, bool shouldLog);
 
     /**
      * Wayland: Driver
@@ -61,6 +70,10 @@ private:
 
     glm::dvec2 m_pos = {};
     std::chrono::steady_clock::time_point m_lastGlobalCursorQuery = {};
+
+#ifdef ENABLE_KDE_EXPERIMENTAL_FEATURES
+    KDECursorInput m_kdeCursor;
+#endif /* ENABLE_KDE_EXPERIMENTAL_FEATURES */
 };
 } // namespace WallpaperEngine::Input::Drivers