From 4e9b7c3aa1cbfea8b03ed95b6ca0d611b48e4068 Mon Sep 17 00:00:00 2001 From: rankun Date: Sun, 10 Feb 2019 20:22:29 +0800 Subject: [PATCH] =?UTF-8?q?update:mac=E9=99=90=E5=88=B6=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QtScrcpy/QtScrcpy.pro | 2 + QtScrcpy/main.cpp | 10 ++ QtScrcpy/util/.DS_Store | Bin 0 -> 6148 bytes QtScrcpy/util/cocoamousetap.h | 29 ++++ QtScrcpy/util/cocoamousetap.mm | 237 +++++++++++++++++++++++++++++++++ QtScrcpy/util/util.pri | 6 + QtScrcpy/videoform.cpp | 6 + TODO.txt | 3 +- 8 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 QtScrcpy/util/.DS_Store create mode 100644 QtScrcpy/util/cocoamousetap.h create mode 100644 QtScrcpy/util/cocoamousetap.mm create mode 100644 QtScrcpy/util/util.pri diff --git a/QtScrcpy/QtScrcpy.pro b/QtScrcpy/QtScrcpy.pro index fb41029..7c1364c 100644 --- a/QtScrcpy/QtScrcpy.pro +++ b/QtScrcpy/QtScrcpy.pro @@ -54,6 +54,7 @@ include ($$PWD/uibase/uibase.pri) include ($$PWD/fontawesome/fontawesome.pri) include ($$PWD/filehandler/filehandler.pri) include ($$PWD/recorder/recorder.pri) +include ($$PWD/util/util.pri) # 附加包含路径 INCLUDEPATH += \ @@ -68,6 +69,7 @@ INCLUDEPATH += \ $$PWD/uibase \ $$PWD/filehandler \ $$PWD/recorder \ + $$PWD/util \ $$PWD/fontawesome diff --git a/QtScrcpy/main.cpp b/QtScrcpy/main.cpp index 6e208e8..5f780d7 100644 --- a/QtScrcpy/main.cpp +++ b/QtScrcpy/main.cpp @@ -6,6 +6,9 @@ #include "dialog.h" #include "decoder.h" +#ifdef Q_OS_OSX +#include "cocoamousetap.h" +#endif Dialog* g_mainDlg = Q_NULLPTR; @@ -25,6 +28,10 @@ int main(int argc, char *argv[]) installTranslator(); +#ifdef Q_OS_OSX + CocoaMouseTap::getInstance()->initMouseEventTap(); +#endif + #ifdef Q_OS_WIN32 qputenv("QTSCRCPY_ADB_PATH", "../../../third_party/adb/win/adb.exe"); qputenv("QTSCRCPY_SERVER_PATH", "../../../third_party/scrcpy-server.jar"); @@ -50,6 +57,9 @@ int main(int argc, char *argv[]) int ret = a.exec(); +#ifdef Q_OS_OSX + CocoaMouseTap::getInstance()->quitMouseEventTap(); +#endif Decoder::deInit(); return ret; } diff --git a/QtScrcpy/util/.DS_Store b/QtScrcpy/util/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8z45|!R0Z1N%F(jFgL>QrFAPJ2!M?+vV1V%$(Gz3ON zU^D~25V%SxcdJP zRior+2#kinunYl47MEZbCs3t{!+W4QHvuXKVuPw;Mo^s$(F3lEVT}ML$bg~*R5_@+ b2Uo?6kTwK}57Iu`5P${HC_Nei0}uiLNUI8I literal 0 HcmV?d00001 diff --git a/QtScrcpy/util/cocoamousetap.h b/QtScrcpy/util/cocoamousetap.h new file mode 100644 index 0000000..da92cb9 --- /dev/null +++ b/QtScrcpy/util/cocoamousetap.h @@ -0,0 +1,29 @@ +#ifndef COCOAMOUSETAP_H +#define COCOAMOUSETAP_H +#include +#include + +struct MouseEventTapData; +class QWidget; +class CocoaMouseTap : public QThread +{ +private: + CocoaMouseTap(QObject *parent = Q_NULLPTR); + ~CocoaMouseTap(); + +public: + static CocoaMouseTap* getInstance(); + void initMouseEventTap(); + void quitMouseEventTap(); + void enableMouseEventTap(QWidget* widget, bool enabled); + +protected: + void run() override; + +private: + MouseEventTapData *m_tapData = Q_NULLPTR; + QSemaphore m_runloopStartedSemaphore; + static CocoaMouseTap *s_instance; +}; + +#endif // COCOAMOUSETAP_H diff --git a/QtScrcpy/util/cocoamousetap.mm b/QtScrcpy/util/cocoamousetap.mm new file mode 100644 index 0000000..3d4d5f8 --- /dev/null +++ b/QtScrcpy/util/cocoamousetap.mm @@ -0,0 +1,237 @@ +#import +#include +#include + +#include "cocoamousetap.h" + +static const CGEventMask movementEventsMask = + CGEventMaskBit(kCGEventLeftMouseDragged) + | CGEventMaskBit(kCGEventRightMouseDragged) + | CGEventMaskBit(kCGEventMouseMoved); + +static const CGEventMask allGrabbedEventsMask = + CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventLeftMouseUp) + | CGEventMaskBit(kCGEventRightMouseDown) | CGEventMaskBit(kCGEventRightMouseUp) + | CGEventMaskBit(kCGEventOtherMouseDown) | CGEventMaskBit(kCGEventOtherMouseUp) + | CGEventMaskBit(kCGEventLeftMouseDragged) | CGEventMaskBit(kCGEventRightMouseDragged) + | CGEventMaskBit(kCGEventMouseMoved); + +typedef struct MouseEventTapData{ + CFMachPortRef tap = Q_NULLPTR; + CFRunLoopRef runloop = Q_NULLPTR; + CFRunLoopSourceRef runloopSource = Q_NULLPTR; + QWidget* widget = Q_NULLPTR; +} MouseEventTapData; + +static CGEventRef Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) +{ + Q_UNUSED(proxy); + MouseEventTapData *tapdata = (MouseEventTapData*)refcon; + + NSView *nsview; + NSWindow *nswindow; + NSRect windowRect; + NSRect newWindowRect; + CGPoint eventLocation; + + switch (type) { + case kCGEventTapDisabledByTimeout: + { + CGEventTapEnable(tapdata->tap, true); + return NULL; + } + case kCGEventTapDisabledByUserInput: + { + return NULL; + } + default: + break; + } + + + if (!tapdata->widget) { + return event; + } + // get nswindow from qt widget + nsview = (NSView *)tapdata->widget->window()->winId(); + if (!nsview) { + return event; + } + nswindow = [nsview window]; + + eventLocation = CGEventGetUnflippedLocation(event); + windowRect = [nswindow contentRectForFrameRect:[nswindow frame]]; + + newWindowRect = NSMakeRect(windowRect.origin.x, windowRect.origin.y, + windowRect.size.width - 10, windowRect.size.height - 10); + qDebug() << newWindowRect.origin.x << newWindowRect.origin.y + << newWindowRect.size.width << newWindowRect.size.height; + + if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), newWindowRect, NO)) { + + /* This is in CGs global screenspace coordinate system, which has a + * flipped Y. + */ + CGPoint newLocation = CGEventGetLocation(event); + + if (eventLocation.x < NSMinX(windowRect)) { + newLocation.x = NSMinX(windowRect); + } else if (eventLocation.x >= NSMaxX(windowRect)) { + newLocation.x = NSMaxX(windowRect) - 1.0; + } + + if (eventLocation.y <= NSMinY(windowRect)) { + newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1); + } else if (eventLocation.y > NSMaxY(windowRect)) { + newLocation.y += (eventLocation.y - NSMaxY(windowRect)); + } + + CGWarpMouseCursorPosition(newLocation); + CGAssociateMouseAndMouseCursorPosition(YES); + + if ((CGEventMaskBit(type) & movementEventsMask) == 0) { + /* For click events, we just constrain the event to the window, so + * no other app receives the click event. We can't due the same to + * movement events, since they mean that our warp cursor above + * behaves strangely. + */ + CGEventSetLocation(event, newLocation); + } + } + + return event; +} + +static void SemaphorePostCallback(CFRunLoopTimerRef timer, void *info) +{ + Q_UNUSED(timer); + QSemaphore *runloopStartedSemaphore = (QSemaphore *)info; + if (runloopStartedSemaphore) { + runloopStartedSemaphore->release(); + } +} + +CocoaMouseTap * CocoaMouseTap::s_instance = Q_NULLPTR; + +CocoaMouseTap::CocoaMouseTap(QObject *parent) + : QThread(parent) +{ + m_tapData = new MouseEventTapData; +} + +CocoaMouseTap::~CocoaMouseTap() +{ + if (m_tapData) { + delete m_tapData; + m_tapData = Q_NULLPTR; + } +} + +CocoaMouseTap *CocoaMouseTap::getInstance() +{ + if (s_instance == Q_NULLPTR) { + s_instance = new CocoaMouseTap(); + } + return s_instance; +} + +void CocoaMouseTap::initMouseEventTap() +{ + if (!m_tapData) { + return; + } + + m_tapData->tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, + kCGEventTapOptionDefault, allGrabbedEventsMask, + &Cocoa_MouseTapCallback, m_tapData); + if (!m_tapData->tap) { + return; + } + /* Tap starts disabled, until app requests mouse grab */ + CGEventTapEnable(m_tapData->tap, false); + start(); +} + +void CocoaMouseTap::quitMouseEventTap() +{ + bool status; + if (m_tapData == Q_NULLPTR || m_tapData->tap == Q_NULLPTR) { + /* event tap was already cleaned up (possibly due to CGEventTapCreate + * returning null.) + */ + return; + } + + /* Ensure that the runloop has been started first. + * TODO: Move this to InitMouseEventTap, check for error conditions that can + * happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of + * grabbing the mouse if it fails to Init. + */ + status = m_runloopStartedSemaphore.tryAcquire(1, 5000); + if (status) { + /* Then stop it, which will cause Cocoa_MouseTapThread to return. */ + CFRunLoopStop(m_tapData->runloop); + /* And then wait for Cocoa_MouseTapThread to finish cleaning up. It + * releases some of the pointers in tapdata. */ + wait(); + } +} + +void CocoaMouseTap::enableMouseEventTap(QWidget* widget, bool enabled) +{ + if (m_tapData && m_tapData->tap) + { + enabled ? m_tapData->widget = widget : m_tapData->widget = Q_NULLPTR; + CGEventTapEnable(m_tapData->tap, enabled); + } +} + +void CocoaMouseTap::run() +{ + /* Tap was created on main thread but we own it now. */ + CFMachPortRef eventTap = m_tapData->tap; + if (eventTap) { + /* Try to create a runloop source we can schedule. */ + CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); + if (runloopSource) { + m_tapData->runloopSource = runloopSource; + } else { + CFRelease(eventTap); + m_runloopStartedSemaphore.release(); + /* TODO: Both here and in the return below, set some state in + * tapdata to indicate that initialization failed, which we should + * check in InitMouseEventTap, after we move the semaphore check + * from Quit to Init. + */ + return; + } + } else { + m_runloopStartedSemaphore.release(); + return; + } + + m_tapData->runloop = CFRunLoopGetCurrent(); + CFRunLoopAddSource(m_tapData->runloop, m_tapData->runloopSource, kCFRunLoopCommonModes); + CFRunLoopTimerContext context = {.info = &m_runloopStartedSemaphore}; + /* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */ + CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context); + CFRunLoopAddTimer(m_tapData->runloop, timer, kCFRunLoopCommonModes); + CFRelease(timer); + + /* Run the event loop to handle events in the event tap. */ + CFRunLoopRun(); + /* Make sure this is signaled so that SDL_QuitMouseEventTap knows it can safely SDL_WaitThread for us. */ + if (m_runloopStartedSemaphore.available() < 1) { + m_runloopStartedSemaphore.release(); + } + CFRunLoopRemoveSource(m_tapData->runloop, m_tapData->runloopSource, kCFRunLoopCommonModes); + + /* Clean up. */ + CGEventTapEnable(m_tapData->tap, false); + CFRelease(m_tapData->runloopSource); + CFRelease(m_tapData->tap); + m_tapData->runloopSource = Q_NULLPTR; + m_tapData->tap = Q_NULLPTR; + + return; +} diff --git a/QtScrcpy/util/util.pri b/QtScrcpy/util/util.pri new file mode 100644 index 0000000..b489d3f --- /dev/null +++ b/QtScrcpy/util/util.pri @@ -0,0 +1,6 @@ +mac { + HEADERS += $$PWD/cocoamousetap.h + OBJECTIVE_SOURCES += $$PWD/cocoamousetap.mm + LIBS += -framework Appkit + QMAKE_CFLAGS += -mmacosx-version-min=10.6 +} diff --git a/QtScrcpy/videoform.cpp b/QtScrcpy/videoform.cpp index 77442bd..629224a 100644 --- a/QtScrcpy/videoform.cpp +++ b/QtScrcpy/videoform.cpp @@ -18,6 +18,9 @@ #include "toolform.h" #include "controlevent.h" #include "recorder.h" +#ifdef Q_OS_OSX +#include "cocoamousetap.h" +#endif VideoForm::VideoForm(const QString& serial, quint16 maxSize, quint32 bitRate, const QString& fileName, QWidget *parent) : QWidget(parent), @@ -126,6 +129,9 @@ void VideoForm::initSignals() } else { ClipCursor(Q_NULLPTR); } +#endif +#ifdef Q_OS_OSX + CocoaMouseTap::getInstance()->enableMouseEventTap(ui->videoWidget, grab); #endif }); connect(m_server, &Server::serverStartResult, this, [this](bool success){ diff --git a/TODO.txt b/TODO.txt index 0cf3e19..8bd218e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,6 @@ // TODO mac: Qt::FramelessWindowHit full screen is abnormal - +ƶƽ̨ӿ +MacϷʱƶ⡣ չģָȣ ģָ(ע⣺Ͷû)