From e92449d91fccf82e6672a0dede3230ab2f125b72 Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Wed, 16 Dec 2015 20:01:03 +0000 Subject: [PATCH] Add ActiveQt-based COM interop helper --- CMakeLists.txt | 24 ++++++++++++-- qt/Application.cc | 25 ++++++++------ qt/CMakeLists.txt | 52 ++++++++++++++++++++++++++--- qt/ComInteropHelper.cc | 68 +++++++++++++++++++++++++++++++++++++ qt/ComInteropHelper.h | 37 +++++++++++++++++++++ qt/InteropHelper.cc | 70 +++++++++++++++++++++++++++++++++++++++ qt/InteropHelper.h | 43 ++++++++++++++++++++++++ qt/InteropObject.h | 11 ++++++ qt/transmission-qt.idl | 33 ++++++++++++++++++ qt/transmission-qt.tlb.rc | 6 ++++ 10 files changed, 350 insertions(+), 19 deletions(-) create mode 100644 qt/ComInteropHelper.cc create mode 100644 qt/ComInteropHelper.h create mode 100644 qt/InteropHelper.cc create mode 100644 qt/InteropHelper.h create mode 100644 qt/transmission-qt.idl create mode 100644 qt/transmission-qt.tlb.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index 925229d22..ed4a06c76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,7 +223,8 @@ if(ENABLE_QT) set(QT_TARGETS) if(USE_QT5) - set(QT5_REQUIRED_MODULES Core Gui Widgets Network DBus LinguistTools) + set(QT5_REQUIRED_MODULES Core Gui Widgets Network LinguistTools) + set(QT5_OPTIONAL_MODULES DBus AxContainer AxServer) foreach(M ${QT5_REQUIRED_MODULES}) find_package(Qt5${M} QUIET) if(Qt5${M}_FOUND) @@ -235,9 +236,18 @@ if(ENABLE_QT) break() endif() endforeach() + if(QT_TARGETS) + foreach(M ${QT5_OPTIONAL_MODULES}) + find_package(Qt5${M} QUIET) + if(Qt5${M}_FOUND) + list(APPEND QT_TARGETS Qt5::${M}) + endif() + endforeach() + endif() else() - set(QT4_REQUIRED_MODULES QtCore QtGui QtNetwork QtDBus) - find_package(Qt4 4.6.2 QUIET COMPONENTS ${QT4_REQUIRED_MODULES}) + set(QT4_REQUIRED_MODULES QtCore QtGui QtNetwork) + set(QT4_OPTIONAL_MODULES QtDBus QAxContainer QAxServer) + find_package(Qt4 4.6.2 QUIET COMPONENTS ${QT4_REQUIRED_MODULES} OPTIONAL_COMPONENTS ${QT4_OPTIONAL_MODULES}) foreach(M ${QT4_REQUIRED_MODULES}) string(TOUPPER "${M}" M_UPPER) if(QT_${M_UPPER}_FOUND) @@ -247,6 +257,14 @@ if(ENABLE_QT) break() endif() endforeach() + if (QT_TARGETS) + foreach(M ${QT4_OPTIONAL_MODULES}) + string(TOUPPER "${M}" M_UPPER) + if(QT_${M_UPPER}_FOUND) + list(APPEND QT_TARGETS Qt4::${M}) + endif() + endforeach() + endif() endif() set(QT_FOUND ON) diff --git a/qt/Application.cc b/qt/Application.cc index 51e56dafd..cdde7773b 100644 --- a/qt/Application.cc +++ b/qt/Application.cc @@ -10,9 +10,6 @@ #include #include -#include -#include -#include #include #include #include @@ -20,6 +17,12 @@ #include #include +#ifdef QT_DBUS_LIB + #include + #include + #include +#endif + #include #include #include @@ -27,8 +30,8 @@ #include "AddData.h" #include "Application.h" -#include "DBusInteropHelper.h" #include "Formatter.h" +#include "InteropHelper.h" #include "MainWindow.h" #include "OptionsDialog.h" #include "Prefs.h" @@ -157,7 +160,7 @@ Application::Application (int& argc, char ** argv): // try to delegate the work to an existing copy of Transmission // before starting ourselves... - DBusInteropHelper interopClient; + InteropHelper interopClient; if (interopClient.isConnected ()) { bool delegated = false; @@ -175,11 +178,7 @@ Application::Application (int& argc, char ** argv): default: break; } - if (metainfo.isEmpty ()) - continue; - - const QVariant result = interopClient.addMetainfo (metainfo); - if (result.isValid () && result.toBool ()) + if (!metainfo.isEmpty () && interopClient.addMetainfo (metainfo)) delegated = true; } @@ -294,7 +293,7 @@ Application::Application (int& argc, char ** argv): for (const QString& filename: filenames) addTorrent (filename); - DBusInteropHelper::registerObject (this); + InteropHelper::registerObject (this); } void @@ -542,6 +541,7 @@ Application::raise () bool Application::notifyApp (const QString& title, const QString& body) const { +#ifdef QT_DBUS_LIB const QLatin1String dbusServiceName ("org.freedesktop.Notifications"); const QLatin1String dbusInterfaceName ("org.freedesktop.Notifications"); const QLatin1String dbusPath ("/org/freedesktop/Notifications"); @@ -564,6 +564,7 @@ Application::notifyApp (const QString& title, const QString& body) const if (replyMsg.isValid () && replyMsg.value () > 0) return true; } +#endif myWindow->trayIcon ().showMessage (title, body); return true; @@ -582,6 +583,8 @@ int tr_main (int argc, char * argv[]) { + InteropHelper::initialize (); + Application app (argc, argv); return app.exec (); } diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index 67d4e9b56..56c4a3751 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -22,11 +22,26 @@ else() endmacro() endif() +set(ENABLE_COM_INTEROP OFF) +if(MSVC AND ((Qt5AxContainer_FOUND AND Qt5AxServer_FOUND) OR (QT_QAXCONTAINER_FOUND AND QT_QAXSERVER_FOUND))) + set(ENABLE_COM_INTEROP ON) +endif() + +set(ENABLE_DBUS_INTEROP OFF) +if(Qt5DBus_FOUND OR QT_QTDBUS_FOUND) + set(ENABLE_DBUS_INTEROP ON) +endif() + +if(NOT ENABLE_COM_INTEROP AND NOT ENABLE_DBUS_INTEROP) + message(FATAL_ERROR "Neither D-Bus nor COM interop is possible") +endif() + set(${PROJECT_NAME}_SOURCES AboutDialog.cc AddData.cc Application.cc ColumnResizer.cc + ComInteropHelper.cc DBusInteropHelper.cc DetailsDialog.cc FaviconCache.cc @@ -42,6 +57,7 @@ set(${PROJECT_NAME}_SOURCES Formatter.cc FreeSpaceLabel.cc IconToolButton.cc + InteropHelper.cc InteropObject.cc LicenseDialog.cc MainWindow.cc @@ -69,12 +85,20 @@ set(${PROJECT_NAME}_SOURCES WatchDir.cc ) +if (NOT ENABLE_COM_INTEROP) + set_source_files_properties(ComInteropHelper.cc PROPERTIES HEADER_FILE_ONLY ON) +endif() +if (NOT ENABLE_DBUS_INTEROP) + set_source_files_properties(DBusInteropHelper.cc PROPERTIES HEADER_FILE_ONLY ON) +endif() + set(${PROJECT_NAME}_HEADERS AboutDialog.h AddData.h Application.h BaseDialog.h ColumnResizer.h + ComInteropHelper.h CustomVariantType.h DBusInteropHelper.h DetailsDialog.h @@ -91,6 +115,7 @@ set(${PROJECT_NAME}_HEADERS Formatter.h FreeSpaceLabel.h IconToolButton.h + InteropHelper.h InteropObject.h LicenseDialog.h MainWindow.h @@ -182,17 +207,28 @@ include_directories( ${EVENT2_INCLUDE_DIRS} ) -add_definitions( - "-DTRANSLATIONS_DIR=\"${CMAKE_INSTALL_FULL_DATADIR}/${TR_NAME}/translations\"" - -DQT_NO_CAST_FROM_ASCII -) - tr_win32_app_info(${PROJECT_NAME}_WIN32_RC_FILE "Transmission Qt Client" "transmission-qt" "transmission-qt.exe" "qtr.ico") +if(ENABLE_COM_INTEROP) + find_program(MIDL_EXECUTABLE midl) + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/transmission-qt.tlb + COMMAND + ${MIDL_EXECUTABLE} /tlb ${CMAKE_CURRENT_BINARY_DIR}/transmission-qt.tlb transmission-qt.idl + DEPENDS + transmission-qt.idl + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + ) + list(APPEND ${PROJECT_NAME}_WIN32_RC_FILE transmission-qt.tlb.rc transmission-qt.idl ${CMAKE_CURRENT_BINARY_DIR}/transmission-qt.tlb) + set_source_files_properties(transmission-qt.idl ${CMAKE_CURRENT_BINARY_DIR}/transmission-qt.tlb PROPERTIES HEADER_FILE_ONLY ON) +endif() + add_executable(${TR_NAME}-qt WIN32 ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_UI_SOURCES} @@ -209,6 +245,12 @@ target_link_libraries(${TR_NAME}-qt ${EVENT2_LIBRARIES} ) +target_compile_definitions(${TR_NAME}-qt PRIVATE + "TRANSLATIONS_DIR=\"${CMAKE_INSTALL_FULL_DATADIR}/${TR_NAME}/translations\"" + QT_NO_CAST_FROM_ASCII + $<$:ENABLE_COM_INTEROP> + $<$:ENABLE_DBUS_INTEROP>) + if(MSVC) tr_append_target_property(${TR_NAME}-qt LINK_FLAGS "/ENTRY:mainCRTStartup") endif() diff --git a/qt/ComInteropHelper.cc b/qt/ComInteropHelper.cc new file mode 100644 index 000000000..b27b59ac4 --- /dev/null +++ b/qt/ComInteropHelper.cc @@ -0,0 +1,68 @@ +/* + * This file Copyright (C) 2015 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + * $Id$ + */ + +#include +#include + +#include +#include +#include +#include + +#include "ComInteropHelper.h" +#include "InteropObject.h" + +QAXFACTORY_BEGIN("{1e405fc2-1a3a-468b-8bd6-bfbb58770390}", "{792d1aac-53cc-4dc9-bc29-e5295fdb93a9}") + QAXCLASS(InteropObject) +QAXFACTORY_END() + +// These are ActiveQt internals; declaring here as I don't like their WinMain much... +extern HANDLE qAxInstance; +extern bool qAxOutProcServer; +extern wchar_t qAxModuleFilename[MAX_PATH]; +extern QString qAxInit(); + +ComInteropHelper::ComInteropHelper (): + m_client (new QAxObject (QLatin1String ("Transmission.QtClient"))) +{ +} + +ComInteropHelper::~ComInteropHelper () +{ +} + +bool +ComInteropHelper::isConnected () const +{ + return !m_client->isNull (); +} + +QVariant +ComInteropHelper::addMetainfo (const QString& metainfo) +{ + return m_client->dynamicCall ("AddMetainfo(QString)", metainfo); +} + +void +ComInteropHelper::initialize () +{ + qAxOutProcServer = true; + ::GetModuleFileNameW (0, qAxModuleFilename, MAX_PATH); + qAxInstance = ::GetModuleHandleW (NULL); + + ::CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); + qAxInit (); +} + +void +ComInteropHelper::registerObject (QObject * parent) +{ + QAxFactory::startServer(); + QAxFactory::registerActiveObject(new InteropObject (parent)); +} diff --git a/qt/ComInteropHelper.h b/qt/ComInteropHelper.h new file mode 100644 index 000000000..9f7322b4b --- /dev/null +++ b/qt/ComInteropHelper.h @@ -0,0 +1,37 @@ +/* + * This file Copyright (C) 2015 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + * $Id$ + */ + +#ifndef QTR_COM_INTEROP_HELPER_H +#define QTR_COM_INTEROP_HELPER_H + +#include + +class QAxObject; +class QObject; +class QString; +class QVariant; + +class ComInteropHelper +{ + public: + ComInteropHelper (); + ~ComInteropHelper (); + + bool isConnected () const; + + QVariant addMetainfo (const QString& metainfo); + + static void initialize (); + static void registerObject (QObject * parent); + + private: + std::unique_ptr m_client; +}; + +#endif // QTR_COM_INTEROP_HELPER_H diff --git a/qt/InteropHelper.cc b/qt/InteropHelper.cc new file mode 100644 index 000000000..bf38d6486 --- /dev/null +++ b/qt/InteropHelper.cc @@ -0,0 +1,70 @@ +/* + * This file Copyright (C) 2015 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + * $Id$ + */ + +#include + +#include "InteropHelper.h" + +bool +InteropHelper::isConnected () const +{ +#ifdef ENABLE_DBUS_INTEROP + if (myDbusClient.isConnected ()) + return true; +#endif + +#ifdef ENABLE_COM_INTEROP + if (myComClient.isConnected ()) + return true; +#endif + + return false; +} + +bool +InteropHelper::addMetainfo (const QString& metainfo) +{ +#ifdef ENABLE_DBUS_INTEROP + { + const QVariant response = myDbusClient.addMetainfo (metainfo); + if (response.isValid () && response.toBool ()) + return true; + } +#endif + +#ifdef ENABLE_COM_INTEROP + { + const QVariant response = myComClient.addMetainfo (metainfo); + if (response.isValid () && response.toBool ()) + return true; + } +#endif + + return false; +} + +void +InteropHelper::initialize () +{ +#ifdef ENABLE_COM_INTEROP + ComInteropHelper::initialize (); +#endif +} + +void +InteropHelper::registerObject (QObject * parent) +{ +#ifdef ENABLE_DBUS_INTEROP + DBusInteropHelper::registerObject (parent); +#endif + +#ifdef ENABLE_COM_INTEROP + ComInteropHelper::registerObject (parent); +#endif +} diff --git a/qt/InteropHelper.h b/qt/InteropHelper.h new file mode 100644 index 000000000..a395a808b --- /dev/null +++ b/qt/InteropHelper.h @@ -0,0 +1,43 @@ +/* + * This file Copyright (C) 2015 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + * $Id$ + */ + +#ifndef QTR_INTEROP_HELPER_H +#define QTR_INTEROP_HELPER_H + +#ifdef ENABLE_COM_INTEROP + #include "ComInteropHelper.h" +#endif +#ifdef ENABLE_DBUS_INTEROP + #include "DBusInteropHelper.h" +#endif + +class QAxObject; +class QString; +class QVariant; + +class InteropHelper +{ + public: + bool isConnected () const; + + bool addMetainfo (const QString& metainfo); + + static void initialize (); + static void registerObject (QObject * parent); + + private: +#ifdef ENABLE_DBUS_INTEROP + DBusInteropHelper myDbusClient; +#endif +#ifdef ENABLE_COM_INTEROP + ComInteropHelper myComClient; +#endif +}; + +#endif // QTR_INTEROP_HELPER_H diff --git a/qt/InteropObject.h b/qt/InteropObject.h index 4ae118481..83237fb97 100644 --- a/qt/InteropObject.h +++ b/qt/InteropObject.h @@ -15,7 +15,18 @@ class InteropObject: public QObject { Q_OBJECT + +#ifdef ENABLE_DBUS_INTEROP Q_CLASSINFO ("D-Bus Interface", "com.transmissionbt.Transmission") +#endif + +#ifdef ENABLE_COM_INTEROP + Q_CLASSINFO ("ClassID", "{0e2c952c-0597-491f-ba26-249d7e6fab49}") + Q_CLASSINFO ("InterfaceID", "{9402f54f-4906-4f20-ad73-afcfeb5b228d}") + Q_CLASSINFO ("RegisterObject", "yes") + Q_CLASSINFO ("CoClassAlias", "QtClient") + Q_CLASSINFO ("Description", "Transmission Qt Client Class") +#endif public: InteropObject (QObject * parent = nullptr); diff --git a/qt/transmission-qt.idl b/qt/transmission-qt.idl new file mode 100644 index 000000000..80e1637e6 --- /dev/null +++ b/qt/transmission-qt.idl @@ -0,0 +1,33 @@ +import "ocidl.idl"; + +[ + uuid(1E405FC2-1A3A-468B-8BD6-BFBB58770390), + version(1.0), + helpstring("Transmission Qt Client Type Library 1.0") +] +library TransmissionLib +{ + [ + uuid(9402F54F-4906-4F20-AD73-AFCFEB5B228D), + helpstring("QtClient Interface") + ] + dispinterface IQtClient + { + properties: + + methods: + [id(1)] VARIANT_BOOL PresentWindow(); + [id(2)] VARIANT_BOOL AddMetainfo([in] BSTR p_metainfo); + }; + + [ + aggregatable, + appobject, + helpstring("Transmission Qt Client Class"), + uuid(0E2C952C-0597-491F-BA26-249D7E6FAB49) + ] + coclass QtClient + { + [default] dispinterface IQtClient; + }; +}; diff --git a/qt/transmission-qt.tlb.rc b/qt/transmission-qt.tlb.rc new file mode 100644 index 000000000..07c0d7e78 --- /dev/null +++ b/qt/transmission-qt.tlb.rc @@ -0,0 +1,6 @@ +#include "winresrc.h" + +#pragma code_page(1252) + +LANGUAGE 0, 0 +1 TYPELIB "transmission-qt.tlb"