diff --git a/DeskControler/DeskControler.cpp b/DeskControler/DeskControler.cpp index 3a84ada..5ba4e81 100644 --- a/DeskControler/DeskControler.cpp +++ b/DeskControler/DeskControler.cpp @@ -4,7 +4,6 @@ #include #include #include "VideoWidget.h" -#include "RemoteClipboard.h" #include "LogWidget.h" DeskControler::DeskControler(QWidget* parent) @@ -201,10 +200,16 @@ void DeskControler::setupVideoSession(const QString& relayServer, quint16 relayP m_videoReceiver = new VideoReceiver(this); + m_remoteClipboard = new RemoteClipboard(this); + m_remoteClipboard->setRemoteWindow(scrollArea); - RemoteClipboard* remoteClipboard = new RemoteClipboard(this); - - connect(remoteClipboard, &RemoteClipboard::clipboardDataReady, + if (m_remoteClipboard->start()) { + LogWidget::instance()->addLog("Global keyboard hook installed", LogWidget::Info); + } + else { + LogWidget::instance()->addLog("Failed to install global keyboard hook", LogWidget::Error); + } + connect(m_remoteClipboard, &RemoteClipboard::ctrlCPressed, m_videoReceiver, &VideoReceiver::clipboardDataCaptured); connect(videoWidget, &VideoWidget::mouseEventCaptured, @@ -231,6 +236,12 @@ void DeskControler::setupVideoSession(const QString& relayServer, quint16 relayP void DeskControler::destroyVideoSession() { + if (m_remoteClipboard) { + m_remoteClipboard->stop(); + delete m_remoteClipboard; + m_remoteClipboard = nullptr; + } + ui.pushButton->setEnabled(true); ui.ipLineEdit_->setEnabled(true); ui.portLineEdit_->setEnabled(true); diff --git a/DeskControler/DeskControler.h b/DeskControler/DeskControler.h index 488a459..4ad5916 100644 --- a/DeskControler/DeskControler.h +++ b/DeskControler/DeskControler.h @@ -4,6 +4,7 @@ #include "NetworkManager.h" #include "VideoReceiver.h" #include "ui_DeskControler.h" +#include "RemoteClipboard.h" class NetworkManager; @@ -31,4 +32,5 @@ private slots: Ui::DeskControlerClass ui; NetworkManager* m_networkManager; VideoReceiver* m_videoReceiver; + RemoteClipboard* m_remoteClipboard = nullptr; }; diff --git a/DeskControler/NetworkWorker.cpp b/DeskControler/NetworkWorker.cpp index e383782..1456cdf 100644 --- a/DeskControler/NetworkWorker.cpp +++ b/DeskControler/NetworkWorker.cpp @@ -255,6 +255,7 @@ void NetworkWorker::sendClipboardEventToServer(const ClipboardEvent& clipboardEv sendData.append(reinterpret_cast(&len_be), sizeof(len_be)); sendData.append(protobufData); + LogWidget::instance()->addLog("sendClipboardEventToServer", LogWidget::Error); if (m_socket && m_socket->state() == QAbstractSocket::ConnectedState) { m_socket->write(sendData); m_socket->flush(); diff --git a/DeskControler/RemoteClipboard.cpp b/DeskControler/RemoteClipboard.cpp index c2da3d5..123fe1a 100644 --- a/DeskControler/RemoteClipboard.cpp +++ b/DeskControler/RemoteClipboard.cpp @@ -1,67 +1,119 @@ #include "RemoteClipboard.h" +#include "LogWidget.h" #include #include #include #include #include -#include "LogWidget.h" +#include +#include + +HHOOK RemoteClipboard::s_hook = nullptr; +RemoteClipboard* RemoteClipboard::s_instance = nullptr; RemoteClipboard::RemoteClipboard(QObject* parent) : QObject(parent) { - qApp->installEventFilter(this); + s_instance = this; } +RemoteClipboard::~RemoteClipboard() +{ + stop(); + s_instance = nullptr; +} -bool RemoteClipboard::eventFilter(QObject* /*obj*/, QEvent* event) +bool RemoteClipboard::start() { - if (event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); - if ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_C) { - QClipboard* clipboard = QApplication::clipboard(); - const QMimeData* mimeData = clipboard->mimeData(); - if (mimeData) { - LogWidget::instance()->addLog("Control side: Detected Ctrl+C, sending clipboard data", LogWidget::Info); - sendClipboardData(mimeData); - } - else { - LogWidget::instance()->addLog("Control side: Clipboard is empty", LogWidget::Warning); - } - return true; + if (!s_hook) { + s_hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(nullptr), 0); + if (!s_hook) { + LogWidget::instance()->addLog("RemoteClipboard: Failed to install global keyboard hook", LogWidget::Error); + return false; } + LogWidget::instance()->addLog("RemoteClipboard: Global keyboard hook installed", LogWidget::Info); } - return QObject::eventFilter(nullptr, event); + return true; } -void RemoteClipboard::sendClipboardData(const QMimeData* mimeData) +void RemoteClipboard::stop() { - ClipboardEvent eventMsg; - // 如果剪贴板中包含文件 URL,优先处理文件数据 - if (mimeData->hasUrls() && !mimeData->urls().isEmpty()) { - QString filePath = mimeData->urls().first().toLocalFile(); - QFile file(filePath); - if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); - file.close(); - FileContent* fileContent = eventMsg.mutable_file(); - fileContent->set_file_data(data.toStdString()); - QFileInfo fileInfo(filePath); - fileContent->set_file_name(fileInfo.fileName().toStdString()); - LogWidget::instance()->addLog(QString("Control side: Copied file: %1").arg(filePath), LogWidget::Info); - } - else { - LogWidget::instance()->addLog(QString("Control side: Failed to open file: %1").arg(filePath), LogWidget::Error); - return; - } + if (s_hook) { + UnhookWindowsHookEx(s_hook); + s_hook = nullptr; + LogWidget::instance()->addLog("RemoteClipboard: Global keyboard hook removed", LogWidget::Info); } - else if (mimeData->hasText()) { - TextContent* textContent = eventMsg.mutable_text(); - textContent->set_text_data(mimeData->text().toStdString()); - LogWidget::instance()->addLog("Control side: Copied text data", LogWidget::Info); +} + +void RemoteClipboard::setRemoteWindow(QWidget* remoteWindow) +{ + m_remoteWindow = remoteWindow; +} + +LRESULT CALLBACK RemoteClipboard::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + if (s_instance) { + return s_instance->handleKeyEvent(nCode, wParam, lParam); } - else { - LogWidget::instance()->addLog("Control side: Unsupported clipboard data", LogWidget::Warning); - return; + return CallNextHookEx(s_hook, nCode, wParam, lParam); +} + +LRESULT RemoteClipboard::handleKeyEvent(int nCode, WPARAM wParam, LPARAM lParam) +{ + if (nCode == HC_ACTION) { + KBDLLHOOKSTRUCT* pKeyboard = reinterpret_cast(lParam); + // 仅处理按下事件 + if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { + bool ctrlPressed = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; + if (ctrlPressed && pKeyboard->vkCode == 'C') { + QWidget* activeWnd = QApplication::activeWindow(); + if (m_remoteWindow && activeWnd == m_remoteWindow) { + // 如果激活窗口是远程桌面,则不处理 Ctrl+C,交由远程桌面处理 + LogWidget::instance()->addLog("Remote window active, ignoring Ctrl+C on control side", LogWidget::Info); + return CallNextHookEx(s_hook, nCode, wParam, lParam); + } + // 读取剪贴板数据,并构造 ClipboardEvent + ClipboardEvent eventMsg; + QClipboard* clipboard = QApplication::clipboard(); + const QMimeData* mimeData = clipboard->mimeData(); + if (mimeData) { + // 如果剪贴板包含文件 URL,则优先处理文件数据 + if (mimeData->hasUrls() && !mimeData->urls().isEmpty()) { + QString filePath = mimeData->urls().first().toLocalFile(); + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + QByteArray data = file.readAll(); + file.close(); + FileContent* fileContent = eventMsg.mutable_file(); + fileContent->set_file_data(data.toStdString()); + QFileInfo fileInfo(filePath); + fileContent->set_file_name(fileInfo.fileName().toStdString()); + LogWidget::instance()->addLog(QString("RemoteClipboard: Captured file data: %1").arg(filePath), LogWidget::Info); + } + else { + LogWidget::instance()->addLog(QString("RemoteClipboard: Failed to open file: %1").arg(filePath), LogWidget::Error); + } + } + // 否则处理文本数据 + else if (mimeData->hasText()) { + TextContent* textContent = eventMsg.mutable_text(); + textContent->set_text_data(mimeData->text().toStdString()); + LogWidget::instance()->addLog("RemoteClipboard: Captured text data", LogWidget::Info); + } + else { + LogWidget::instance()->addLog("RemoteClipboard: Unsupported clipboard data", LogWidget::Warning); + } + } + else { + LogWidget::instance()->addLog("RemoteClipboard: Clipboard is empty", LogWidget::Warning); + } + // 使用 queued 方式发射带参数的信号,确保在 Qt 主线程中处理 + QMetaObject::invokeMethod(this, "ctrlCPressed", Qt::QueuedConnection, + Q_ARG(ClipboardEvent, eventMsg)); + LogWidget::instance()->addLog("RemoteClipboard: Global Ctrl+C detected and clipboard data captured", LogWidget::Info); + // 可选择拦截此事件,或返回 CallNextHookEx 继续传递 + } + } } - emit clipboardDataReady(eventMsg); + return CallNextHookEx(s_hook, nCode, wParam, lParam); } diff --git a/DeskControler/RemoteClipboard.h b/DeskControler/RemoteClipboard.h index f172620..5a4622f 100644 --- a/DeskControler/RemoteClipboard.h +++ b/DeskControler/RemoteClipboard.h @@ -2,24 +2,31 @@ #define REMOTECLIPBOARD_H #include -#include -#include +#include #include "rendezvous.pb.h" +Q_DECLARE_METATYPE(ClipboardEvent) + class RemoteClipboard : public QObject { Q_OBJECT public: explicit RemoteClipboard(QObject* parent = nullptr); + ~RemoteClipboard(); -protected: - bool eventFilter(QObject* obj, QEvent* event) override; + bool start(); + void stop(); -private: - void sendClipboardData(const QMimeData* mimeData); + void setRemoteWindow(QWidget* remoteWindow); +signals: + void ctrlCPressed(const ClipboardEvent& clipboardEvent); +private: + static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); + LRESULT handleKeyEvent(int nCode, WPARAM wParam, LPARAM lParam); -signals: - void clipboardDataReady(const ClipboardEvent& clipboardEvent); + static HHOOK s_hook; + static RemoteClipboard* s_instance; + QWidget* m_remoteWindow = nullptr; }; #endif // REMOTECLIPBOARD_H diff --git a/DeskServer/DeskServer.vcxproj b/DeskServer/DeskServer.vcxproj index c498496..2d18ddb 100644 --- a/DeskServer/DeskServer.vcxproj +++ b/DeskServer/DeskServer.vcxproj @@ -74,6 +74,7 @@ + @@ -104,6 +105,9 @@ + + + diff --git a/DeskServer/RelayManager.cpp b/DeskServer/RelayManager.cpp index 796e4c9..28ef739 100644 --- a/DeskServer/RelayManager.cpp +++ b/DeskServer/RelayManager.cpp @@ -14,6 +14,7 @@ RelayManager::RelayManager(QObject* parent) m_encoder(nullptr) { m_inputSimulator = new RemoteInputSimulator(nullptr); + m_remoteClipboard = new RemoteClipboard(nullptr); } RelayManager::~RelayManager() { @@ -134,6 +135,7 @@ void RelayManager::processReceivedData(const QByteArray& packetData) { LogWidget::instance()->addLog("Failed to parse RendezvousMessage message from RelayManager", LogWidget::Warning); return; } + LogWidget::instance()->addLog("processReceivedData", LogWidget::Warning); if (msg.has_inputcontrolevent()) { const InputControlEvent& event = msg.inputcontrolevent(); if (event.has_mouse_event()) { @@ -152,6 +154,15 @@ void RelayManager::processReceivedData(const QByteArray& packetData) { Q_ARG(int, key), Q_ARG(bool, pressed)); } } + else if (msg.has_clipboardevent()) { + + const ClipboardEvent& clipboardEvent = msg.clipboardevent(); + QMetaObject::invokeMethod(m_remoteClipboard, "onClipboardMessageReceived", Qt::QueuedConnection, + Q_ARG(ClipboardEvent, clipboardEvent)); + } + else { + LogWidget::instance()->addLog("Received unknown message type in RendezvousMessage", LogWidget::Warning); + } } void RelayManager::onEncodedPacketReady(const QByteArray& packet) { diff --git a/DeskServer/RelayManager.h b/DeskServer/RelayManager.h index d5bac23..2a902ca 100644 --- a/DeskServer/RelayManager.h +++ b/DeskServer/RelayManager.h @@ -7,6 +7,7 @@ #include "RemoteInputSimulator.h" #include "ScreenCaptureEncoder.h" #include "RelaySocketWorker.h" +#include "RemoteClipboard.h" class RelayManager : public QObject { Q_OBJECT @@ -44,6 +45,7 @@ private slots: ScreenCaptureEncoder* m_encoder; RemoteInputSimulator* m_inputSimulator; QThread* m_encoderThread; + RemoteClipboard* m_remoteClipboard; }; #endif // RELAYMANAGER_H diff --git a/DeskServer/RemoteClipboard.cpp b/DeskServer/RemoteClipboard.cpp new file mode 100644 index 0000000..98eae32 --- /dev/null +++ b/DeskServer/RemoteClipboard.cpp @@ -0,0 +1,53 @@ +#include "RemoteClipboard.h" +#include +#include +#include +#include +#include +#include +#include "LogWidget.h" + +RemoteClipboard::RemoteClipboard(QObject* parent) + : QObject(parent) +{ +} + +void RemoteClipboard::onClipboardMessageReceived(const ClipboardEvent& clipboardEvent) +{ + LogWidget::instance()->addLog("onClipboardMessageReceived", LogWidget::Error); + switch (clipboardEvent.event_case()) { + case ClipboardEvent::kText: { + // 处理文本数据 + QString text = QString::fromStdString(clipboardEvent.text().text_data()); + QApplication::clipboard()->setText(text); + LogWidget::instance()->addLog("RemoteClipboard: Updated clipboard with text data", LogWidget::Info); + break; + } + case ClipboardEvent::kFile: { + // 处理文件数据 + const FileContent& fileContent = clipboardEvent.file(); + QString fileName = QString::fromStdString(fileContent.file_name()); + // 保存文件到临时目录 + QString tempPath = QDir::tempPath() + "/" + fileName; + QFile file(tempPath); + if (file.open(QIODevice::WriteOnly)) { + file.write(QByteArray::fromStdString(fileContent.file_data())); + file.close(); + LogWidget::instance()->addLog(QString("RemoteClipboard: File saved to %1").arg(tempPath), LogWidget::Info); + // 更新剪贴板为文件 URL,使得粘贴操作可以获取该文件 + QMimeData* mimeData = new QMimeData; + QList urls; + urls.append(QUrl::fromLocalFile(tempPath)); + mimeData->setUrls(urls); + QApplication::clipboard()->setMimeData(mimeData); + } + else { + LogWidget::instance()->addLog("RemoteClipboard: Failed to save file", LogWidget::Error); + } + break; + } + default: + LogWidget::instance()->addLog("RemoteClipboard: Received unknown clipboard event", LogWidget::Warning); + break; + } +} diff --git a/DeskServer/RemoteClipboard.h b/DeskServer/RemoteClipboard.h new file mode 100644 index 0000000..b82b08b --- /dev/null +++ b/DeskServer/RemoteClipboard.h @@ -0,0 +1,17 @@ +#ifndef REMOTECLIPBOARD_H +#define REMOTECLIPBOARD_H + +#include +#include "rendezvous.pb.h" + +class RemoteClipboard : public QObject { + Q_OBJECT +public: + explicit RemoteClipboard(QObject* parent = nullptr); + +public slots: + // 接收远程传来的 ClipboardEvent 消息,并更新系统剪贴板数据 + void onClipboardMessageReceived(const ClipboardEvent& clipboardEvent); +}; + +#endif // REMOTECLIPBOARD_H