diff --git a/CMakeLists.txt b/CMakeLists.txt index ec37cc2b..b4d6753f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,128 +1,128 @@ cmake_minimum_required(VERSION 3.0) project(kdeconnect) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMAddTests) include(ECMSetupVersion) include(ECMInstallIcons) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) include(KDEConnectMacros.cmake) ecm_setup_version(1.3.3 VARIABLE_PREFIX KDECONNECT VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/kdeconnect-version.h ) if (SAILFISHOS) find_package(PkgConfig) set(KF5_MIN_VERSION "5.31.0") set(QT_MIN_VERSION "5.6.0") set(KF5_REQUIRED_COMPONENTS I18n DBusAddons CoreAddons IconThemes Config) set(KF5_OPTIONAL_COMPONENTS) set(QCA_MIN_VERSION 2.0.0) pkg_search_module(SFOS REQUIRED sailfishapp) pkg_check_modules(QCA2 qca2-qt5>=${QCA_MIN_VERSION} REQUIRED) add_definitions(-DSAILFISHOS) include_directories(${QCA2_INCLUDEDIR}) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) else() set(KF5_MIN_VERSION "5.48.0") set(QT_MIN_VERSION "5.10.0") set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Kirigami2) set(KF5_OPTIONAL_COMPONENTS DocTools) - if(UNIX) + if(UNIX AND NOT APPLE) set(KF5_OPTIONAL_COMPONENTS ${KF5_OPTIONAL_COMPONENTS} Runner) endif() set(QCA_MIN_VERSION "2.1.0") find_package(Qca-qt5 ${QCA_MIN_VERSION} REQUIRED) if(NOT WIN32 AND NOT APPLE) find_package(KF5PulseAudioQt REQUIRED) endif() add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_KEYWORDS -DQT_NO_CAST_FROM_ASCII) endif() find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Quick Network) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ${KF5_REQUIRED_COMPONENTS}) if (KF5_OPTIONAL_COMPONENTS) find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS ${KF5_OPTIONAL_COMPONENTS}) endif() if (NOT ZSH_AUTOCOMPLETE_DIR) set(ZSH_AUTOCOMPLETE_DIR "${CMAKE_INSTALL_PREFIX}/share/zsh/site-functions") endif() find_package(Qt5Multimedia) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "QtQuick plugins to build user interfaces based on KDE UX guidelines" PURPOSE "Required for KDE Connect's QML-based GUI applications" URL "https://www.kde.org/products/kirigami/" TYPE RUNTIME ) option(PRIVATE_DBUS_ENABLED "Use private dbus session for kdeconnect" OFF) if(PRIVATE_DBUS_ENABLED OR APPLE) add_compile_definitions(USE_PRIVATE_DBUS) endif() add_subdirectory(core) if(NOT SAILFISHOS) add_subdirectory(kcm) add_subdirectory(kcmplugin) add_subdirectory(daemon) endif() if(NOT WIN32 AND NOT SAILFISHOS) add_subdirectory(kio) add_subdirectory(plasmoid) endif() add_subdirectory(icon) add_subdirectory(interfaces) add_subdirectory(data) add_subdirectory(plugins) add_subdirectory(cli) add_subdirectory(declarativeplugin) if(KF5Runner_FOUND) add_subdirectory(runners) endif() if (NOT SAILFISHOS) add_subdirectory(app) add_subdirectory(indicator) add_subdirectory(urlhandler) add_subdirectory(nautilus-extension) add_subdirectory(fileitemactionplugin) else() add_subdirectory(sfos) endif() find_package(KF5Kirigami2) find_package(KF5People REQUIRED) find_package(KF5PeopleVCard) set_package_properties(KF5PeopleVCard PROPERTIES PURPOSE "Read vcards from the file system" URL "https://phabricator.kde.org/source/kpeoplevcard/" TYPE RUNTIME ) add_subdirectory(smsapp) if(KF5DocTools_FOUND) add_subdirectory(doc) endif() if(BUILD_TESTING AND NOT SAILFISHOS) add_subdirectory(tests) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/plugins/mousepad/CMakeLists.txt b/plugins/mousepad/CMakeLists.txt index 8ea00470..d6dc68af 100644 --- a/plugins/mousepad/CMakeLists.txt +++ b/plugins/mousepad/CMakeLists.txt @@ -1,39 +1,45 @@ -if(UNIX) +if(UNIX AND NOT APPLE) find_package(KF5 ${KF5_MIN_VERSION} QUIET OPTIONAL_COMPONENTS Wayland) find_package(LibFakeKey QUIET) set_package_properties(LibFakeKey PROPERTIES DESCRIPTION "fake key events" URL "https://www.yoctoproject.org/tools-resources/projects/matchbox" TYPE OPTIONAL PURPOSE "Needed for the remote mousepad plugin" ) if (LibFakeKey_FOUND) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras) find_package(XTest REQUIRED) find_package(X11 REQUIRED) include_directories(${XTEST_INCLUDE_DIRS} ${X11_INCLUDE_DIR} ${LibFakeKey_INCLUDE_DIRS}) endif() endif() set(HAVE_WINDOWS ${WIN32}) set(HAVE_X11 ${LibFakeKey_FOUND}) set(HAVE_WAYLAND ${KF5Wayland_FOUND}) +set(HAVE_MACOS ${APPLE}) configure_file(config-mousepad.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-mousepad.h ) kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES mousepadplugin.cpp abstractremoteinput.cpp) target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui KF5::I18n) if (HAVE_WINDOWS) target_sources(kdeconnect_mousepad PUBLIC windowsremoteinput.cpp) endif() if(HAVE_WAYLAND) target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp) target_link_libraries(kdeconnect_mousepad KF5::WaylandClient) endif() if(HAVE_X11) target_sources(kdeconnect_mousepad PUBLIC x11remoteinput.cpp) target_link_libraries(kdeconnect_mousepad Qt5::X11Extras ${X11_LIBRARIES} ${XTEST_LIBRARIES} ${LibFakeKey_LIBRARIES}) endif() + +if (HAVE_MACOS) + target_sources(kdeconnect_mousepad PUBLIC macosremoteinput.mm) + target_link_libraries(kdeconnect_mousepad "-framework CoreGraphics" "-framework ApplicationServices" "-framework Cocoa") +endif() diff --git a/plugins/mousepad/config-mousepad.h.cmake b/plugins/mousepad/config-mousepad.h.cmake index 91dbd4d8..12f9d1a0 100644 --- a/plugins/mousepad/config-mousepad.h.cmake +++ b/plugins/mousepad/config-mousepad.h.cmake @@ -1,3 +1,4 @@ #cmakedefine01 HAVE_WAYLAND #cmakedefine01 HAVE_X11 #cmakedefine01 HAVE_WINDOWS +#cmakedefine01 HAVE_MACOS diff --git a/plugins/mousepad/macosremoteinput.h b/plugins/mousepad/macosremoteinput.h new file mode 100644 index 00000000..a6aeaa25 --- /dev/null +++ b/plugins/mousepad/macosremoteinput.h @@ -0,0 +1,38 @@ +/** + * Copyright 2019 Weixuan XIAO + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MACOSREMOTEINPUT_H +#define MACOSREMOTEINPUT_H + +#include "abstractremoteinput.h" + +class MacOSRemoteInput + : public AbstractRemoteInput +{ + Q_OBJECT + +public: + explicit MacOSRemoteInput(QObject* parent); + + bool handlePacket(const NetworkPacket& np) override; + bool hasKeyboardSupport() override; +}; + +#endif diff --git a/plugins/mousepad/macosremoteinput.mm b/plugins/mousepad/macosremoteinput.mm new file mode 100644 index 00000000..9059d300 --- /dev/null +++ b/plugins/mousepad/macosremoteinput.mm @@ -0,0 +1,241 @@ +/** + * Copyright 2019 Weixuan XIAO + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "macosremoteinput.h" + +#include +#include + +#include +#include +#include + +int SpecialKeysMap[] = { + 0, // Invalid + kVK_Delete, // 1 + kVK_Tab, // 2 + kVK_Return, // 3 + kVK_LeftArrow, // 4 + kVK_UpArrow, // 5 + kVK_RightArrow, // 6 + kVK_DownArrow, // 7 + kVK_PageUp, // 8 + kVK_PageDown, // 9 + kVK_Home, // 10 + kVK_End, // 11 + kVK_Return, // 12 + kVK_ForwardDelete, // 13 + kVK_Escape, // 14 + 0, // 15 + 0, // 16 + 0, // 17 + 0, // 18 + 0, // 19 + 0, // 20 + kVK_F1, // 21 + kVK_F2, // 22 + kVK_F3, // 23 + kVK_F4, // 24 + kVK_F5, // 25 + kVK_F6, // 26 + kVK_F7, // 27 + kVK_F8, // 28 + kVK_F9, // 29 + kVK_F10, // 30 + kVK_F11, // 31 + kVK_F12, // 32 +}; + +template +size_t arraySize(T(&arr)[N]) { (void)arr; return N; } + +MacOSRemoteInput::MacOSRemoteInput(QObject* parent) + : AbstractRemoteInput(parent) +{ + // Ask user to enable accessibility API + NSDictionary *options = @{(id)kAXTrustedCheckOptionPrompt: @YES}; + if (!AXIsProcessTrustedWithOptions((CFDictionaryRef)options)) { + qWarning() << "kdeconnectd is not set as a trusted accessibility, mousepad and keyboard will not work"; + } +} + +bool MacOSRemoteInput::handlePacket(const NetworkPacket& np) +{ + // Return if not trusted + if (!AXIsProcessTrusted()) { + return false; + } + + float dx = np.get(QStringLiteral("dx"), 0); + float dy = np.get(QStringLiteral("dy"), 0); + + bool isSingleClick = np.get(QStringLiteral("singleclick"), false); + bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); + bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); + bool isRightClick = np.get(QStringLiteral("rightclick"), false); + bool isSingleHold = np.get(QStringLiteral("singlehold"), false); + bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); + bool isScroll = np.get(QStringLiteral("scroll"), false); + QString key = np.get(QStringLiteral("key"), QLatin1String("")); + int specialKey = np.get(QStringLiteral("specialKey"), 0); + + if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) { + QPoint point = QCursor::pos(); + + if (isSingleClick) { + CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, CGPointMake(point.x(), point.y()), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, event); + CGEventSetType(event, kCGEventLeftMouseUp); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (isDoubleClick) { + CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, CGPointMake(point.x(), point.y()), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, event); + CGEventSetType(event, kCGEventLeftMouseUp); + CGEventPost(kCGHIDEventTap, event); + + /* Key to access an integer field that contains the mouse button click + state. A click state of 1 represents a single click. A click state of 2 + represents a double-click. A click state of 3 represents a + triple-click. */ + CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2); + + CGEventSetType(event, kCGEventLeftMouseDown); + CGEventPost(kCGHIDEventTap, event); + CGEventSetType(event, kCGEventLeftMouseUp); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (isMiddleClick) { + CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseDown, CGPointMake(point.x(), point.y()), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, event); + CGEventSetType(event, kCGEventOtherMouseUp); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (isRightClick) { + CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventRightMouseDown, CGPointMake(point.x(), point.y()), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, event); + CGEventSetType(event, kCGEventRightMouseUp); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (isSingleHold) { + CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, CGPointMake(point.x(), point.y()), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (isSingleRelease) { + CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, CGPointMake(point.x(), point.y()), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (isScroll) { + CGEventRef event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, dy); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + } else if (!key.isEmpty() || specialKey) { + // Get function keys + bool ctrl = np.get(QStringLiteral("ctrl"), false); + bool alt = np.get(QStringLiteral("alt"), false); + bool shift = np.get(QStringLiteral("shift"), false); + bool super = np.get(QStringLiteral("super"), false); + + + if (ctrl) { + CGEventRef ctrlEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)kVK_Control, true); + CGEventPost(kCGHIDEventTap, ctrlEvent); + CFRelease(ctrlEvent); + } + if (alt) { + CGEventRef optionEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)kVK_Option, true); + CGEventPost(kCGHIDEventTap, optionEvent); + CFRelease(optionEvent); + } + if (shift) { + CGEventRef shiftEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)kVK_Shift, true); + CGEventPost(kCGHIDEventTap, shiftEvent); + CFRelease(shiftEvent); + } + if (super) { + CGEventRef commandEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)kVK_Command, true); + CGEventPost(kCGHIDEventTap, commandEvent); + CFRelease(commandEvent); + } + + // Keys + if (specialKey) + { + if (specialKey >= (int)arraySize(SpecialKeysMap)) { + qWarning() << "Unsupported special key identifier"; + return false; + } + + CGEventRef specialKeyDownEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)SpecialKeysMap[specialKey], true), + specialKeyUpEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)SpecialKeysMap[specialKey], false); + + CGEventPost(kCGHIDEventTap, specialKeyDownEvent); + CGEventPost(kCGHIDEventTap, specialKeyUpEvent); + + CFRelease(specialKeyDownEvent); + CFRelease(specialKeyUpEvent); + } else { + for (int i=0;i * Copyright 2015 Martin Gräßlin * Copyright 2014 Ahmed I. Khalil * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mousepadplugin.h" #include #include #include #if HAVE_WINDOWS #include "windowsremoteinput.h" +#elif HAVE_MACOS + #include "macosremoteinput.h" #else #if HAVE_X11 #include "x11remoteinput.h" #endif #if HAVE_WAYLAND #include "waylandremoteinput.h" #endif #endif K_PLUGIN_CLASS_WITH_JSON(MousepadPlugin, "kdeconnect_mousepad.json") MousepadPlugin::MousepadPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_impl(nullptr) { #if HAVE_WINDOWS m_impl = new WindowsRemoteInput(this); +#elif HAVE_MACOS + m_impl = new MacOSRemoteInput(this); #else #if HAVE_X11 if (QGuiApplication::platformName() == QLatin1String("xcb")) { m_impl = new X11RemoteInput(this); } #endif #if HAVE_WAYLAND if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { m_impl = new WaylandRemoteInput(this); } #endif #endif if (!m_impl) { qDebug() << "KDE Connect was built without" << QGuiApplication::platformName() << "support"; } } MousepadPlugin::~MousepadPlugin() { delete m_impl; } bool MousepadPlugin::receivePacket(const NetworkPacket& np) { if (m_impl) { return m_impl->handlePacket(np); } else { return false; } } void MousepadPlugin::connected() { NetworkPacket np(PACKET_TYPE_MOUSEPAD_KEYBOARDSTATE); if (m_impl) { np.set(QStringLiteral("state"), m_impl->hasKeyboardSupport()); } sendPacket(np); } #include "mousepadplugin.moc"