From 1b11b2e3f51bbaea5fa10f3103006669a7b29145 Mon Sep 17 00:00:00 2001
From: Fabio Bas <ctrlaltca@gmail.com>
Date: Fri, 18 Jul 2025 16:20:07 +0200
Subject: [PATCH] Use KModifierKeyInfo to detect *lock modifiers' status

RDP: sync {caps,num,lock}lock keystate to remote session on start and at every focusIn

BUG: 349813
---
 CMakeLists.txt                                |  5 ++-
 core/CMakeLists.txt                           |  1 +
 core/remoteview.cpp                           | 34 +++++++++++++++++++
 core/remoteview.h                             | 16 +++++++++
 ...krdc.desktop => org.kde.krdc.desktop.cmake |  3 +-
 rdp/rdpsession.cpp                            | 24 +++++++++++++
 rdp/rdpsession.h                              |  2 ++
 rdp/rdpview.cpp                               |  9 +++++
 rdp/rdpview.h                                 |  1 +
 9 files changed, 93 insertions(+), 2 deletions(-)
 rename org.kde.krdc.desktop => org.kde.krdc.desktop.cmake (98%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e446da0a..d556e980 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,6 +51,7 @@ find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
     I18n
     KIO
     Crash
+    GuiAddons
 )
 
 find_package(KF6StatusNotifierItem ${KF_MIN_VERSION} REQUIRED)
@@ -230,7 +231,9 @@ if (HAVE_KACTIVITIES)
 endif()
 
 install(TARGETS krdc ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
-install(PROGRAMS org.kde.krdc.desktop DESTINATION ${KDE_INSTALL_APPDIR})
+configure_file(org.kde.krdc.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.krdc.desktop @ONLY)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.krdc.desktop DESTINATION ${KDE_INSTALL_APPDIR})
 install(FILES org.kde.krdc.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
 
 ecm_install_icons(
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index bb8e3b6c..85260d57 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -31,6 +31,7 @@ target_link_libraries(krdccore
     KF6::I18n
     KF6::ConfigGui
     KF6::Completion
+    KF6::GuiAddons
     Qt::Gui
     Qt::Widgets)
 
diff --git a/core/remoteview.cpp b/core/remoteview.cpp
index ef76ef3c..d912ae29 100644
--- a/core/remoteview.cpp
+++ b/core/remoteview.cpp
@@ -9,6 +9,9 @@
 #include "remoteview.h"
 #include "krdc_debug.h"
 
+#ifndef QTONLY
+#include <KModifierKeyInfo>
+#endif
 #include <QApplication>
 #include <QBitmap>
 #include <QEvent>
@@ -58,6 +61,10 @@ RemoteView::RemoteView(QWidget *parent)
     m_clipboard = QApplication::clipboard();
     connect(m_clipboard, &QClipboard::dataChanged, this, &RemoteView::localClipboardChanged);
 
+#ifndef QTONLY
+    m_modifierKeyInfo = new KModifierKeyInfo(this);
+#endif
+
 #ifdef HAVE_WAYLAND
     if (qGuiApp->platformName() == QLatin1String("wayland")) {
         m_inhibition.reset(new WaylandInhibition(window()->windowHandle()));
@@ -568,4 +575,31 @@ void RemoteView::sshErrorMessage(const QString &message)
     QWidget::releaseKeyboard();
 }
 
+bool RemoteView::isCapsLockEnabled()
+{
+#ifdef QTONLY
+    return false;
+#else
+    return m_modifierKeyInfo->isKeyLatched(Qt::Key_CapsLock) || m_modifierKeyInfo->isKeyLocked(Qt::Key_CapsLock);
+#endif
+}
+
+bool RemoteView::isNumLockEnabled()
+{
+#ifdef QTONLY
+    return false;
+#else
+    return m_modifierKeyInfo->isKeyLatched(Qt::Key_NumLock) || m_modifierKeyInfo->isKeyLocked(Qt::Key_NumLock);
+#endif
+}
+
+bool RemoteView::isScrollLockEnabled()
+{
+#ifdef QTONLY
+    return false;
+#else
+    return m_modifierKeyInfo->isKeyLatched(Qt::Key_ScrollLock) || m_modifierKeyInfo->isKeyLocked(Qt::Key_ScrollLock);
+#endif
+}
+
 #include "moc_remoteview.cpp"
diff --git a/core/remoteview.h b/core/remoteview.h
index c5bf3cbe..bf1483e4 100644
--- a/core/remoteview.h
+++ b/core/remoteview.h
@@ -36,6 +36,7 @@ struct ModifierKey {
 };
 
 class HostPreferences;
+class KModifierKeyInfo;
 
 /**
  * Generic widget that displays a remote framebuffer.
@@ -263,6 +264,19 @@ public:
      */
     virtual QPixmap takeScreenshot();
 
+    /**
+     * @return status of the caps lock key
+     */
+    bool isCapsLockEnabled();
+    /**
+     * @return status of the num lock key
+     */
+    bool isNumLockEnabled();
+    /**
+     * @return status of the scroll lock key
+     */
+    bool isScrollLockEnabled();
+
 #ifndef QTONLY
     /**
      * Returns the current host preferences of this view.
@@ -456,6 +470,8 @@ protected:
     qreal m_factor;
     QClipboard *m_clipboard;
     QMap<int, ModifierKey> m_modifiers;
+    KModifierKeyInfo *m_modifierKeyInfo;
+
 #ifdef HAVE_WAYLAND
     std::unique_ptr<ShortcutInhibition> m_inhibition;
 #endif
diff --git a/org.kde.krdc.desktop b/org.kde.krdc.desktop.cmake
similarity index 98%
rename from org.kde.krdc.desktop
rename to org.kde.krdc.desktop.cmake
index 29ab2e5c..3beb404c 100755
--- a/org.kde.krdc.desktop
+++ b/org.kde.krdc.desktop.cmake
@@ -1,7 +1,7 @@
 # KDE Config File
 [Desktop Entry]
 Type=Application
-Exec=krdc -qwindowtitle %c %u
+Exec=@CMAKE_INSTALL_PREFIX@/bin/krdc -qwindowtitle %c %u
 Icon=krdc
 Terminal=false
 Name=KRDC
@@ -180,3 +180,4 @@ X-DocPath=krdc/index.html
 StartupWMClass=krdc
 MimeType=x-scheme-handler/vnc;x-scheme-handler/rdp;application/x-krdc;
 Categories=Qt;KDE;Network;RemoteAccess;
+X-KDE-Wayland-Interfaces=org_kde_kwin_keystate
\ No newline at end of file
diff --git a/rdp/rdpsession.cpp b/rdp/rdpsession.cpp
index 25747609..3449092a 100644
--- a/rdp/rdpsession.cpp
+++ b/rdp/rdpsession.cpp
@@ -987,6 +987,10 @@ bool RdpSession::sendEvent(QEvent *event, QWidget *source)
     switch (event->type()) {
     case QEvent::KeyPress:
     case QEvent::KeyRelease: {
+        if (m_needKeyStateSync) {
+            m_needKeyStateSync = false;
+            syncKeyState();
+        }
         auto keyEvent = static_cast<QKeyEvent *>(event);
         const DWORD vc = GetVirtualKeyCodeFromKeycode(keyEvent->nativeScanCode(), WINPR_KEYCODE_TYPE_XKB);
         const DWORD code = GetVirtualScanCodeFromVirtualKeyCode(vc, WINPR_KBD_TYPE_IBM_ENHANCED);
@@ -1090,6 +1094,26 @@ bool RdpSession::sendEvent(QEvent *event, QWidget *source)
     return QObject::event(event);
 }
 
+bool RdpSession::syncKeyState()
+{
+    auto input = m_context.rdp->input;
+    if (!input) {
+        return false;
+    }
+
+    UINT16 flags = 0;
+
+    if (m_view->isCapsLockEnabled())
+        flags |= KBD_SYNC_CAPS_LOCK;
+    if (m_view->isNumLockEnabled())
+        flags |= KBD_SYNC_NUM_LOCK;
+    if (m_view->isScrollLockEnabled())
+        flags |= KBD_SYNC_SCROLL_LOCK;
+
+    qCDebug(KRDC) << "Sync key state: " << flags;
+    return freerdp_input_send_synchronize_event(input, flags);
+}
+
 void RdpSession::setState(RdpSession::State newState)
 {
     if (newState == m_state) {
diff --git a/rdp/rdpsession.h b/rdp/rdpsession.h
index 1caf25ae..61e42b60 100644
--- a/rdp/rdpsession.h
+++ b/rdp/rdpsession.h
@@ -103,6 +103,7 @@ public:
     void stop();
 
     bool sendEvent(QEvent *event, QWidget *source);
+    bool syncKeyState();
 
     void initializeClipboard(RdpContext *krdp, CliprdrClientContext *cliprdr);
     void initializeDisplay(RdpContext *krdp, DispClientContext *disp);
@@ -201,6 +202,7 @@ private:
     int m_port = -1;
     QSize m_size;
     bool m_firstPasswordTry;
+    bool m_needKeyStateSync = true;
 
     std::thread m_thread;
 
diff --git a/rdp/rdpview.cpp b/rdp/rdpview.cpp
index 1a32bd72..d4583584 100644
--- a/rdp/rdpview.cpp
+++ b/rdp/rdpview.cpp
@@ -512,4 +512,13 @@ void RdpView::handleLocalClipboardChanged(const QMimeData *data)
     if (m_session) {
         m_session->sendClipboard(data);
     }
-}
\ No newline at end of file
+}
+
+void RdpView::focusInEvent(QFocusEvent *event)
+{
+    if (m_session) {
+        m_session->syncKeyState();
+    }
+
+    RemoteView::focusInEvent(event);
+}
diff --git a/rdp/rdpview.h b/rdp/rdpview.h
index 996beecb..c5063125 100644
--- a/rdp/rdpview.h
+++ b/rdp/rdpview.h
@@ -75,6 +75,7 @@ protected:
     void handleWheelEvent(QWheelEvent *event) override;
     void handleMouseEvent(QMouseEvent *event) override;
     void handleLocalClipboardChanged(const QMimeData *data) override;
+    void focusInEvent(QFocusEvent *event) override;
 
 private:
     void onRectangleUpdated(const QRect &rect);
-- 
GitLab

