Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions DeskControler/DeskControler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <QJsonObject>
#include <QFile>
#include "VideoWidget.h"
#include "RemoteClipboard.h"
#include "LogWidget.h"

DeskControler::DeskControler(QWidget* parent)
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions DeskControler/DeskControler.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "NetworkManager.h"
#include "VideoReceiver.h"
#include "ui_DeskControler.h"
#include "RemoteClipboard.h"

class NetworkManager;

Expand Down Expand Up @@ -31,4 +32,5 @@ private slots:
Ui::DeskControlerClass ui;
NetworkManager* m_networkManager;
VideoReceiver* m_videoReceiver;
RemoteClipboard* m_remoteClipboard = nullptr;
};
1 change: 1 addition & 0 deletions DeskControler/NetworkWorker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ void NetworkWorker::sendClipboardEventToServer(const ClipboardEvent& clipboardEv
sendData.append(reinterpret_cast<const char*>(&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();
Expand Down
140 changes: 96 additions & 44 deletions DeskControler/RemoteClipboard.cpp
Original file line number Diff line number Diff line change
@@ -1,67 +1,119 @@
#include "RemoteClipboard.h"
#include "LogWidget.h"
#include <QApplication>
#include <QClipboard>
#include <QKeyEvent>
#include <QFile>
#include <QFileInfo>
#include "LogWidget.h"
#include <QMetaObject>
#include <QtCore/QMimeData>

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<QKeyEvent*>(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<KBDLLHOOKSTRUCT*>(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);
}
23 changes: 15 additions & 8 deletions DeskControler/RemoteClipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@
#define REMOTECLIPBOARD_H

#include <QObject>
#include <QEvent>
#include <QMimeData>
#include <windows.h>
#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
4 changes: 4 additions & 0 deletions DeskServer/DeskServer.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<ClCompile Include="RelayManager.cpp" />
<ClCompile Include="RelayPeerClient.cpp" />
<ClCompile Include="RelaySocketWorker.cpp" />
<ClCompile Include="RemoteClipboard.cpp" />
<ClCompile Include="RemoteInputSimulator.cpp" />
<ClCompile Include="ScreenCaptureEncoder.cpp" />
<QtRcc Include="DeskServer.qrc" />
Expand Down Expand Up @@ -104,6 +105,9 @@
<ItemGroup>
<QtMoc Include="RelaySocketWorker.h" />
</ItemGroup>
<ItemGroup>
<QtMoc Include="RemoteClipboard.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
Expand Down
11 changes: 11 additions & 0 deletions DeskServer/RelayManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ RelayManager::RelayManager(QObject* parent)
m_encoder(nullptr)
{
m_inputSimulator = new RemoteInputSimulator(nullptr);
m_remoteClipboard = new RemoteClipboard(nullptr);
}

RelayManager::~RelayManager() {
Expand Down Expand Up @@ -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()) {
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions DeskServer/RelayManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "RemoteInputSimulator.h"
#include "ScreenCaptureEncoder.h"
#include "RelaySocketWorker.h"
#include "RemoteClipboard.h"

class RelayManager : public QObject {
Q_OBJECT
Expand Down Expand Up @@ -44,6 +45,7 @@ private slots:
ScreenCaptureEncoder* m_encoder;
RemoteInputSimulator* m_inputSimulator;
QThread* m_encoderThread;
RemoteClipboard* m_remoteClipboard;
};

#endif // RELAYMANAGER_H
53 changes: 53 additions & 0 deletions DeskServer/RemoteClipboard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "RemoteClipboard.h"
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#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<QUrl> 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;
}
}
Loading
Loading