diff --git a/CMakeLists.txt b/CMakeLists.txt index 642bea7..2917a49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,21 +29,25 @@ set(HEADERS src/controller.h src/stdafx.h src/updater.h + src/hotkey.h ) set(SOURCES - "src/main.cpp" - "src/controller.cpp" - "src/updater.cpp" + src/main.cpp + src/controller.cpp + src/updater.cpp + src/hotkey.cpp ) set(RESOURCES GPT_Translator.qrc ) +add_subdirectory(lib/) IF (CMAKE_SYSTEM_NAME MATCHES "Linux") MESSAGE(STATUS "current platform: Linux ") + find_package(X11 REQUIRED) qt_add_executable(GPT_Translator ${SOURCES} ${HEADERS} ${RESOURCES} @@ -66,7 +70,7 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Windows") ) ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin") MESSAGE(STATUS "current platform: Darwin") - + find_library( APP_SERVICES_LIBRARY ApplicationServices ) set(MACOSX_BUNDLE_ICON_FILE logo.icns) # And the following tells CMake where to find and install the file itself. @@ -99,19 +103,36 @@ IF (CMAKE_SYSTEM_NAME MATCHES "Windows") PRIVATE Qt6::Quick Qt6::TextToSpeech + qhotkey + user32 ) else() target_link_libraries(GPT_Translator PRIVATE Qt6::Quick Qt6::TextToSpeech + qhotkey + user32 ) endif() -ELSE() +ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_include_directories(GPT_Translator PRIVATE ${X11_INCLUDE_DIR}) + + target_link_libraries(GPT_Translator + PRIVATE + Qt6::Quick + Qt6::TextToSpeech + qhotkey + ${X11_LIBRARIES} + ) + +ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin") target_link_libraries(GPT_Translator PRIVATE Qt6::Quick Qt6::TextToSpeech + qhotkey + ${APP_SERVICES_LIBRARY} ) ENDIF() diff --git a/GPT_Translator.qrc b/GPT_Translator.qrc index eb0d560..5dd1967 100644 --- a/GPT_Translator.qrc +++ b/GPT_Translator.qrc @@ -23,5 +23,7 @@ res/speak1.png res/speak2.png res/tray.png + qml/GPopWindow.qml + res/icon.png diff --git a/doc/screenshot.png b/doc/screenshot.png index 296cebb..dca0086 100644 Binary files a/doc/screenshot.png and b/doc/screenshot.png differ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..ab4d751 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + + +add_subdirectory(QHotkey-1.5.0) diff --git a/lib/QHotkey-1.5.0/.gitignore b/lib/QHotkey-1.5.0/.gitignore new file mode 100644 index 0000000..04b7a50 --- /dev/null +++ b/lib/QHotkey-1.5.0/.gitignore @@ -0,0 +1,34 @@ +# C++ objects and libs + +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es + +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +qrc_*.cpp +ui_*.h +Makefile* +*build-* + +# QtCreator + +*.autosave + +#QtCtreator Qml +*.qmlproject.user +*.qmlproject.user.* diff --git a/lib/QHotkey-1.5.0/CMakeLists.txt b/lib/QHotkey-1.5.0/CMakeLists.txt new file mode 100644 index 0000000..a3d1097 --- /dev/null +++ b/lib/QHotkey-1.5.0/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.1) + +project(qhotkey VERSION 1.5.0 LANGUAGES CXX) + +option(QHOTKEY_EXAMPLES "Build examples" OFF) +option(QHOTKEY_INSTALL "Enable install rule" ON) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_AUTOMOC ON) + +if(NOT QT_DEFAULT_MAJOR_VERSION) + set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5") +endif() + +if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + find_package(Qt${QT_DEFAULT_MAJOR_VERSION} 6.2.0 COMPONENTS Core Gui REQUIRED) +else() + find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core Gui REQUIRED) +endif() + +add_library(qhotkey QHotkey/qhotkey.cpp) +add_library(QHotkey::QHotkey ALIAS qhotkey) +target_link_libraries(qhotkey PUBLIC Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Gui) + +if(BUILD_SHARED_LIBS) + target_compile_definitions(qhotkey PRIVATE QHOTKEY_LIBRARY) + target_compile_definitions(qhotkey PUBLIC QHOTKEY_SHARED) +endif() + +if(APPLE) + find_library(CARBON_LIBRARY Carbon) + mark_as_advanced(CARBON_LIBRARY) + + target_sources(qhotkey PRIVATE QHotkey/qhotkey_mac.cpp) + target_link_libraries(qhotkey PRIVATE ${CARBON_LIBRARY}) +elseif(WIN32) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_win.cpp) +else() + find_package(X11 REQUIRED) + if(QT_DEFAULT_MAJOR_VERSION GREATER_EQUAL 6) + target_link_libraries(qhotkey PRIVATE ${X11_LIBRARIES}) + else() + find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS X11Extras REQUIRED) + target_link_libraries(qhotkey + PRIVATE + ${X11_LIBRARIES} + Qt${QT_DEFAULT_MAJOR_VERSION}::X11Extras) + endif() + + include_directories(${X11_INCLUDE_DIR}) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_x11.cpp) +endif() + +include(GNUInstallDirs) + +target_include_directories(qhotkey + PUBLIC + $ + $) + +include(CMakePackageConfigHelpers) + +set_target_properties(qhotkey PROPERTIES + SOVERSION ${PROJECT_VERSION_MAJOR} + VERSION ${PROJECT_VERSION} + INTERFACE_QHotkey_MAJOR_VERSION ${PROJECT_VERSION_MAJOR} + COMPATIBLE_INTERFACE_STRING QHotkey_MAJOR_VERSION) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/QHotkeyConfigVersion.cmake + VERSION "${PROJECT_VERSION}" + COMPATIBILITY AnyNewerVersion) + +if(QHOTKEY_EXAMPLES) + add_subdirectory(HotkeyTest) +endif() + +if(QHOTKEY_INSTALL) + set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/QHotkey) + + install( + TARGETS qhotkey EXPORT QHotkeyConfig + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/QHotkey/qhotkey.h + ${CMAKE_CURRENT_SOURCE_DIR}/QHotkey/QHotkey + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/QHotkeyConfigVersion.cmake + DESTINATION ${INSTALL_CONFIGDIR}) + install(EXPORT QHotkeyConfig DESTINATION ${INSTALL_CONFIGDIR}) + + export(TARGETS qhotkey FILE QHotkeyConfig.cmake) +endif() diff --git a/lib/QHotkey-1.5.0/QHotkey/QHotkey b/lib/QHotkey-1.5.0/QHotkey/QHotkey new file mode 100644 index 0000000..21d5e57 --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/QHotkey @@ -0,0 +1 @@ +#include "qhotkey.h" diff --git a/lib/QHotkey-1.5.0/QHotkey/QHotkey.pro b/lib/QHotkey-1.5.0/QHotkey/QHotkey.pro new file mode 100644 index 0000000..3a79376 --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/QHotkey.pro @@ -0,0 +1,16 @@ +TEMPLATE = lib +win32: CONFIG += dll + +TARGET = QHotkey +VERSION = 1.5.0 + +include(../qhotkey.pri) + +DEFINES += QHOTKEY_SHARED QHOTKEY_LIBRARY + +# use INSTALL_ROOT to modify the install location +headers.files = $$PUBLIC_HEADERS +headers.path = $$[QT_INSTALL_HEADERS] +target.path = $$[QT_INSTALL_LIBS] +INSTALLS += target headers + diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey.cpp b/lib/QHotkey-1.5.0/QHotkey/qhotkey.cpp new file mode 100644 index 0000000..3b76d9c --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey.cpp @@ -0,0 +1,377 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(logQHotkey, "QHotkey") + +void QHotkey::addGlobalMapping(const QKeySequence &shortcut, QHotkey::NativeShortcut nativeShortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const int key = shortcut[0].toCombined(); +#else + const int key = shortcut[0]; +#endif + + QMetaObject::invokeMethod(QHotkeyPrivate::instance(), "addMappingInvoked", Qt::QueuedConnection, + Q_ARG(Qt::Key, Qt::Key(key & ~Qt::KeyboardModifierMask)), + Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)), + Q_ARG(QHotkey::NativeShortcut, nativeShortcut)); +} + +bool QHotkey::isPlatformSupported() +{ + return QHotkeyPrivate::isPlatformSupported(); +} + +QHotkey::QHotkey(QObject *parent) : + QObject(parent), + _keyCode(Qt::Key_unknown), + _modifiers(Qt::NoModifier), + _registered(false) +{} + +QHotkey::QHotkey(const QKeySequence &shortcut, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setShortcut(shortcut, autoRegister); +} + +QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setShortcut(keyCode, modifiers, autoRegister); +} + +QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setNativeShortcut(shortcut, autoRegister); +} + +QHotkey::~QHotkey() +{ + if(_registered) + QHotkeyPrivate::instance()->removeShortcut(this); +} + +QKeySequence QHotkey::shortcut() const +{ + if(_keyCode == Qt::Key_unknown) + return QKeySequence(); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QKeySequence((_keyCode | _modifiers).toCombined()); +#else + return QKeySequence(static_cast(_keyCode | _modifiers)); +#endif +} + +Qt::Key QHotkey::keyCode() const +{ + return _keyCode; +} + +Qt::KeyboardModifiers QHotkey::modifiers() const +{ + return _modifiers; +} + +QHotkey::NativeShortcut QHotkey::currentNativeShortcut() const +{ + return _nativeShortcut; +} + +bool QHotkey::isRegistered() const +{ + return _registered; +} + +bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister) +{ + if(shortcut.isEmpty()) + return resetShortcut(); + if(shortcut.count() > 1) { + qCWarning(logQHotkey, "Keysequences with multiple shortcuts are not allowed! " + "Only the first shortcut will be used!"); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const int key = shortcut[0].toCombined(); +#else + const int key = shortcut[0]; +#endif + + return setShortcut(Qt::Key(key & ~Qt::KeyboardModifierMask), + Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask), + autoRegister); +} + +bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister) +{ + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else + return false; + } + + if(keyCode == Qt::Key_unknown) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; + } + + _keyCode = keyCode; + _modifiers = modifiers; + _nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(keyCode, modifiers); + if(_nativeShortcut.isValid()) { + if(autoRegister) + return QHotkeyPrivate::instance()->addShortcut(this); + return true; + } + + qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << keyCode << "Modifiers:" << modifiers; + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return false; +} + +bool QHotkey::resetShortcut() +{ + if(_registered && + !QHotkeyPrivate::instance()->removeShortcut(this)) { + return false; + } + + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; +} + +bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister) +{ + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else + return false; + } + + if(nativeShortcut.isValid()) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = nativeShortcut; + if(autoRegister) + return QHotkeyPrivate::instance()->addShortcut(this); + return true; + } + + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; +} + +bool QHotkey::setRegistered(bool registered) +{ + if(_registered && !registered) + return QHotkeyPrivate::instance()->removeShortcut(this); + if(!_registered && registered) { + if(!_nativeShortcut.isValid()) + return false; + return QHotkeyPrivate::instance()->addShortcut(this); + } + return true; +} + + + +// ---------- QHotkeyPrivate implementation ---------- + +QHotkeyPrivate::QHotkeyPrivate() +{ + Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated"); + qApp->eventDispatcher()->installNativeEventFilter(this); +} + +QHotkeyPrivate::~QHotkeyPrivate() +{ + if(!shortcuts.isEmpty()) + qCWarning(logQHotkey) << "QHotkeyPrivate destroyed with registered shortcuts!"; + if(qApp && qApp->eventDispatcher()) + qApp->eventDispatcher()->removeNativeEventFilter(this); +} + +QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers) +{ + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + QHotkey::NativeShortcut res; + if(!QMetaObject::invokeMethod(this, "nativeShortcutInvoked", conType, + Q_RETURN_ARG(QHotkey::NativeShortcut, res), + Q_ARG(Qt::Key, keycode), + Q_ARG(Qt::KeyboardModifiers, modifiers))) { + return QHotkey::NativeShortcut(); + } + return res; +} + +bool QHotkeyPrivate::addShortcut(QHotkey *hotkey) +{ + if(hotkey->_registered) + return false; + + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + bool res = false; + if(!QMetaObject::invokeMethod(this, "addShortcutInvoked", conType, + Q_RETURN_ARG(bool, res), + Q_ARG(QHotkey*, hotkey))) { + return false; + } + + if(res) + emit hotkey->registeredChanged(true); + return res; +} + +bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey) +{ + if(!hotkey->_registered) + return false; + + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + bool res = false; + if(!QMetaObject::invokeMethod(this, "removeShortcutInvoked", conType, + Q_RETURN_ARG(bool, res), + Q_ARG(QHotkey*, hotkey))) { + return false; + } + + if(res) + emit hotkey->registeredChanged(false); + return res; +} + +void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut) +{ + QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::activated); + for(QHotkey *hkey : shortcuts.values(shortcut)) + signal.invoke(hkey, Qt::QueuedConnection); +} + +void QHotkeyPrivate::releaseShortcut(QHotkey::NativeShortcut shortcut) +{ + QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::released); + for(QHotkey *hkey : shortcuts.values(shortcut)) + signal.invoke(hkey, Qt::QueuedConnection); +} + +void QHotkeyPrivate::addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut) +{ + mapping.insert({keycode, modifiers}, nativeShortcut); +} + +bool QHotkeyPrivate::addShortcutInvoked(QHotkey *hotkey) +{ + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; + + if(!shortcuts.contains(shortcut)) { + if(!registerShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to register %1. Error: %2").arg(hotkey->shortcut().toString(), error); + return false; + } + } + + shortcuts.insert(shortcut, hotkey); + hotkey->_registered = true; + return true; +} + +bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey) +{ + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; + + if(shortcuts.remove(shortcut, hotkey) == 0) + return false; + hotkey->_registered = false; + emit hotkey->registeredChanged(true); + if(shortcuts.count(shortcut) == 0) { + if (!unregisterShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to unregister %1. Error: %2").arg(hotkey->shortcut().toString(), error); + return false; + } + return true; + } + return true; +} + +QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers) +{ + if(mapping.contains({keycode, modifiers})) + return mapping.value({keycode, modifiers}); + + bool ok1 = false; + auto k = nativeKeycode(keycode, ok1); + bool ok2 = false; + auto m = nativeModifiers(modifiers, ok2); + if(ok1 && ok2) + return {k, m}; + return {}; +} + + + +QHotkey::NativeShortcut::NativeShortcut() : + key(), + modifier(), + valid(false) +{} + +QHotkey::NativeShortcut::NativeShortcut(quint32 key, quint32 modifier) : + key(key), + modifier(modifier), + valid(true) +{} + +bool QHotkey::NativeShortcut::isValid() const +{ + return valid; +} + +bool QHotkey::NativeShortcut::operator ==(QHotkey::NativeShortcut other) const +{ + return (key == other.key) && + (modifier == other.modifier) && + valid == other.valid; +} + +bool QHotkey::NativeShortcut::operator !=(QHotkey::NativeShortcut other) const +{ + return (key != other.key) || + (modifier != other.modifier) || + valid != other.valid; +} + +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key) +{ + return qHash(key.key) ^ qHash(key.modifier); +} + +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed) +{ + return qHash(key.key, seed) ^ qHash(key.modifier, seed); +} diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey.h b/lib/QHotkey-1.5.0/QHotkey/qhotkey.h new file mode 100644 index 0000000..3697c8e --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey.h @@ -0,0 +1,130 @@ +#ifndef QHOTKEY_H +#define QHOTKEY_H + +#include +#include +#include +#include + +#ifdef QHOTKEY_SHARED +# ifdef QHOTKEY_LIBRARY +# define QHOTKEY_EXPORT Q_DECL_EXPORT +# else +# define QHOTKEY_EXPORT Q_DECL_IMPORT +# endif +#else +# define QHOTKEY_EXPORT +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + #define QHOTKEY_HASH_SEED size_t +#else + #define QHOTKEY_HASH_SEED uint +#endif + +//! A class to define global, systemwide Hotkeys +class QHOTKEY_EXPORT QHotkey : public QObject +{ + Q_OBJECT + //! @private + friend class QHotkeyPrivate; + + //! Specifies whether this hotkey is currently registered or not + Q_PROPERTY(bool registered READ isRegistered WRITE setRegistered NOTIFY registeredChanged) + //! Holds the shortcut this hotkey will be triggered on + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut RESET resetShortcut) + +public: + //! Defines shortcut with native keycodes + class QHOTKEY_EXPORT NativeShortcut { + public: + //! The native keycode + quint32 key; + //! The native modifiers + quint32 modifier; + + //! Creates an invalid native shortcut + NativeShortcut(); + //! Creates a valid native shortcut, with the given key and modifiers + NativeShortcut(quint32 key, quint32 modifier = 0); + + //! Checks, whether this shortcut is valid or not + bool isValid() const; + + //! Equality operator + bool operator ==(NativeShortcut other) const; + //! Inequality operator + bool operator !=(NativeShortcut other) const; + + private: + bool valid; + }; + + //! Adds a global mapping of a key sequence to a replacement native shortcut + static void addGlobalMapping(const QKeySequence &shortcut, NativeShortcut nativeShortcut); + + //! Checks if global shortcuts are supported by the current platform + static bool isPlatformSupported(); + + //! Default Constructor + explicit QHotkey(QObject *parent = nullptr); + //! Constructs a hotkey with a shortcut and optionally registers it + explicit QHotkey(const QKeySequence &shortcut, bool autoRegister = false, QObject *parent = nullptr); + //! Constructs a hotkey with a key and modifiers and optionally registers it + explicit QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false, QObject *parent = nullptr); + //! Constructs a hotkey from a native shortcut and optionally registers it + explicit QHotkey(NativeShortcut shortcut, bool autoRegister = false, QObject *parent = nullptr); + ~QHotkey() override; + + //! @readAcFn{QHotkey::registered} + bool isRegistered() const; + //! @readAcFn{QHotkey::shortcut} + QKeySequence shortcut() const; + //! @readAcFn{QHotkey::shortcut} - the key only + Qt::Key keyCode() const; + //! @readAcFn{QHotkey::shortcut} - the modifiers only + Qt::KeyboardModifiers modifiers() const; + + //! Get the current native shortcut + NativeShortcut currentNativeShortcut() const; + +public slots: + //! @writeAcFn{QHotkey::registered} + bool setRegistered(bool registered); + + //! @writeAcFn{QHotkey::shortcut} + bool setShortcut(const QKeySequence &shortcut, bool autoRegister = false); + //! @writeAcFn{QHotkey::shortcut} + bool setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false); + //! @resetAcFn{QHotkey::shortcut} + bool resetShortcut(); + + //! Set this hotkey to a native shortcut + bool setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister = false); + +signals: + //! Will be emitted if the shortcut is pressed + void activated(QPrivateSignal); + + //! Will be emitted if the shortcut press is released + void released(QPrivateSignal); + + //! @notifyAcFn{QHotkey::registered} + void registeredChanged(bool registered); + +private: + Qt::Key _keyCode; + Qt::KeyboardModifiers _modifiers; + + NativeShortcut _nativeShortcut; + bool _registered; +}; + +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key); +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed); + +QHOTKEY_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey) + +Q_DECLARE_METATYPE(QHotkey::NativeShortcut) + +#endif // QHOTKEY_H diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey.pri b/lib/QHotkey-1.5.0/QHotkey/qhotkey.pri new file mode 100644 index 0000000..a7c9725 --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey.pri @@ -0,0 +1 @@ +message(The pri file has been moved one directory up! use that one instead) diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey_mac.cpp b/lib/QHotkey-1.5.0/QHotkey/qhotkey_mac.cpp new file mode 100644 index 0000000..2cf0db4 --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey_mac.cpp @@ -0,0 +1,291 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include + +class QHotkeyPrivateMac : public QHotkeyPrivate +{ +public: + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + + static OSStatus hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + static OSStatus hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + +protected: + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static bool isHotkeyHandlerRegistered; + static QHash hotkeyRefs; +}; +NATIVE_INSTANCE(QHotkeyPrivateMac) + +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + +bool QHotkeyPrivateMac::isHotkeyHandlerRegistered = false; +QHash QHotkeyPrivateMac::hotkeyRefs; + +bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(message) + Q_UNUSED(result) + return false; +} + +quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode, bool &ok) +{ + // Constants found in NSEvent.h from AppKit.framework + ok = true; + switch (keycode) { + case Qt::Key_Return: + return kVK_Return; + case Qt::Key_Enter: + return kVK_ANSI_KeypadEnter; + case Qt::Key_Tab: + return kVK_Tab; + case Qt::Key_Space: + return kVK_Space; + case Qt::Key_Backspace: + return kVK_Delete; + case Qt::Key_Escape: + return kVK_Escape; + case Qt::Key_CapsLock: + return kVK_CapsLock; + case Qt::Key_Option: + return kVK_Option; + case Qt::Key_F17: + return kVK_F17; + case Qt::Key_VolumeUp: + return kVK_VolumeUp; + case Qt::Key_VolumeDown: + return kVK_VolumeDown; + case Qt::Key_F18: + return kVK_F18; + case Qt::Key_F19: + return kVK_F19; + case Qt::Key_F20: + return kVK_F20; + case Qt::Key_F5: + return kVK_F5; + case Qt::Key_F6: + return kVK_F6; + case Qt::Key_F7: + return kVK_F7; + case Qt::Key_F3: + return kVK_F3; + case Qt::Key_F8: + return kVK_F8; + case Qt::Key_F9: + return kVK_F9; + case Qt::Key_F11: + return kVK_F11; + case Qt::Key_F13: + return kVK_F13; + case Qt::Key_F16: + return kVK_F16; + case Qt::Key_F14: + return kVK_F14; + case Qt::Key_F10: + return kVK_F10; + case Qt::Key_F12: + return kVK_F12; + case Qt::Key_F15: + return kVK_F15; + case Qt::Key_Help: + return kVK_Help; + case Qt::Key_Home: + return kVK_Home; + case Qt::Key_PageUp: + return kVK_PageUp; + case Qt::Key_Delete: + return kVK_ForwardDelete; + case Qt::Key_F4: + return kVK_F4; + case Qt::Key_End: + return kVK_End; + case Qt::Key_F2: + return kVK_F2; + case Qt::Key_PageDown: + return kVK_PageDown; + case Qt::Key_F1: + return kVK_F1; + case Qt::Key_Left: + return kVK_LeftArrow; + case Qt::Key_Right: + return kVK_RightArrow; + case Qt::Key_Down: + return kVK_DownArrow; + case Qt::Key_Up: + return kVK_UpArrow; + default: + ok = false; + break; + } + + UTF16Char ch = keycode; + + CFDataRef currentLayoutData; + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + + if (currentKeyboard == NULL) + return 0; + + currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + CFRelease(currentKeyboard); + if (currentLayoutData == NULL) + return 0; + + UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData); + UCKeyboardTypeHeader* table = header->keyboardTypeList; + + uint8_t *data = (uint8_t*)header; + for (quint32 i=0; i < header->keyboardTypeCount; i++) { + UCKeyStateRecordsIndex* stateRec = 0; + if (table[i].keyStateRecordsIndexOffset != 0) { + stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); + if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; + } + + UCKeyToCharTableIndex* charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); + if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue; + + for (quint32 j=0; j < charTable->keyToCharTableCount; j++) { + UCKeyOutput* keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); + for (quint32 k=0; k < charTable->keyToCharTableSize; k++) { + if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { + long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; + if (stateRec && idx < stateRec->keyStateRecordCount) { + UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); + if (rec->stateZeroCharData == ch) { + ok = true; + return k; + } + } + } + else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { + if (keyToChar[k] == ch) { + ok = true; + return k; + } + } + } + } + } + return 0; +} + +quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= shiftKey; + if (modifiers & Qt::ControlModifier) + nMods |= cmdKey; + if (modifiers & Qt::AltModifier) + nMods |= optionKey; + if (modifiers & Qt::MetaModifier) + nMods |= controlKey; + if (modifiers & Qt::KeypadModifier) + nMods |= kEventKeyModifierNumLockMask; + ok = true; + return nMods; +} + +bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut) +{ + if (!this->isHotkeyHandlerRegistered) + { + EventTypeSpec pressEventSpec; + pressEventSpec.eventClass = kEventClassKeyboard; + pressEventSpec.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyPressEventHandler, 1, &pressEventSpec, NULL, NULL); + + EventTypeSpec releaseEventSpec; + releaseEventSpec.eventClass = kEventClassKeyboard; + releaseEventSpec.eventKind = kEventHotKeyReleased; + InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyReleaseEventHandler, 1, &releaseEventSpec, NULL, NULL); + } + + EventHotKeyID hkeyID; + hkeyID.signature = shortcut.key; + hkeyID.id = shortcut.modifier; + + EventHotKeyRef eventRef = 0; + OSStatus status = RegisterEventHotKey(shortcut.key, + shortcut.modifier, + hkeyID, + GetApplicationEventTarget(), + 0, + &eventRef); + if (status != noErr) { + error = QString::number(status); + return false; + } else { + this->hotkeyRefs.insert(shortcut, eventRef); + return true; + } +} + +bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ + EventHotKeyRef eventRef = QHotkeyPrivateMac::hotkeyRefs.value(shortcut); + OSStatus status = UnregisterEventHotKey(eventRef); + if (status != noErr) { + error = QString::number(status); + return false; + } else { + this->hotkeyRefs.remove(shortcut); + return true; + } +} + +OSStatus QHotkeyPrivateMac::hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + + if (GetEventClass(event) == kEventClassKeyboard && + GetEventKind(event) == kEventHotKeyPressed) { + EventHotKeyID hkeyID; + GetEventParameter(event, + kEventParamDirectObject, + typeEventHotKeyID, + NULL, + sizeof(EventHotKeyID), + NULL, + &hkeyID); + hotkeyPrivate->activateShortcut({hkeyID.signature, hkeyID.id}); + } + + return noErr; +} + +OSStatus QHotkeyPrivateMac::hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + + if (GetEventClass(event) == kEventClassKeyboard && + GetEventKind(event) == kEventHotKeyReleased) { + EventHotKeyID hkeyID; + GetEventParameter(event, + kEventParamDirectObject, + typeEventHotKeyID, + NULL, + sizeof(EventHotKeyID), + NULL, + &hkeyID); + hotkeyPrivate->releaseShortcut({hkeyID.signature, hkeyID.id}); + } + + return noErr; +} diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey_p.h b/lib/QHotkey-1.5.0/QHotkey/qhotkey_p.h new file mode 100644 index 0000000..8bc5ab6 --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey_p.h @@ -0,0 +1,62 @@ +#ifndef QHOTKEY_P_H +#define QHOTKEY_P_H + +#include "qhotkey.h" +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + #define _NATIVE_EVENT_RESULT qintptr +#else + #define _NATIVE_EVENT_RESULT long +#endif + +class QHOTKEY_EXPORT QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT + +public: + QHotkeyPrivate();//singleton!!! + ~QHotkeyPrivate(); + + static QHotkeyPrivate *instance(); + static bool isPlatformSupported(); + + QHotkey::NativeShortcut nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers); + + bool addShortcut(QHotkey *hotkey); + bool removeShortcut(QHotkey *hotkey); + +protected: + void activateShortcut(QHotkey::NativeShortcut shortcut); + void releaseShortcut(QHotkey::NativeShortcut shortcut); + + virtual quint32 nativeKeycode(Qt::Key keycode, bool &ok) = 0;//platform implement + virtual quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) = 0;//platform implement + + virtual bool registerShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement + virtual bool unregisterShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement + + QString error; + +private: + QHash, QHotkey::NativeShortcut> mapping; + QMultiHash shortcuts; + + Q_INVOKABLE void addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut); + Q_INVOKABLE bool addShortcutInvoked(QHotkey *hotkey); + Q_INVOKABLE bool removeShortcutInvoked(QHotkey *hotkey); + Q_INVOKABLE QHotkey::NativeShortcut nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers); +}; + +#define NATIVE_INSTANCE(ClassName) \ + Q_GLOBAL_STATIC(ClassName, hotkeyPrivate) \ + \ + QHotkeyPrivate *QHotkeyPrivate::instance()\ + {\ + return hotkeyPrivate;\ + } + +#endif // QHOTKEY_P_H diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey_win.cpp b/lib/QHotkey-1.5.0/QHotkey/qhotkey_win.cpp new file mode 100644 index 0000000..715b92e --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey_win.cpp @@ -0,0 +1,302 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include +#include + +#define HKEY_ID(nativeShortcut) (((nativeShortcut.key ^ (nativeShortcut.modifier << 8)) & 0x0FFF) | 0x7000) + +#if !defined(MOD_NOREPEAT) +#define MOD_NOREPEAT 0x4000 +#endif + +class QHotkeyPrivateWin : public QHotkeyPrivate +{ +public: + QHotkeyPrivateWin(); + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + +protected: + void pollForHotkeyRelease(); + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static QString formatWinError(DWORD winError); + QTimer pollTimer; + QHotkey::NativeShortcut polledShortcut; +}; +NATIVE_INSTANCE(QHotkeyPrivateWin) + +QHotkeyPrivateWin::QHotkeyPrivateWin(){ + pollTimer.setInterval(50); + connect(&pollTimer, &QTimer::timeout, this, &QHotkeyPrivateWin::pollForHotkeyRelease); +} + +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + +bool QHotkeyPrivateWin::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + MSG* msg = static_cast(message); + if(msg->message == WM_HOTKEY) { + QHotkey::NativeShortcut shortcut = {HIWORD(msg->lParam), LOWORD(msg->lParam)}; + this->activateShortcut(shortcut); + this->polledShortcut = shortcut; + this->pollTimer.start(); + } + + return false; +} + +void QHotkeyPrivateWin::pollForHotkeyRelease() +{ + bool pressed = (GetAsyncKeyState(this->polledShortcut.key) & (1 << 15)) != 0; + if(!pressed) { + this->pollTimer.stop(); + this->releaseShortcut(this->polledShortcut); + } +} + +quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode, bool &ok) +{ + ok = true; + if(keycode <= 0xFFFF) {//Try to obtain the key from it's "character" + const SHORT vKey = VkKeyScanW(static_cast(keycode)); + if(vKey > -1) + return LOBYTE(vKey); + } + + //find key from switch/case --> Only finds a very small subset of keys + switch (keycode) + { + case Qt::Key_Escape: + return VK_ESCAPE; + case Qt::Key_Tab: + case Qt::Key_Backtab: + return VK_TAB; + case Qt::Key_Backspace: + return VK_BACK; + case Qt::Key_Return: + case Qt::Key_Enter: + return VK_RETURN; + case Qt::Key_Insert: + return VK_INSERT; + case Qt::Key_Delete: + return VK_DELETE; + case Qt::Key_Pause: + return VK_PAUSE; + case Qt::Key_Print: + return VK_PRINT; + case Qt::Key_Clear: + return VK_CLEAR; + case Qt::Key_Home: + return VK_HOME; + case Qt::Key_End: + return VK_END; + case Qt::Key_Left: + return VK_LEFT; + case Qt::Key_Up: + return VK_UP; + case Qt::Key_Right: + return VK_RIGHT; + case Qt::Key_Down: + return VK_DOWN; + case Qt::Key_PageUp: + return VK_PRIOR; + case Qt::Key_PageDown: + return VK_NEXT; + case Qt::Key_CapsLock: + return VK_CAPITAL; + case Qt::Key_NumLock: + return VK_NUMLOCK; + case Qt::Key_ScrollLock: + return VK_SCROLL; + + case Qt::Key_F1: + return VK_F1; + case Qt::Key_F2: + return VK_F2; + case Qt::Key_F3: + return VK_F3; + case Qt::Key_F4: + return VK_F4; + case Qt::Key_F5: + return VK_F5; + case Qt::Key_F6: + return VK_F6; + case Qt::Key_F7: + return VK_F7; + case Qt::Key_F8: + return VK_F8; + case Qt::Key_F9: + return VK_F9; + case Qt::Key_F10: + return VK_F10; + case Qt::Key_F11: + return VK_F11; + case Qt::Key_F12: + return VK_F12; + case Qt::Key_F13: + return VK_F13; + case Qt::Key_F14: + return VK_F14; + case Qt::Key_F15: + return VK_F15; + case Qt::Key_F16: + return VK_F16; + case Qt::Key_F17: + return VK_F17; + case Qt::Key_F18: + return VK_F18; + case Qt::Key_F19: + return VK_F19; + case Qt::Key_F20: + return VK_F20; + case Qt::Key_F21: + return VK_F21; + case Qt::Key_F22: + return VK_F22; + case Qt::Key_F23: + return VK_F23; + case Qt::Key_F24: + return VK_F24; + + case Qt::Key_Menu: + return VK_APPS; + case Qt::Key_Help: + return VK_HELP; + case Qt::Key_MediaNext: + return VK_MEDIA_NEXT_TRACK; + case Qt::Key_MediaPrevious: + return VK_MEDIA_PREV_TRACK; + case Qt::Key_MediaPlay: + return VK_MEDIA_PLAY_PAUSE; + case Qt::Key_MediaStop: + return VK_MEDIA_STOP; + case Qt::Key_VolumeDown: + return VK_VOLUME_DOWN; + case Qt::Key_VolumeUp: + return VK_VOLUME_UP; + case Qt::Key_VolumeMute: + return VK_VOLUME_MUTE; + case Qt::Key_Mode_switch: + return VK_MODECHANGE; + case Qt::Key_Select: + return VK_SELECT; + case Qt::Key_Printer: + return VK_PRINT; + case Qt::Key_Execute: + return VK_EXECUTE; + case Qt::Key_Sleep: + return VK_SLEEP; + case Qt::Key_Period: + return VK_DECIMAL; + case Qt::Key_Play: + return VK_PLAY; + case Qt::Key_Cancel: + return VK_CANCEL; + + case Qt::Key_Forward: + return VK_BROWSER_FORWARD; + case Qt::Key_Refresh: + return VK_BROWSER_REFRESH; + case Qt::Key_Stop: + return VK_BROWSER_STOP; + case Qt::Key_Search: + return VK_BROWSER_SEARCH; + case Qt::Key_Favorites: + return VK_BROWSER_FAVORITES; + case Qt::Key_HomePage: + return VK_BROWSER_HOME; + + case Qt::Key_LaunchMail: + return VK_LAUNCH_MAIL; + case Qt::Key_LaunchMedia: + return VK_LAUNCH_MEDIA_SELECT; + case Qt::Key_Launch0: + return VK_LAUNCH_APP1; + case Qt::Key_Launch1: + return VK_LAUNCH_APP2; + + case Qt::Key_Massyo: + return VK_OEM_FJ_MASSHOU; + case Qt::Key_Touroku: + return VK_OEM_FJ_TOUROKU; + + default: + if(keycode <= 0xFFFF) + return (byte)keycode; + else { + ok = false; + return 0; + } + } +} + +quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= MOD_SHIFT; + if (modifiers & Qt::ControlModifier) + nMods |= MOD_CONTROL; + if (modifiers & Qt::AltModifier) + nMods |= MOD_ALT; + if (modifiers & Qt::MetaModifier) + nMods |= MOD_WIN; + ok = true; + return nMods; +} + +bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut) +{ + BOOL ok = RegisterHotKey(NULL, + HKEY_ID(shortcut), + shortcut.modifier + MOD_NOREPEAT, + shortcut.key); + if(ok) + return true; + else { + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); + return false; + } +} + +bool QHotkeyPrivateWin::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ + BOOL ok = UnregisterHotKey(NULL, HKEY_ID(shortcut)); + if(ok) + return true; + else { + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); + return false; + } +} + +QString QHotkeyPrivateWin::formatWinError(DWORD winError) +{ + wchar_t *buffer = NULL; + DWORD num = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + winError, + 0, + (LPWSTR)&buffer, + 0, + NULL); + if(buffer) { + QString res = QString::fromWCharArray(buffer, num); + LocalFree(buffer); + return res; + } else + return QString(); +} diff --git a/lib/QHotkey-1.5.0/QHotkey/qhotkey_x11.cpp b/lib/QHotkey-1.5.0/QHotkey/qhotkey_x11.cpp new file mode 100644 index 0000000..ce408e6 --- /dev/null +++ b/lib/QHotkey-1.5.0/QHotkey/qhotkey_x11.cpp @@ -0,0 +1,268 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + #include +#else + #include + #include +#endif + +#include +#include +#include +#include + +//compability to pre Qt 5.8 +#ifndef Q_FALLTHROUGH +#define Q_FALLTHROUGH() (void)0 +#endif + +class QHotkeyPrivateX11 : public QHotkeyPrivate +{ +public: + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + +protected: + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + static QString getX11String(Qt::Key keycode); + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static const QVector specialModifiers; + static const quint32 validModsMask; + xcb_key_press_event_t prevHandledEvent; + xcb_key_press_event_t prevEvent; + + static QString formatX11Error(Display *display, int errorCode); + + class HotkeyErrorHandler { + public: + HotkeyErrorHandler(); + ~HotkeyErrorHandler(); + + static bool hasError; + static QString errorString; + + private: + XErrorHandler prevHandler; + + static int handleError(Display *display, XErrorEvent *error); + }; +}; +NATIVE_INSTANCE(QHotkeyPrivateX11) + +bool QHotkeyPrivate::isPlatformSupported() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + return qGuiApp->nativeInterface(); +#else + return QX11Info::isPlatformX11(); +#endif +} + +const QVector QHotkeyPrivateX11::specialModifiers = {0, Mod2Mask, LockMask, (Mod2Mask | LockMask)}; +const quint32 QHotkeyPrivateX11::validModsMask = ShiftMask | ControlMask | Mod1Mask | Mod4Mask; + +bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + auto *genericEvent = static_cast(message); + if (genericEvent->response_type == XCB_KEY_PRESS) { + xcb_key_press_event_t keyEvent = *static_cast(message); + this->prevEvent = keyEvent; + if (this->prevHandledEvent.response_type == XCB_KEY_RELEASE) { + if(this->prevHandledEvent.time == keyEvent.time) return false; + } + this->activateShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); + } else if (genericEvent->response_type == XCB_KEY_RELEASE) { + xcb_key_release_event_t keyEvent = *static_cast(message); + this->prevEvent = keyEvent; + QTimer::singleShot(50, [this, keyEvent] { + if(this->prevEvent.time == keyEvent.time && this->prevEvent.response_type == keyEvent.response_type && this->prevEvent.detail == keyEvent.detail){ + this->releaseShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); + } + }); + this->prevHandledEvent = keyEvent; + } + + return false; +} + +QString QHotkeyPrivateX11::getX11String(Qt::Key keycode) +{ + switch(keycode){ + + case Qt::Key_MediaLast : + case Qt::Key_MediaPrevious : + return QStringLiteral("XF86AudioPrev"); + case Qt::Key_MediaNext : + return QStringLiteral("XF86AudioNext"); + case Qt::Key_MediaPause : + case Qt::Key_MediaPlay : + case Qt::Key_MediaTogglePlayPause : + return QStringLiteral("XF86AudioPlay"); + case Qt::Key_MediaRecord : + return QStringLiteral("XF86AudioRecord"); + case Qt::Key_MediaStop : + return QStringLiteral("XF86AudioStop"); + default : + return QKeySequence(keycode).toString(QKeySequence::NativeText); + } +} + +quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok) +{ + QString keyString = getX11String(keycode); + + KeySym keysym = XStringToKeysym(keyString.toLatin1().constData()); + if (keysym == NoSymbol) { + //not found -> just use the key + if(keycode <= 0xFFFF) + keysym = keycode; + else + return 0; + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); + Display *display = x11Interface->display(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); + Display *display = QX11Info::display(); +#endif + + if(x11Interface) { + auto res = XKeysymToKeycode(display, keysym); + if(res != 0) + ok = true; + return res; + } + return 0; +} + +quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= ShiftMask; + if (modifiers & Qt::ControlModifier) + nMods |= ControlMask; + if (modifiers & Qt::AltModifier) + nMods |= Mod1Mask; + if (modifiers & Qt::MetaModifier) + nMods |= Mod4Mask; + ok = true; + return nMods; +} + +bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); + Display *display = x11Interface->display(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); + Display *display = QX11Info::display(); +#endif + + if(!display || !x11Interface) + return false; + + HotkeyErrorHandler errorHandler; + for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { + XGrabKey(display, + shortcut.key, + shortcut.modifier | specialMod, + DefaultRootWindow(display), + True, + GrabModeAsync, + GrabModeAsync); + } + XSync(display, False); + + if(errorHandler.hasError) { + error = errorHandler.errorString; + this->unregisterShortcut(shortcut); + return false; + } + return true; +} + +bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + Display *display = qGuiApp->nativeInterface()->display(); +#else + Display *display = QX11Info::display(); +#endif + + if(!display) + return false; + + HotkeyErrorHandler errorHandler; + for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { + XUngrabKey(display, + shortcut.key, + shortcut.modifier | specialMod, + XDefaultRootWindow(display)); + } + XSync(display, False); + + if(HotkeyErrorHandler::hasError) { + error = HotkeyErrorHandler::errorString; + return false; + } + return true; +} + +QString QHotkeyPrivateX11::formatX11Error(Display *display, int errorCode) +{ + char errStr[256]; + XGetErrorText(display, errorCode, errStr, 256); + return QString::fromLatin1(errStr); +} + + + +// ---------- QHotkeyPrivateX11::HotkeyErrorHandler implementation ---------- + +bool QHotkeyPrivateX11::HotkeyErrorHandler::hasError = false; +QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString; + +QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler() +{ + prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError); +} + +QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler() +{ + XSetErrorHandler(prevHandler); + hasError = false; + errorString.clear(); +} + +int QHotkeyPrivateX11::HotkeyErrorHandler::handleError(Display *display, XErrorEvent *error) +{ + switch (error->error_code) { + case BadAccess: + case BadValue: + case BadWindow: + if (error->request_code == 33 || //grab key + error->request_code == 34) {// ungrab key + hasError = true; + errorString = QHotkeyPrivateX11::formatX11Error(display, error->error_code); + return 1; + } + Q_FALLTHROUGH(); + // fall through + default: + return 0; + } +} diff --git a/qml/AppView.qml b/qml/AppView.qml index 5c6b4cc..6a286ef 100644 --- a/qml/AppView.qml +++ b/qml/AppView.qml @@ -9,6 +9,8 @@ import "." Item { id:root + property bool pinned: false + property alias inputText: inputArea.text signal settingClicked; function startTrans(){ if(inputArea.text.length > 0 && transBtn.visible) @@ -153,6 +155,7 @@ Item { onClicked: { mainWindow.flags = mainWindow.flags & (0xFFFFFFFF ^ Qt.WindowStaysOnTopHint) tItem.state = "no" + pinned = false } } @@ -167,6 +170,7 @@ Item { onClicked: { mainWindow.flags = mainWindow.flags |Qt.WindowStaysOnTopHint tItem.state = "yes" + pinned = true } } diff --git a/qml/GPopWindow.qml b/qml/GPopWindow.qml new file mode 100644 index 0000000..a2d1d48 --- /dev/null +++ b/qml/GPopWindow.qml @@ -0,0 +1,82 @@ +import QtQuick +import QtQuick.Controls + +import QtLocation +import Qt5Compat.GraphicalEffects +Window { + flags:Qt.Window | Qt.FramelessWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + + id:popWindow + height:20 + width:100 + maximumHeight: height + maximumWidth: width + minimumHeight: height + minimumWidth: width + color: "transparent" + + + + Rectangle{ + id:_rect + anchors.fill: parent + radius:5 + clip:true + + Image { + id: img + source:"qrc:/res/icon.png" + + anchors.left:parent.left + anchors.top: parent.top + height:parent.height + width:height + fillMode: Image.PreserveAspectCrop + layer.enabled: true + layer.effect: OpacityMask { + maskSource: mask + } + MouseArea{ + anchors.fill: parent + property variant clickPos: "1,1" + onPressed: { + clickPos = Qt.point(mouseX ,mouseY) + } + + onPositionChanged: { + var delta = Qt.point(mouseX -clickPos.x, mouseY-clickPos.y) + popWindow.x += delta.x; + popWindow.y += delta.y; + } + } + } + + Rectangle { + id: mask + width: popWindow.width + height: width + radius: 5 + visible: false + } + + } + + + +// Column{ +// anchors.fill: parent +// Button{ +// width:parent.width +// height:parent.height/2 + +// } +// Button{ +// width:parent.width +// height:parent.height/2 + +// } +// } + + + +} diff --git a/qml/SettingView.qml b/qml/SettingView.qml index f7a4782..cde8f0c 100644 --- a/qml/SettingView.qml +++ b/qml/SettingView.qml @@ -6,16 +6,46 @@ import Controller Item { signal backClicked; + + property bool lock:false + + function reload(){ setting.loadConfig() + lock = true keyInput.text = setting.apiKey serverInput.text = setting.apiServer + shortcutText.text = setting.shortCut if(setting.model == "gpt-3.5-turbo") modelSelector.currentIndex = 0 else if(setting.model == "gpt-4") modelSelector.currentIndex = 1 - saveBtn.visible = false + lock = false + + } + + function saveConfig(){ + if(lock){ + return + } + setting.apiServer = serverInput.text.trim() + setting.apiKey = keyInput.text + setting.shortCut = shortcutText.text + if(modelSelector.currentIndex == 0) + setting.model = "gpt-3.5-turbo" + else + setting.model = "gpt-4" + setting.updateConfig() + } + MouseArea { + anchors.fill: parent + onClicked: { + shortcutRect.focus = false; + if(shortcutText.text.length > 0){ + hotkey.setShortcut(shortcutText.text) + } + } } @@ -42,30 +72,6 @@ Item { } } - - - IconButton{ - id:saveBtn - width: 17.5 - height:20 - visible:false - anchors.right: parent.right - anchors.top: parent.top - normalUrl:"qrc:///res/save1.png" - hoveredUrl:"qrc:///res/save1.png" - pressedUrl:"qrc:///res/save2.png" - onClicked: { - setting.apiServer = serverInput.text.trim() - setting.apiKey = keyInput.text - if(modelSelector.currentIndex == 0) - setting.model = "gpt-3.5-turbo" - else - setting.model = "gpt-4" - setting.updateConfig() - visible = false - } - } - } Text{ id:serverText @@ -96,7 +102,7 @@ Item { padding:7 text: "https://api.openai.com" onTextChanged: { - saveBtn.visible = true + saveConfig() } } } @@ -133,7 +139,7 @@ Item { y:20 wrapMode: Text.WrapAnywhere onTextChanged:{ - saveBtn.visible = true + saveConfig() } background: Rectangle{ color: "#E6E7E7" @@ -161,17 +167,153 @@ Item { currentIndex: 0 model:["GPT-3.5", "GPT-4"] onCurrentIndexChanged:{ - saveBtn.visible = true + saveConfig() } height:40 } + Text{ + id:shortCutText + anchors.left: header.left + anchors.top:modelSelector.bottom + anchors.topMargin: 20 + text:"Shortcut" + font.bold: true + color:"green" + } + + Item { + id:shortcutItem + anchors.left: header.left + anchors.top:shortCutText.bottom + anchors.topMargin: 10 + width:100 + height:30 + Rectangle { + id:shortcutRect + color: "#E6E7E7" + anchors.fill: parent + radius: 5 + border.width:1 + border.color: color + onActiveFocusChanged: { + } + + onFocusChanged: { + if(focus){ + border.color = "green" + shortcutRect.forceActiveFocus() + shortcutText.text = "" + hotkey.setShortcut("") + }else{ + border.color = color + } + + } + + Text{ + id:shortcutText + anchors.centerIn: parent + text:"" + onTextChanged: { + saveConfig() + } + } + + Keys.onPressed:(event)=> { + if(!shortcutRect.focus){ + return + } + + shortcutText.text = "" + var vaild = false + var haveCtrl = false + + + if(Qt.platform.os === "macos" || Qt.platform.os === "osx"){ + if (event.modifiers & Qt.ControlModifier) { + shortcutText.text = "Ctrl+" + haveCtrl = true + } + if (event.modifiers & Qt.MetaModifier) { + shortcutText.text = "Meta+" + haveCtrl = true + } + }else{ + if (event.modifiers & Qt.ControlModifier) { + shortcutText.text = "Ctrl+" + haveCtrl = true + } + } + + if (event.modifiers & Qt.AltModifier) { + shortcutText.text = "Alt+" + haveCtrl = true + } + if (event.modifiers & Qt.ShiftModifier) { + shortcutText.text = "Shift+" + haveCtrl = true + } + + if(shortCutText.text.length > 0){ + switch(event.key){ + case Qt.Key_F1: shortcutText.text = "F1"; vaild = true; break; + case Qt.Key_F2: shortcutText.text = "F2";vaild = true; break; + case Qt.Key_F3: shortcutText.text = "F3";vaild = true; break; + case Qt.Key_F4: shortcutText.text = "F4"; vaild = true; break; + case Qt.Key_F5: shortcutText.text = "F5"; vaild = true; break; + case Qt.Key_F6: shortcutText.text = "F6";vaild = true; break; + case Qt.Key_F7: shortcutText.text = "F7"; vaild = true; break; + case Qt.Key_F8: shortcutText.text = "F8"; vaild = true; break; + case Qt.Key_F9: shortcutText.text = "F9"; vaild = true; break; + case Qt.Key_F10: shortcutText.text = "F10"; vaild = true; break; + case Qt.Key_F11: shortcutText.text = "F11"; vaild = true; break; + case Qt.Key_F12: shortcutText.text = "F12"; vaild = true; break; + } + if(event.key >= Qt.Key_0 && event.key <= Qt.Key_9 ){ + if(haveCtrl){ + shortcutText.text += String.fromCharCode(event.key) + vaild = true + } + }else if(event.key >= Qt.Key_A && event.key <= Qt.Key_Z ){ + if(haveCtrl){ + shortcutText.text += String.fromCharCode(event.key) + vaild = true + } + } + } + + if(vaild){ + shortcutRect.focus = false; + if(shortcutText.text.length > 0){ + if(hotkey.setShortcut(shortcutText.text) == false){ + shortcutRect.focus = false; + shortcutText.text = "" + } + } + } + + + } + + } + + MouseArea{ + anchors.fill: parent + onClicked: { + shortcutRect.focus = true + } + } + + + + } Text{ id:about anchors.left: header.left - anchors.top:modelSelector.bottom + anchors.top:shortcutItem.bottom anchors.topMargin: 20 text:"About" font.bold: true diff --git a/qml/main.qml b/qml/main.qml index 0e29f68..bd28dc0 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,11 +1,13 @@ import QtQuick import QtQuick.Controls - +import QtQuick.Window import QtQuick.Layouts import Qt.labs.platform import "." +import Controller + Window { id: mainWindow @@ -16,11 +18,66 @@ Window { minimumWidth:400 title: qsTr("GPT Translator") + property Component popComponent: null + property QtObject popW: null + + // flags:Qt.Window | Qt.FramelessWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + function popWindow(t, pos){ + + if(popComponent !== null){ + popW.close() + popComponent.destroy() + } + + popComponent = Qt.createComponent("GPopWindow.qml") + if (popComponent.status === Component.Ready) { + popW = popComponent.createObject(mainWindow) + if (popW) { + popW.x = pos.x + popW.y = pos.y + popW.visible = true + popW.show() + popW.raise() + popW.requestActivate() + mainWindow.visible = false + } else { + console.error("Error creating new window:", popComponent.errorString()) + } + } else { + console.error("Error loading DynamicWindow component:", popComponent.errorString()) + } + + } + Hotkey{ + id:hotkey + onSelectedTextChanged: { +// popWindow(selectedText, mousePos) + appView.inputText = selectedText + appView.startTrans() + mainWindow.show() + mainWindow.raise() + mainWindow.requestActivate() + + } + + } + + + + Component.onCompleted: { + hotkey.binding(app) + hotkey.setShortcut(setting.shortCut) + } + MouseArea{ + id:mouseArea anchors.fill: parent property variant clickPos: "1,1" + onClicked: { + + } onPressed: { clickPos = Qt.point(mouseX ,mouseY) @@ -32,15 +89,29 @@ Window { mainWindow.y += delta.y; } } + + onActiveChanged: { +// if(active){ +// mainWindow.visible = true +// }else{ +// if(!appView.pinned) +// mainWindow.visible = false +// } + } + + + + + Item{ anchors.fill: parent focus: true Keys.onPressed:(event)=> { - if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_R || - (event.modifiers & Qt.MetaModifier) && event.key === Qt.Key_R) { - // Command+R or Ctrl+R pressed - appView.startTrans() - } + if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_R || + (event.modifiers & Qt.MetaModifier) && event.key === Qt.Key_R) { + // Command+R or Ctrl+R pressed + appView.startTrans() + } } SystemTrayIcon { @@ -63,7 +134,13 @@ Window { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() + +// mainWindow.x = trayIcon.geometry.x - mainWindow.width/2 +// mainWindow.y = trayIcon.geometry.y + 50 +// mainWindow.visible = true } + + } SwipeView { @@ -90,4 +167,5 @@ Window { } + } diff --git a/res/icon.png b/res/icon.png new file mode 100644 index 0000000..9d4de2a Binary files /dev/null and b/res/icon.png differ diff --git a/src/controller.cpp b/src/controller.cpp index b2fa848..dc9a06d 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -7,6 +7,7 @@ Setting::Setting(QObject * parent): QObject{parent} _apiServer = ""; _apiKey = ""; _model = ""; + _shortCut = ""; //win C:\Users\xxxx\AppData\Local\GPT_Translator //macos /Users/xxx/Library/Preferences/GPT_Translator/ _configPath = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "config.json", QStandardPaths::LocateFile); @@ -51,6 +52,7 @@ bool Setting::loadConfig() _apiKey = obj.value("apiKey").toString(); _model = obj.value("model").toString(); _apiServer = obj.value("apiServer").toString(); + _shortCut = obj.value("shortCut").toString(); if(_apiServer.trimmed().length() == 0){ _apiServer = "https://api.openai.com"; } @@ -62,7 +64,7 @@ bool Setting::loadConfig() void Setting::updateConfig() { - QString s = "{\"apiKey\":\"" + _apiKey + "\",\"model\":\"" + _model + "\", \"apiServer\":\"" + _apiServer + "\"}"; + QString s = "{\"apiKey\":\"" + _apiKey + "\",\"model\":\"" + _model + "\", \"apiServer\":\"" + _apiServer + "\", \"shortCut\":\"" + _shortCut + "\"}"; QFile file(_configPath); if(file.open(QIODevice::WriteOnly)){ QTextStream out(&file); @@ -158,7 +160,7 @@ void Controller::sendMessage(QString str, int mode) QUrl apiUrl(_apiServer + "/v1/chat/completions"); QNetworkRequest request(apiUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Authorization", QString::fromStdString("Bearer %1").arg(_apiKey).toUtf8()); + request.setRawHeader("Authorization", QString::fromStdString("Bearer %1").arg(_apiKey.trimmed()).toUtf8()); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached QJsonObject requestData; diff --git a/src/controller.h b/src/controller.h index b026d84..cb4e8c4 100644 --- a/src/controller.h +++ b/src/controller.h @@ -1,7 +1,7 @@ /* * @Date: 2023-04-07 15:38:00 * @LastEditors: JessGuo - * @LastEditTime: 2023-04-07 15:54:06 + * @LastEditTime: 2023-05-04 23:49:11 * @FilePath: /GPT_Translator/src/controller.h */ #ifndef CONTROLLER_H @@ -31,6 +31,7 @@ class Setting : public QObject Q_PROPERTY_AUTO(QString,apiServer); Q_PROPERTY_AUTO(QString,apiKey); Q_PROPERTY_AUTO(QString,model); + Q_PROPERTY_AUTO(QString,shortCut); public: explicit Setting(QObject *parent = nullptr); Q_INVOKABLE bool loadConfig(); diff --git a/src/hotkey.cpp b/src/hotkey.cpp new file mode 100644 index 0000000..ddaae7b --- /dev/null +++ b/src/hotkey.cpp @@ -0,0 +1,126 @@ +/* + * @Date: 2023-05-02 23:52:58 + * @LastEditors: JessGuo + * @LastEditTime: 2023-05-05 22:34:50 + * @FilePath: /GPT_Translator/src/hotkey.cpp + */ +#include "hotkey.h" + +#ifdef Q_OS_MAC +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_LINUX +#include +#include +#include +#endif + + +Hotkey::Hotkey(QObject *parent) + : QObject{parent} +{ + +} + +void Hotkey::binding(QObject *obj) +{ + _hotkey = new QHotkey(QKeySequence(""), true, obj); //The hotkey will be automatically registered + qDebug() << "Is segistered:" << _hotkey->isRegistered(); + + + QObject::connect(_hotkey, &QHotkey::activated, obj, [&,this](){ + qDebug() << "Hotkey Activated "; + +#ifdef Q_OS_MAC + CGEventRef push = CGEventCreateKeyboardEvent(NULL, 0x08, true);//0x08=='c' + CGEventSetFlags(push, kCGEventFlagMaskCommand); + CGEventPost(kCGHIDEventTap, push); + + push = CGEventCreateKeyboardEvent(NULL, 0x08, false);//0x08=='c' + CGEventSetFlags(push, kCGEventFlagMaskCommand); + CGEventPost(kCGHIDEventTap, push); + + CGPoint mouseLocation; + mouseLocation = CGEventGetLocation(CGEventCreate(NULL)); + qDebug() << "x:" << mouseLocation.x; + qDebug() << "y:" << mouseLocation.y; + _mousePos.setX(mouseLocation.x); + _mousePos.setY(mouseLocation.y); +#endif + +#ifdef Q_OS_WIN + // 为按下和释放 Ctrl+C 准备输入事件 + INPUT inputs[4] = { 0 }; + inputs[0].type = inputs[1].type = inputs[2].type = inputs[3].type = INPUT_KEYBOARD; + inputs[0].ki.wVk = inputs[2].ki.wVk = VK_CONTROL; + inputs[1].ki.wVk = inputs[3].ki.wVk = 'C'; + inputs[2].ki.dwFlags = inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; + + // 发送输入事件 + UINT numEventsSent = SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); + if (numEventsSent != ARRAYSIZE(inputs)) { + qDebug() << "SendInput failed: " << GetLastError(); + } +#endif + +#ifdef Q_OS_LINUX + Display *display = XOpenDisplay(NULL); + if (display == NULL) { + qDebug() << "Unable to open X display."; + return; + } + + Window root = DefaultRootWindow(display); + KeyCode ctrl_key = XKeysymToKeycode(display, XK_Control_L); + KeyCode c_key = XKeysymToKeycode(display, XK_c); + + XEvent event; + memset(&event, 0, sizeof(event)); + event.xkey.type = KeyPress; + event.xkey.root = root; + event.xkey.window = root; + event.xkey.same_screen = True; + + // 按下 Ctrl 键 + event.xkey.keycode = ctrl_key; + XSendEvent(display, root, True, KeyPressMask, &event); + XFlush(display); + usleep(10000); + + // 按下 C 键 + event.xkey.keycode = c_key; + XSendEvent(display, root, True, KeyPressMask, &event); + XFlush(display); + usleep(10000); + + // 释放 C 键 + event.xkey.type = KeyRelease; + event.xkey.keycode = c_key; + XSendEvent(display, root, True, KeyReleaseMask, &event); + XFlush(display); + usleep(10000); + + // 释放 Ctrl 键 + event.xkey.keycode = ctrl_key; + XSendEvent(display, root, True, KeyReleaseMask, &event); + XFlush(display); + usleep(10000); + + XCloseDisplay(display); +#endif + // Use a timer to wait for the copy operation to complete + QTimer::singleShot(200, [this] { + QClipboard *clipboard = QGuiApplication::clipboard(); + QString copiedText = clipboard->text(); + qDebug() << "Copied text:" << copiedText; + this->_selectedText = copiedText; + this->selectedTextChanged(); + }); + + }); +} diff --git a/src/hotkey.h b/src/hotkey.h new file mode 100644 index 0000000..d8f1f38 --- /dev/null +++ b/src/hotkey.h @@ -0,0 +1,38 @@ +#ifndef HOTKEY_H +#define HOTKEY_H + +#include + +#include "stdafx.h" + +#include +#include +#include +#include +#include + + + +class Hotkey : public QObject +{ + Q_OBJECT + Q_PROPERTY_AUTO(QString,selectedText); + Q_PROPERTY_AUTO(QPoint,mousePos); + +public: + explicit Hotkey(QObject *parent = nullptr); + + Q_INVOKABLE void binding(QObject * obj); + + Q_INVOKABLE bool setShortcut(QString str){ + return _hotkey->setShortcut(QKeySequence(str), true); + } + + +private: + QHotkey *_hotkey; +signals: + +}; + +#endif // HOTKEY_H diff --git a/src/main.cpp b/src/main.cpp index ab7ea31..4b3b058 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,17 +14,31 @@ #include "updater.h" #include -#include +#include "hotkey.h" + int main(int argc, char *argv[]) { - QString type = QSysInfo::productType(); - if((type != "macos") && (type != "windows")){ + + #ifdef Q_OS_WIN + qDebug() << "Current OS: Windows"; + #endif + + #ifdef Q_OS_MAC + qDebug() << "Current OS: macOS"; + #endif + + #ifdef Q_OS_LINUX + qDebug() << "Current OS: Linux"; + #endif + + #ifdef Q_OS_LINUX qputenv("QT_QUICK_BACKEND","software");//Failed to build graphics pipeline state under linux, need to be software - } + #endif QGuiApplication app(argc, argv); app.setWindowIcon(QIcon("qrc:///res/logo/logo.ico")); + QTranslator translator; const QStringList uiLanguages = QLocale::system().uiLanguages(); for (const QString &locale : uiLanguages) { @@ -42,15 +56,19 @@ int main(int argc, char *argv[]) #endif Setting * setting = new Setting(); - +// Hotkey *key = new Hotkey(); +// key->binding(&app); qmlRegisterType("Controller",1,0,"APIController"); qmlRegisterType("Updater",1,0,"APIUpdater"); + qmlRegisterType("Controller",1,0,"Hotkey"); QQmlApplicationEngine engine; const QUrl url(u"qrc:///qml/main.qml"_qs); engine.rootContext()->setContextProperty("setting", setting); + engine.rootContext()->setContextProperty("app", &app); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl)