diff --git a/DeskControler/DeskControler.cpp b/DeskControler/DeskControler.cpp index 5ba4e81..bdc1ce5 100644 --- a/DeskControler/DeskControler.cpp +++ b/DeskControler/DeskControler.cpp @@ -218,6 +218,10 @@ void DeskControler::setupVideoSession(const QString& relayServer, quint16 relayP QObject::connect(videoWidget, &VideoWidget::keyEventCaptured, m_videoReceiver, &VideoReceiver::keyEventCaptured); + QObject::connect(m_videoReceiver, &VideoReceiver::onClipboardMessageReceived, + m_remoteClipboard, &RemoteClipboard::onClipboardMessageReceived); + + connect(m_videoReceiver, &VideoReceiver::frameReady, this, [videoWidget, scrollArea](const QImage& img) { static bool firstFrame = true; if (firstFrame && !img.isNull()) diff --git a/DeskControler/MessageHandler.cpp b/DeskControler/MessageHandler.cpp index 1ffc967..713faaf 100644 --- a/DeskControler/MessageHandler.cpp +++ b/DeskControler/MessageHandler.cpp @@ -46,6 +46,10 @@ void MessageHandler::processReceivedData(const QByteArray& data) const InpuVideoFrame& frame = msg.inpuvideoframe(); emit InpuVideoFrameReceived(QByteArray::fromStdString(frame.data())); } + else if (msg.has_clipboardevent()) { + const ClipboardEvent& clipboardEvent = msg.clipboardevent(); + emit onClipboardMessageReceived(clipboardEvent); + } else { emit parseError("Received unknown message type"); } diff --git a/DeskControler/MessageHandler.h b/DeskControler/MessageHandler.h index 0d00915..1218384 100644 --- a/DeskControler/MessageHandler.h +++ b/DeskControler/MessageHandler.h @@ -23,4 +23,6 @@ class MessageHandler : public QObject { // 消息解析错误 void parseError(const QString& error); + void onClipboardMessageReceived(const ClipboardEvent& clipboardEvent); + }; diff --git a/DeskControler/NetworkWorker.cpp b/DeskControler/NetworkWorker.cpp index 1456cdf..2efe071 100644 --- a/DeskControler/NetworkWorker.cpp +++ b/DeskControler/NetworkWorker.cpp @@ -11,6 +11,9 @@ NetworkWorker::NetworkWorker(QObject* parent) { connect(&messageHandler, &MessageHandler::InpuVideoFrameReceived, this, &NetworkWorker::packetReady); + + connect(&messageHandler, &MessageHandler::onClipboardMessageReceived, + this, &NetworkWorker::onClipboardMessageReceived); } NetworkWorker::~NetworkWorker() diff --git a/DeskControler/NetworkWorker.h b/DeskControler/NetworkWorker.h index fee59bb..257f444 100644 --- a/DeskControler/NetworkWorker.h +++ b/DeskControler/NetworkWorker.h @@ -28,6 +28,7 @@ public slots: // 网络出错、断开等信号,可以通知主线程 void networkError(const QString& error); void connectedToServer(); + void onClipboardMessageReceived(const ClipboardEvent& clipboardEvent); private slots: void onSocketConnected(); diff --git a/DeskControler/RemoteClipboard.cpp b/DeskControler/RemoteClipboard.cpp index 123fe1a..96cb01f 100644 --- a/DeskControler/RemoteClipboard.cpp +++ b/DeskControler/RemoteClipboard.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,47 @@ void RemoteClipboard::setRemoteWindow(QWidget* remoteWindow) m_remoteWindow = remoteWindow; } + +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; + } +} + LRESULT CALLBACK RemoteClipboard::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (s_instance) { @@ -116,4 +158,4 @@ LRESULT RemoteClipboard::handleKeyEvent(int nCode, WPARAM wParam, LPARAM lParam) } } return CallNextHookEx(s_hook, nCode, wParam, lParam); -} +} \ No newline at end of file diff --git a/DeskControler/RemoteClipboard.h b/DeskControler/RemoteClipboard.h index 5a4622f..704e3a8 100644 --- a/DeskControler/RemoteClipboard.h +++ b/DeskControler/RemoteClipboard.h @@ -1,6 +1,7 @@ #ifndef REMOTECLIPBOARD_H #define REMOTECLIPBOARD_H + #include #include #include "rendezvous.pb.h" @@ -20,6 +21,10 @@ class RemoteClipboard : public QObject { signals: void ctrlCPressed(const ClipboardEvent& clipboardEvent); +public slots: + // 接收远程传来的 ClipboardEvent 消息,并更新系统剪贴板数据 + void onClipboardMessageReceived(const ClipboardEvent& clipboardEvent); + private: static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT handleKeyEvent(int nCode, WPARAM wParam, LPARAM lParam); @@ -29,4 +34,4 @@ class RemoteClipboard : public QObject { QWidget* m_remoteWindow = nullptr; }; -#endif // REMOTECLIPBOARD_H +#endif // REMOTECLIPBOARD_H \ No newline at end of file diff --git a/DeskControler/VideoReceiver.cpp b/DeskControler/VideoReceiver.cpp index cfda9cc..7629cf5 100644 --- a/DeskControler/VideoReceiver.cpp +++ b/DeskControler/VideoReceiver.cpp @@ -28,6 +28,10 @@ VideoReceiver::VideoReceiver(QObject* parent) m_decoderWorker, &VideoDecoderWorker::decodePacket, Qt::QueuedConnection); + connect(m_netWorker, &NetworkWorker::onClipboardMessageReceived, + this, &VideoReceiver::onClipboardMessageReceived, + Qt::QueuedConnection); + // 瑙g爜瀹屾垚鍚庡洖鍒颁富绾跨▼ connect(m_decoderWorker, &VideoDecoderWorker::frameDecoded, this, &VideoReceiver::onFrameDecoded, diff --git a/DeskControler/VideoReceiver.h b/DeskControler/VideoReceiver.h index 8505361..32a7d3f 100644 --- a/DeskControler/VideoReceiver.h +++ b/DeskControler/VideoReceiver.h @@ -26,6 +26,8 @@ class VideoReceiver : public QObject void frameReady(const QImage& image); // 鍙互鎶 NetworkWorker 鐨勯敊璇浆鍙戝嚭鍘 void networkError(const QString& error); + void onClipboardMessageReceived(const ClipboardEvent& clipboardEvent); + public slots: void mouseEventCaptured(int x, int y, int mask); void keyEventCaptured(int key, bool pressed); diff --git a/DeskServer/RelayManager.cpp b/DeskServer/RelayManager.cpp index 28ef739..b760522 100644 --- a/DeskServer/RelayManager.cpp +++ b/DeskServer/RelayManager.cpp @@ -49,7 +49,9 @@ void RelayManager::start(const QHostAddress& relayAddress, quint16 relayPort, co connect(m_encoderThread, &QThread::started, m_encoder, &ScreenCaptureEncoder::startCapture); connect(m_encoder, &ScreenCaptureEncoder::encodedPacketReady, this, &RelayManager::onEncodedPacketReady); connect(m_encoderThread, &QThread::finished, m_encoder, &QObject::deleteLater); + connect(m_remoteClipboard, &RemoteClipboard::ctrlCPressed, this, &RelayManager::sendClipboardEvent); m_encoderThread->start(); + m_remoteClipboard->start(); } void RelayManager::stop() { @@ -72,6 +74,11 @@ void RelayManager::stop() { m_encoderThread->deleteLater(); m_encoderThread = nullptr; } + if (m_remoteClipboard) { + m_remoteClipboard->stop(); + m_remoteClipboard->deleteLater(); + m_remoteClipboard = nullptr; + } } void RelayManager::onWorkerSocketConnected() { @@ -190,3 +197,26 @@ void RelayManager::onEncodedPacketReady(const QByteArray& packet) { LogWidget::instance()->addLog("RelayManager: Cannot forward packet, TCP connection not established", LogWidget::Warning); } } + +void RelayManager::sendClipboardEvent(const ClipboardEvent& clipboardEvent) +{ + // 组装 ClipboardEvent 消息到 RendezvousMessage 中 + RendezvousMessage msg; + *msg.mutable_clipboardevent() = clipboardEvent; + + std::string serialized; + if (!msg.SerializeToString(&serialized)) { + LogWidget::instance()->addLog("Failed to serialize ClipboardEvent message", LogWidget::Error); + return; + } + + QByteArray data(serialized.data(), static_cast(serialized.size())); + quint32 packetSize = static_cast(data.size()); + quint32 bigEndianSize = qToBigEndian(packetSize); + QByteArray header(reinterpret_cast(&bigEndianSize), sizeof(bigEndianSize)); + QByteArray fullData; + fullData.append(header); + fullData.append(data); + QMetaObject::invokeMethod(m_socketWorker, "sendData", Qt::QueuedConnection, + Q_ARG(QByteArray, fullData)); +} diff --git a/DeskServer/RelayManager.h b/DeskServer/RelayManager.h index 2a902ca..6241c0c 100644 --- a/DeskServer/RelayManager.h +++ b/DeskServer/RelayManager.h @@ -30,6 +30,8 @@ private slots: void onWorkerDataReceived(const QByteArray& data); void onWorkerSocketError(const QString& errMsg); void onEncodedPacketReady(const QByteArray& packet); + void sendClipboardEvent(const ClipboardEvent& clipboardEvent); + private: void processReceivedData(const QByteArray& packetData); diff --git a/DeskServer/RemoteClipboard.cpp b/DeskServer/RemoteClipboard.cpp index 98eae32..603b95e 100644 --- a/DeskServer/RemoteClipboard.cpp +++ b/DeskServer/RemoteClipboard.cpp @@ -2,14 +2,113 @@ #include #include #include +#include #include #include #include +#include #include "LogWidget.h" +HHOOK RemoteClipboard::s_hook = nullptr; +RemoteClipboard* RemoteClipboard::s_instance = nullptr; + RemoteClipboard::RemoteClipboard(QObject* parent) : QObject(parent) { + s_instance = this; + +} + +RemoteClipboard::~RemoteClipboard() +{ + stop(); + s_instance = nullptr; +} + + +bool RemoteClipboard::start() +{ + 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 true; +} + +void RemoteClipboard::stop() +{ + if (s_hook) { + UnhookWindowsHookEx(s_hook); + s_hook = nullptr; + LogWidget::instance()->addLog("RemoteClipboard: Global keyboard hook removed", LogWidget::Info); + } +} + + +LRESULT CALLBACK RemoteClipboard::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + if (s_instance) { + return s_instance->handleKeyEvent(nCode, wParam, lParam); + } + 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') { + // 读取剪贴板数据,并构造 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 继续传递 + } + } + } + return CallNextHookEx(s_hook, nCode, wParam, lParam); } void RemoteClipboard::onClipboardMessageReceived(const ClipboardEvent& clipboardEvent) @@ -50,4 +149,4 @@ void RemoteClipboard::onClipboardMessageReceived(const ClipboardEvent& clipboard LogWidget::instance()->addLog("RemoteClipboard: Received unknown clipboard event", LogWidget::Warning); break; } -} +} \ No newline at end of file diff --git a/DeskServer/RemoteClipboard.h b/DeskServer/RemoteClipboard.h index b82b08b..2093273 100644 --- a/DeskServer/RemoteClipboard.h +++ b/DeskServer/RemoteClipboard.h @@ -2,16 +2,31 @@ #define REMOTECLIPBOARD_H #include +#include #include "rendezvous.pb.h" class RemoteClipboard : public QObject { Q_OBJECT public: explicit RemoteClipboard(QObject* parent = nullptr); + ~RemoteClipboard(); + + bool start(); + void stop(); + +signals: + void ctrlCPressed(const ClipboardEvent& clipboardEvent); public slots: // 接收远程传来的 ClipboardEvent 消息,并更新系统剪贴板数据 void onClipboardMessageReceived(const ClipboardEvent& clipboardEvent); + +private: + static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); + LRESULT handleKeyEvent(int nCode, WPARAM wParam, LPARAM lParam); + + static HHOOK s_hook; + static RemoteClipboard* s_instance; }; -#endif // REMOTECLIPBOARD_H +#endif // REMOTECLIPBOARD_H \ No newline at end of file