diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d0ac3ff..4062cfe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,192 +1,195 @@ # CMake version required. This must be the very first line, because it sets default policies affecting everything else cmake_minimum_required(VERSION 3.1) # Project name and version project(Falkon VERSION 3.1.99) # Find ECM, with nice error handling in case of failure include(FeatureSummary) find_package(ECM 5.27.0 CONFIG) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/frameworks/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # Many includes from ECM, to get all the nice cmake functions and settings include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) include(ECMPoQmTools) # Output dirs (like ECM 5.38 does) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") if (UNIX AND NOT APPLE) set(FALKON_INSTALL_PLUGINDIR "${KDE_INSTALL_PLUGINDIR}/falkon") else() set(FALKON_INSTALL_PLUGINDIR "${KDE_INSTALL_PLUGINDIR}") endif() if (IS_ABSOLUTE ${FALKON_INSTALL_PLUGINDIR}) set(PLUGIN_PATH "${FALKON_INSTALL_PLUGINDIR}") else() set(PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/${FALKON_INSTALL_PLUGINDIR}") endif() if (NOT WIN32) set(FALKON_PLUGIN_PATH "${PLUGIN_PATH}" CACHE PATH "Default plugin search path") endif() # Defines that are always set add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_TO_ASCII) # Mandatory: Qt5 set(QT_MIN_VERSION "5.9.0") find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Widgets Network Sql QuickWidgets PrintSupport WebEngine WebEngineWidgets WebChannel) if (BUILD_TESTING) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Test) endif() if (NOT DISABLE_DBUS) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS DBus) endif() if (UNIX AND NOT APPLE AND NOT NO_X11) add_definitions(-DQZ_WS_X11) find_package(XCB REQUIRED COMPONENTS XCB UTIL) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras) endif() if (WIN32) add_definitions(-DW7API) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS WinExtras) # taken from https://stackoverflow.com/a/40217291 macro(get_WIN32_WINNT version) if (CMAKE_SYSTEM_VERSION) set(ver ${CMAKE_SYSTEM_VERSION}) string(REGEX MATCH "^([0-9]+).([0-9])" ver ${ver}) string(REGEX MATCH "^([0-9]+)" verMajor ${ver}) # Check for Windows 10, b/c we'll need to convert to hex 'A'. if ("${verMajor}" MATCHES "10") set(verMajor "A") string(REGEX REPLACE "^([0-9]+)" ${verMajor} ver ${ver}) endif ("${verMajor}" MATCHES "10") # Remove all remaining '.' characters. string(REPLACE "." "" ver ${ver}) # Prepend each digit with a zero. string(REGEX REPLACE "([0-9A-Z])" "0\\1" ver ${ver}) set(${version} "0x${ver}") endif(CMAKE_SYSTEM_VERSION) endmacro(get_WIN32_WINNT) get_WIN32_WINNT(ver) add_definitions(-D_WIN32_WINNT=${ver}) endif() # Mandatory: OpenSSL find_package(OpenSSL REQUIRED) +# Mandatory: KF5 +find_package(KF5 REQUIRED COMPONENTS Archive) + # KF5I18n: Mandatory with downloaded translations (only for ki18n_install) if (EXISTS "${CMAKE_SOURCE_DIR}/po") find_package(KF5I18n REQUIRED) endif() # Optional: GnomeKeyring find_package(PkgConfig) if (PKG_CONFIG_FOUND) option(BUILD_KEYRING "Gnome keyring password plugin" ON) if (BUILD_KEYRING) pkg_check_modules(GNOME_KEYRING IMPORTED_TARGET gnome-keyring-1 ) endif() endif() # Optional: KWallet, KIO, KCrash, KCoreAddons set(KF5_MIN_VERSION "5.54.0") find_package(KF5Wallet ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Wallet PROPERTIES DESCRIPTION "KDE Frameworks Integration plugin" TYPE OPTIONAL) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE Frameworks Integration plugin" TYPE OPTIONAL) find_package(KF5Crash ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE Frameworks Integration plugin" TYPE OPTIONAL) find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "KDE Frameworks Integration plugin" TYPE OPTIONAL) find_package(KF5Purpose ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Purpose PROPERTIES DESCRIPTION "KDE Frameworks Integration plugin" TYPE OPTIONAL) if (KF5Wallet_FOUND AND KF5KIO_FOUND AND KF5Crash_FOUND AND KF5CoreAddons_FOUND AND KF5Purpose_FOUND) set(ENABLE_KDE_FRAMEWORKS_INTEGRATION_PLUGIN TRUE) endif() # Optional: PySide2 find_package(PySide2 "2.0.0") find_package(Shiboken2 "2.0.0") find_package(Python3 COMPONENTS Development) set_package_properties(PySide2 PROPERTIES DESCRIPTION "Python plugins" TYPE OPTIONAL) set_package_properties(Shiboken2 PROPERTIES DESCRIPTION "Python plugins" TYPE OPTIONAL) set_package_properties(Python3 PROPERTIES DESCRIPTION "Python plugins" TYPE OPTIONAL) if (PySide2_FOUND AND Shiboken2_FOUND AND Python3_FOUND) set(ENABLE_PYTHON_PLUGINS TRUE) endif() find_package(Intl) if (Intl_FOUND) set(HAVE_LIBINTL TRUE) endif() # Git revision if (EXISTS "${CMAKE_SOURCE_DIR}/.git") find_package(Git QUIET) if(GIT_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_REVISION ) string(REGEX REPLACE "\n" "" GIT_REVISION "${GIT_REVISION}") set(FALKON_GIT_REVISION "${GIT_REVISION}") else() message(STATUS "Git revision could not be determined") endif() endif() configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/config.h) # Include dirs used everywhere include_directories( ${CMAKE_SOURCE_DIR}/src/lib/3rdparty ${CMAKE_SOURCE_DIR}/src/lib/adblock ${CMAKE_SOURCE_DIR}/src/lib/app ${CMAKE_SOURCE_DIR}/src/lib/autofill ${CMAKE_SOURCE_DIR}/src/lib/bookmarks ${CMAKE_SOURCE_DIR}/src/lib/cookies ${CMAKE_SOURCE_DIR}/src/lib/downloads ${CMAKE_SOURCE_DIR}/src/lib/history ${CMAKE_SOURCE_DIR}/src/lib/navigation ${CMAKE_SOURCE_DIR}/src/lib/network ${CMAKE_SOURCE_DIR}/src/lib/notifications ${CMAKE_SOURCE_DIR}/src/lib/opensearch ${CMAKE_SOURCE_DIR}/src/lib/other ${CMAKE_SOURCE_DIR}/src/lib/plugins ${CMAKE_SOURCE_DIR}/src/lib/popupwindow ${CMAKE_SOURCE_DIR}/src/lib/preferences ${CMAKE_SOURCE_DIR}/src/lib/session ${CMAKE_SOURCE_DIR}/src/lib/sidebar ${CMAKE_SOURCE_DIR}/src/lib/tabwidget ${CMAKE_SOURCE_DIR}/src/lib/tools ${CMAKE_SOURCE_DIR}/src/lib/webengine ${CMAKE_SOURCE_DIR}/src/lib/webtab ) # Finally, go into the subdirs add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests/benchmarks) endif() # Tell releaseme that po is already taken care of # SKIP_PO_INSTALL feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index b4aff598..74775e35 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,373 +1,375 @@ add_definitions(-DFALKON_SHAREDLIBRARY) set(CMAKE_CXX_STANDARD 14) # Enable C++14, with cmake >= 3.1 set(CMAKE_CXX_EXTENSIONS OFF) # Don't enable gcc-specific extensions set(SRCS 3rdparty/qtsingleapplication/qtsingleapplication.cpp 3rdparty/qtsingleapplication/qtlocalpeer.cpp ) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(SRCS ${SRCS} ${CMAKE_SOURCE_DIR}/tests/modeltest/modeltest.cpp) include_directories(${CMAKE_SOURCE_DIR}/tests/modeltest) endif() include_directories( 3rdparty adblock app autofill bookmarks cookies downloads history navigation network notifications opensearch other plugins popupwindow preferences session sidebar tabwidget tools webengine webtab ) include_directories(${OPENSSL_INCLUDE_DIR}) set(SRCS ${SRCS} 3rdparty/fancytabwidget.cpp 3rdparty/lineedit.cpp 3rdparty/processinfo.cpp 3rdparty/squeezelabelv1.cpp 3rdparty/squeezelabelv2.cpp 3rdparty/stylehelper.cpp adblock/adblockaddsubscriptiondialog.cpp adblock/adblockurlinterceptor.cpp adblock/adblockdialog.cpp adblock/adblockicon.cpp adblock/adblockmanager.cpp adblock/adblockmatcher.cpp adblock/adblockrule.cpp adblock/adblocksearchtree.cpp adblock/adblocksubscription.cpp adblock/adblocktreewidget.cpp adblock/adblockplugin.cpp app/autosaver.cpp app/browserwindow.cpp app/commandlineoptions.cpp app/datapaths.cpp app/mainapplication.cpp app/mainmenu.cpp app/profilemanager.cpp app/proxystyle.cpp app/qzcommon.cpp app/settings.cpp autofill/autofill.cpp autofill/autofillicon.cpp autofill/autofillnotification.cpp autofill/autofillwidget.cpp autofill/passwordbackends/databaseencryptedpasswordbackend.cpp autofill/passwordbackends/databasepasswordbackend.cpp autofill/passwordbackends/passwordbackend.cpp autofill/passwordmanager.cpp bookmarks/bookmarkitem.cpp bookmarks/bookmarks.cpp bookmarks/bookmarksexport/bookmarksexportdialog.cpp bookmarks/bookmarksexport/bookmarksexporter.cpp bookmarks/bookmarksexport/htmlexporter.cpp bookmarks/bookmarksicon.cpp bookmarks/bookmarksimport/bookmarksimportdialog.cpp bookmarks/bookmarksimport/bookmarksimporter.cpp bookmarks/bookmarksimport/firefoximporter.cpp bookmarks/bookmarksimport/htmlimporter.cpp bookmarks/bookmarksimport/chromeimporter.cpp bookmarks/bookmarksimport/ieimporter.cpp bookmarks/bookmarksimport/operaimporter.cpp bookmarks/bookmarksitemdelegate.cpp bookmarks/bookmarksmanager.cpp bookmarks/bookmarksmenu.cpp bookmarks/bookmarksmodel.cpp bookmarks/bookmarkstoolbarbutton.cpp bookmarks/bookmarkstoolbar.cpp bookmarks/bookmarkstools.cpp bookmarks/bookmarkstreeview.cpp bookmarks/bookmarkswidget.cpp cookies/cookiejar.cpp cookies/cookiemanager.cpp downloads/downloaditem.cpp downloads/downloadmanager.cpp downloads/downloadoptionsdialog.cpp downloads/downloadsbutton.cpp history/history.cpp history/historyitem.cpp history/historymanager.cpp history/historymenu.cpp history/historymodel.cpp history/historytreeview.cpp navigation/completer/locationcompleter.cpp navigation/completer/locationcompleterdelegate.cpp navigation/completer/locationcompletermodel.cpp navigation/completer/locationcompleterrefreshjob.cpp navigation/completer/locationcompleterview.cpp navigation/downicon.cpp navigation/goicon.cpp navigation/locationbar.cpp navigation/locationbarpopup.cpp navigation/navigationbar.cpp navigation/navigationbartoolbutton.cpp navigation/navigationbarconfigdialog.cpp navigation/navigationcontainer.cpp navigation/reloadstopbutton.cpp navigation/siteicon.cpp navigation/websearchbar.cpp network/networkmanager.cpp network/networkurlinterceptor.cpp network/schemehandlers/extensionschemehandler.cpp network/schemehandlers/falkonschemehandler.cpp network/sslerrordialog.cpp notifications/desktopnotification.cpp notifications/desktopnotificationsfactory.cpp opensearch/editsearchengine.cpp opensearch/opensearchengine.cpp opensearch/opensearchenginedelegate.cpp opensearch/opensearchreader.cpp opensearch/searchenginesdialog.cpp opensearch/searchenginesmanager.cpp other/aboutdialog.cpp other/browsinglibrary.cpp other/clearprivatedata.cpp other/checkboxdialog.cpp other/iconchooser.cpp other/licenseviewer.cpp other/qzsettings.cpp other/siteinfo.cpp other/siteinfowidget.cpp other/statusbar.cpp other/updater.cpp other/useragentmanager.cpp other/protocolhandlerdialog.cpp other/protocolhandlermanager.cpp plugins/pluginproxy.cpp plugins/plugins.cpp plugins/speeddial.cpp + plugins/ocssupport.cpp plugins/qml/qmlpluginloader.cpp plugins/qml/qmlplugin.cpp plugins/qml/qmlplugins.cpp plugins/qml/qmlplugininterface.cpp plugins/qml/qmlengine.cpp plugins/qml/qmlstaticdata.cpp plugins/qml/api/bookmarks/qmlbookmarktreenode.cpp plugins/qml/api/bookmarks/qmlbookmarks.cpp plugins/qml/api/topsites/qmlmostvisitedurl.cpp plugins/qml/api/topsites/qmltopsites.cpp plugins/qml/api/history/qmlhistoryitem.cpp plugins/qml/api/history/qmlhistory.cpp plugins/qml/api/cookies/qmlcookie.cpp plugins/qml/api/cookies/qmlcookies.cpp plugins/qml/api/tabs/qmltab.cpp plugins/qml/api/tabs/qmltabs.cpp plugins/qml/api/notifications/qmlnotifications.cpp plugins/qml/api/clipboard/qmlclipboard.cpp plugins/qml/api/windows/qmlwindow.cpp plugins/qml/api/windows/qmlwindows.cpp plugins/qml/api/browseraction/qmlbrowseraction.cpp plugins/qml/api/sidebar/qmlsidebar.cpp plugins/qml/api/menus/qmlmenu.cpp plugins/qml/api/menus/qmlaction.cpp plugins/qml/api/menus/qmlwebhittestresult.cpp plugins/qml/api/settings/qmlsettings.cpp plugins/qml/api/events/qmlqzobjects.cpp plugins/qml/api/events/qmlmouseevent.cpp plugins/qml/api/events/qmlwheelevent.cpp plugins/qml/api/events/qmlkeyevent.cpp plugins/qml/api/userscript/qmluserscript.cpp plugins/qml/api/userscript/qmluserscripts.cpp plugins/qml/api/userscript/qmlexternaljsobject.cpp plugins/qml/api/extensionscheme/qmlextensionscheme.cpp plugins/qml/api/extensionscheme/qmlwebengineurlrequestjob.cpp plugins/qml/api/fileutils/qmlfileutils.cpp plugins/qml/api/qmlenums.cpp popupwindow/popuplocationbar.cpp popupwindow/popupstatusbarmessage.cpp popupwindow/popupwebview.cpp popupwindow/popupwindow.cpp preferences/acceptlanguage.cpp preferences/autofillmanager.cpp preferences/jsoptions.cpp preferences/pluginlistdelegate.cpp preferences/pluginsmanager.cpp preferences/preferences.cpp preferences/thememanager.cpp preferences/useragentdialog.cpp session/recoveryjsobject.cpp session/restoremanager.cpp session/sessionmanager.cpp session/sessionmanagerdialog.cpp sidebar/bookmarkssidebar.cpp sidebar/historysidebar.cpp sidebar/sidebar.cpp tabwidget/combotabbar.cpp tabwidget/tabbar.cpp tabwidget/tabicon.cpp tabwidget/tabmodel.cpp tabwidget/tabmrumodel.cpp tabwidget/tabtreemodel.cpp tabwidget/tabstackedwidget.cpp tabwidget/tabwidget.cpp tabwidget/tabcontextmenu.cpp tools/abstractbuttoninterface.cpp tools/aesinterface.cpp tools/animatedwidget.cpp tools/buttonwithmenu.cpp tools/certificateinfowidget.cpp tools/clickablelabel.cpp tools/closedtabsmanager.cpp tools/closedwindowsmanager.cpp tools/colors.cpp tools/delayedfilewatcher.cpp tools/desktopfile.cpp tools/docktitlebarwidget.cpp tools/enhancedmenu.cpp tools/focusselectlineedit.cpp tools/headerview.cpp tools/horizontallistwidget.cpp tools/html5permissions/html5permissionsdialog.cpp tools/html5permissions/html5permissionsmanager.cpp tools/html5permissions/html5permissionsnotification.cpp tools/iconprovider.cpp tools/listitemdelegate.cpp tools/mactoolbutton.cpp tools/menubar.cpp tools/pagethumbnailer.cpp tools/progressbar.cpp tools/qztools.cpp tools/removeitemfocusdelegate.cpp tools/scripts.cpp tools/sqldatabase.cpp tools/toolbutton.cpp tools/treewidget.cpp tools/wheelhelper.cpp webengine/javascript/autofilljsobject.cpp webengine/javascript/externaljsobject.cpp webengine/loadrequest.cpp webengine/webhittestresult.cpp webengine/webinspector.cpp webengine/webpage.cpp webengine/webview.cpp webengine/webscrollbar.cpp webengine/webscrollbarmanager.cpp webtab/searchtoolbar.cpp webtab/tabbedwebview.cpp webtab/webtab.cpp ) if (HAVE_LIBINTL) set(SRCS ${SRCS} plugins/qml/api/i18n/qmli18n.cpp) endif() if (WIN32) set(SRCS ${SRCS} other/registerqappassociation.cpp) endif() if (APPLE) set(SRCS ${SRCS} tools/disablewindowtabbbing.mm) endif() # TODO: use ki18n_wrap_ui? qt5_wrap_ui(SRCS adblock/adblockaddsubscriptiondialog.ui adblock/adblockdialog.ui autofill/autofillnotification.ui autofill/autofillwidget.ui autofill/passwordbackends/masterpassworddialog.ui bookmarks/bookmarksexport/bookmarksexportdialog.ui bookmarks/bookmarksimport/bookmarksimportdialog.ui bookmarks/bookmarksmanager.ui bookmarks/bookmarkswidget.ui cookies/cookiemanager.ui downloads/downloaditem.ui downloads/downloadmanager.ui downloads/downloadoptionsdialog.ui history/historymanager.ui navigation/navigationbarconfigdialog.ui network/sslerrordialog.ui notifications/desktopnotification.ui opensearch/editsearchengine.ui opensearch/searchenginesdialog.ui other/aboutdialog.ui other/browsinglibrary.ui other/clearprivatedata.ui other/iconchooser.ui other/protocolhandlerdialog.ui other/siteinfo.ui other/siteinfowidget.ui preferences/acceptlanguage.ui preferences/addacceptlanguage.ui preferences/autofillmanager.ui preferences/jsoptions.ui preferences/pluginslist.ui preferences/preferences.ui preferences/thememanager.ui preferences/useragentdialog.ui session/sessionmanagerdialog.ui sidebar/bookmarkssidebar.ui sidebar/historysidebar.ui tools/certificateinfowidget.ui tools/docktitlebarwidget.ui tools/html5permissions/html5permissionsdialog.ui tools/html5permissions/html5permissionsnotification.ui webengine/jsalert.ui webengine/jsconfirm.ui webengine/jsprompt.ui webtab/searchtoolbar.ui ) qt5_add_resources(SRCS data/data.qrc data/html.qrc data/icons.qrc data/breeze-fallback.qrc adblock/adblock.qrc ) add_library(FalkonPrivate SHARED ${SRCS}) get_property(QT_WEBENGINE_INCLUDE_DIRS TARGET Qt5::WebEngine PROPERTY INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(FalkonPrivate SYSTEM PUBLIC ${QT_WEBENGINE_INCLUDE_DIRS}) target_link_libraries(FalkonPrivate Qt5::Widgets Qt5::WebEngineWidgets Qt5::Network Qt5::Sql Qt5::PrintSupport Qt5::QuickWidgets Qt5::WebChannel + KF5::Archive ${OPENSSL_CRYPTO_LIBRARY} ) if (UNIX AND NOT APPLE) if (NOT NO_X11) target_link_libraries(FalkonPrivate XCB::XCB Qt5::X11Extras) endif() set_target_properties(FalkonPrivate PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION "3") install(TARGETS FalkonPrivate ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) endif() if (WIN32) target_link_libraries(FalkonPrivate Qt5::WinExtras) endif() if (APPLE) target_link_libraries(FalkonPrivate "-framework CoreServices -framework AppKit") endif() if (NOT DISABLE_DBUS) target_link_libraries(FalkonPrivate Qt5::DBus) endif() diff --git a/src/lib/notifications/desktopnotificationsfactory.cpp b/src/lib/notifications/desktopnotificationsfactory.cpp index 1dc5887f..66db67ac 100644 --- a/src/lib/notifications/desktopnotificationsfactory.cpp +++ b/src/lib/notifications/desktopnotificationsfactory.cpp @@ -1,131 +1,136 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 3 of the License, or * (at your option) any later version. * * 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 "desktopnotificationsfactory.h" #include "desktopnotification.h" #include "datapaths.h" #include "settings.h" #include "mainapplication.h" #include "browserwindow.h" #include "../config.h" #include #include #if defined(Q_OS_UNIX) && !defined(DISABLE_DBUS) #include #endif DesktopNotificationsFactory::DesktopNotificationsFactory(QObject* parent) : QObject(parent) , m_uint(0) { loadSettings(); } void DesktopNotificationsFactory::loadSettings() { Settings settings; settings.beginGroup("Notifications"); m_enabled = settings.value("Enabled", true).toBool(); m_timeout = settings.value("Timeout", 6000).toInt(); #if defined(Q_OS_UNIX) && !defined(DISABLE_DBUS) m_notifType = settings.value("UseNativeDesktop", true).toBool() ? DesktopNative : PopupWidget; #else m_notifType = PopupWidget; #endif m_position = settings.value("Position", QPoint(10, 10)).toPoint(); settings.endGroup(); } bool DesktopNotificationsFactory::supportsNativeNotifications() const { #if defined(Q_OS_UNIX) && !defined(DISABLE_DBUS) return true; #else return false; #endif } +void DesktopNotificationsFactory::showNotification(const QString &heading, const QString &text) +{ + showNotification(QPixmap(), heading, text); +} + void DesktopNotificationsFactory::showNotification(const QPixmap &icon, const QString &heading, const QString &text) { if (!m_enabled) { return; } switch (m_notifType) { case PopupWidget: if (!m_desktopNotif) { m_desktopNotif = new DesktopNotification(); } m_desktopNotif.data()->setPixmap(icon); m_desktopNotif.data()->setHeading(heading); m_desktopNotif.data()->setText(text); m_desktopNotif.data()->setTimeout(m_timeout); m_desktopNotif.data()->move(m_position); m_desktopNotif.data()->show(); break; case DesktopNative: #if defined(Q_OS_UNIX) && !defined(DISABLE_DBUS) QFile tmp(DataPaths::path(DataPaths::Temp) + QLatin1String("/falkon_notif.png")); tmp.open(QFile::WriteOnly); icon.save(tmp.fileName()); QDBusInterface dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()); QVariantList args; args.append(QLatin1String("falkon")); args.append(m_uint); args.append(tmp.fileName()); args.append(heading); args.append(text); args.append(QStringList()); args.append(QVariantMap()); args.append(m_timeout); dbus.callWithCallback("Notify", args, this, SLOT(updateLastId(QDBusMessage)), SLOT(error(QDBusError))); #endif break; } } void DesktopNotificationsFactory::nativeNotificationPreview() { Type type = m_notifType; m_notifType = DesktopNative; const QPixmap icon = mApp->getWindow()->windowIcon().pixmap(64); showNotification(icon, QObject::tr("Native System Notification"), tr("Preview")); m_notifType = type; } void DesktopNotificationsFactory::updateLastId(const QDBusMessage &msg) { Q_UNUSED(msg) #if defined(Q_OS_UNIX) && !defined(DISABLE_DBUS) QVariantList list = msg.arguments(); if (list.count() > 0) { m_uint = list.at(0).toInt(); } #endif } void DesktopNotificationsFactory::error(const QDBusError &error) { Q_UNUSED(error) #if defined(Q_OS_UNIX) && !defined(DISABLE_DBUS) qWarning() << "QDBusError:" << error.message(); #endif } diff --git a/src/lib/notifications/desktopnotificationsfactory.h b/src/lib/notifications/desktopnotificationsfactory.h index b37ee7d7..64c3d6a0 100644 --- a/src/lib/notifications/desktopnotificationsfactory.h +++ b/src/lib/notifications/desktopnotificationsfactory.h @@ -1,63 +1,64 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2014 David Rosca * * 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 3 of the License, or * (at your option) any later version. * * 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 DESKTOPNOTIFICATIONSFACTORY_H #define DESKTOPNOTIFICATIONSFACTORY_H #include "qzcommon.h" #include #include #include class QPixmap; class QDBusMessage; class QDBusError; class DesktopNotification; class FALKON_EXPORT DesktopNotificationsFactory : public QObject { Q_OBJECT public: enum Type { DesktopNative, PopupWidget }; explicit DesktopNotificationsFactory(QObject* parent = 0); void loadSettings(); bool supportsNativeNotifications() const; + void showNotification(const QString &heading, const QString &text); void showNotification(const QPixmap &icon, const QString &heading, const QString &text); void nativeNotificationPreview(); private Q_SLOTS: void updateLastId(const QDBusMessage &msg); void error(const QDBusError &error); private: bool m_enabled; int m_timeout; Type m_notifType; QPoint m_position; QPointer m_desktopNotif; quint32 m_uint; }; #endif // DESKTOPNOTIFICATIONSFACTORY_H diff --git a/src/lib/plugins/ocssupport.cpp b/src/lib/plugins/ocssupport.cpp new file mode 100644 index 00000000..138c15f1 --- /dev/null +++ b/src/lib/plugins/ocssupport.cpp @@ -0,0 +1,206 @@ +/* ============================================================ +* Falkon - Qt web browser +* Copyright (C) 2019 David Rosca +* +* 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 3 of the License, or +* (at your option) any later version. +* +* 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 "ocssupport.h" +#include "pluginproxy.h" +#include "datapaths.h" +#include "networkmanager.h" +#include "desktopnotificationsfactory.h" +#include "mainapplication.h" + +#include +#include +#include +#include + +#include + +Q_GLOBAL_STATIC(OcsSupport, qz_ocs_support) + +OcsSupport::OcsSupport(QObject *parent) + : QObject(parent) +{ +} + +bool OcsSupport::handleUrl(const QUrl &url) +{ + if (url.host() != QL1S("install")) { + return false; + } + + QUrl fileUrl; + QString fileType; + QString fileName; + + const auto items = QUrlQuery(url).queryItems(QUrl::FullyDecoded); + for (const auto &item : items) { + if (item.first == QL1S("url")) { + fileUrl = QUrl(item.second); + } else if (item.first == QL1S("type")) { + fileType = item.second; + } else if (item.first == QL1S("filename")) { + fileName = item.second; + } + } + + if (!fileType.startsWith(QL1S("falkon_"))) { + return false; + } + + if (fileType != QL1S("falkon_themes") && fileType != QL1S("falkon_extensions")) { + qWarning() << "Unsupported type" << fileType; + return false; + } + + if (!fileUrl.isValid()) { + qWarning() << "Invalid url" << fileUrl << url; + return false; + } + + qInfo() << "Downloading" << fileUrl; + + QNetworkReply *reply = mApp->networkManager()->get(QNetworkRequest(fileUrl)); + connect(reply, &QNetworkReply::finished, this, [=]() { + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "Error downloading" << fileUrl << reply->error() << reply->errorString(); + return; + } + QBuffer buf; + buf.setData(reply->readAll()); + KZip zip(&buf); + if (!zip.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open archive"; + return; + } + QString notifyMessage; + if (fileType == QL1S("falkon_themes")) { + installTheme(zip.directory()); + } else if (fileType == QL1S("falkon_extensions")) { + installExtension(zip.directory()); + } + }); + + return true; +} + +// static +OcsSupport *OcsSupport::instance() +{ + return qz_ocs_support(); +} + +void OcsSupport::installTheme(const KArchiveDirectory *directory) +{ + auto showError = []() { + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Failed to install theme")); + }; + + if (directory->entries().size() != 1) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + const QString name = directory->entries().at(0); + const KArchiveEntry *entry = directory->entry(name); + if (!entry || !entry->isDirectory()) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/themes"); + QDir().mkpath(targetDir); + + if (QFileInfo::exists(targetDir + QL1C('/') + name)) { + qWarning() << "Theme" << name << "already exists"; + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Theme is already installed")); + return; + } + + if (!directory->copyTo(targetDir)) { + qWarning() << "Failed to copy theme to" << targetDir; + showError(); + return; + } + + qInfo() << "Theme installed to" << targetDir; + + mApp->desktopNotifications()->showNotification(tr("Theme installed"), tr("Theme was successfully installed")); +} + +void OcsSupport::installExtension(const KArchiveDirectory *directory) +{ + auto showError = []() { + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Failed to install extension")); + }; + + if (directory->entries().size() != 1) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + const QString name = directory->entries().at(0); + const KArchiveEntry *entry = directory->entry(name); + if (!entry || !entry->isDirectory()) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + QString type; + const QStringList files = static_cast(entry)->entries(); + if (files.contains(QL1S("__init__.py"))) { + type = QSL("python"); + } else if (files.contains(QL1S("main.qml"))) { + type = QSL("qml"); + } + + if (type.isEmpty()) { + qWarning() << "Unsupported extension type"; + showError(); + return; + } + + const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/plugins/") + type; + QDir().mkpath(targetDir); + + if (QFileInfo::exists(targetDir + QL1S("/") + name)) { + qWarning() << "Extension" << name << "already exists"; + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Extension is already installed")); + return; + } + + if (!directory->copyTo(targetDir)) { + qWarning() << "Failed to copy extension to" << targetDir; + showError(); + return; + } + + qInfo() << "Extension installed to" << targetDir; + + const QString fullId = QSL("%1:%2/%3").arg(type, targetDir, name); + if (!mApp->plugins()->addPlugin(fullId)) { + qWarning() << "Failed to add plugin" << fullId; + showError(); + return; + } + + mApp->desktopNotifications()->showNotification(tr("Extension installed"), tr("Extension was successfully installed")); +} diff --git a/src/lib/plugins/ocssupport.h b/src/lib/plugins/ocssupport.h new file mode 100644 index 00000000..a91e9ab7 --- /dev/null +++ b/src/lib/plugins/ocssupport.h @@ -0,0 +1,40 @@ +/* ============================================================ +* Falkon - Qt web browser +* Copyright (C) 2019 David Rosca +* +* 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 3 of the License, or +* (at your option) any later version. +* +* 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 . +* ============================================================ */ +#pragma once + +#include "qzcommon.h" + +#include + +class KArchiveDirectory; + +class OcsSupport : public QObject +{ + Q_OBJECT + +public: + explicit OcsSupport(QObject *parent = nullptr); + + bool handleUrl(const QUrl &url); + + static OcsSupport *instance(); + +private: + void installTheme(const KArchiveDirectory *directory); + void installExtension(const KArchiveDirectory *directory); +}; diff --git a/src/lib/plugins/plugins.cpp b/src/lib/plugins/plugins.cpp index 39be4a0d..77aa86be 100644 --- a/src/lib/plugins/plugins.cpp +++ b/src/lib/plugins/plugins.cpp @@ -1,491 +1,505 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 3 of the License, or * (at your option) any later version. * * 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 "pluginproxy.h" #include "plugininterface.h" #include "mainapplication.h" #include "speeddial.h" #include "settings.h" #include "datapaths.h" #include "adblock/adblockplugin.h" #include "../config.h" #include "desktopfile.h" #include "qml/qmlplugins.h" #include "qml/qmlplugin.h" #include #include #include #include #include #include bool Plugins::Plugin::isLoaded() const { return instance; } bool Plugins::Plugin::isRemovable() const { return !pluginPath.isEmpty() && QFileInfo(pluginPath).isWritable(); } bool Plugins::Plugin::operator==(const Plugin &other) const { - return type == other.type && - pluginId == other.pluginId; + return type == other.type && pluginId == other.pluginId; } Plugins::Plugins(QObject* parent) : QObject(parent) , m_pluginsLoaded(false) , m_speedDial(new SpeedDial(this)) { loadSettings(); if (!MainApplication::isTestModeEnabled()) { loadPythonSupport(); } } QList Plugins::availablePlugins() { loadAvailablePlugins(); return m_availablePlugins; } bool Plugins::loadPlugin(Plugins::Plugin* plugin) { if (plugin->isLoaded()) { return true; } if (!initPlugin(PluginInterface::LateInitState, plugin)) { return false; } m_availablePlugins.removeOne(*plugin); m_availablePlugins.prepend(*plugin); refreshLoadedPlugins(); return plugin->isLoaded(); } void Plugins::unloadPlugin(Plugins::Plugin* plugin) { if (!plugin->isLoaded()) { return; } plugin->instance->unload(); emit pluginUnloaded(plugin->instance); plugin->instance = nullptr; m_availablePlugins.removeOne(*plugin); m_availablePlugins.append(*plugin); refreshLoadedPlugins(); } void Plugins::removePlugin(Plugins::Plugin *plugin) { if (!plugin->isRemovable()) { return; } if (plugin->isLoaded()) { unloadPlugin(plugin); } bool result = false; QFileInfo info(plugin->pluginPath); if (info.isDir()) { result = QDir(plugin->pluginPath).removeRecursively(); } else if (info.isFile()) { result = QFile::remove(plugin->pluginPath); } if (!result) { qWarning() << "Failed to remove" << plugin->pluginSpec.name; return; } m_availablePlugins.removeOne(*plugin); emit availablePluginsChanged(); } +bool Plugins::addPlugin(const QString &id) +{ + Plugin plugin = loadPlugin(id); + if (plugin.type == Plugin::Invalid) { + return false; + } + if (plugin.pluginSpec.name.isEmpty()) { + qWarning() << "Invalid plugin spec of" << id << "plugin"; + return false; + } + registerAvailablePlugin(plugin); + emit availablePluginsChanged(); + return true; +} + void Plugins::loadSettings() { QStringList defaultAllowedPlugins = { QSL("internal:adblock") }; // Enable KDE Frameworks Integration when running inside KDE session if (qgetenv("KDE_FULL_SESSION") == QByteArray("true")) { defaultAllowedPlugins.append(QSL("lib:KDEFrameworksIntegration.so")); } Settings settings; settings.beginGroup("Plugin-Settings"); m_allowedPlugins = settings.value("AllowedPlugins", defaultAllowedPlugins).toStringList(); settings.endGroup(); } void Plugins::shutdown() { foreach (PluginInterface* iPlugin, m_loadedPlugins) { iPlugin->unload(); } } PluginSpec Plugins::createSpec(const DesktopFile &metaData) { PluginSpec spec; spec.name = metaData.name(); spec.description = metaData.comment(); spec.version = metaData.value(QSL("X-Falkon-Version")).toString(); spec.author = QSL("%1 <%2>").arg(metaData.value(QSL("X-Falkon-Author")).toString(), metaData.value(QSL("X-Falkon-Email")).toString()); spec.hasSettings = metaData.value(QSL("X-Falkon-Settings")).toBool(); const QString iconName = metaData.icon(); if (!iconName.isEmpty()) { if (QFileInfo::exists(iconName)) { spec.icon = QIcon(iconName).pixmap(32); } else { const QString relativeFile = QFileInfo(metaData.fileName()).dir().absoluteFilePath(iconName); if (QFileInfo::exists(relativeFile)) { spec.icon = QIcon(relativeFile).pixmap(32); } else { spec.icon = QIcon::fromTheme(iconName).pixmap(32); } } } return spec; } void Plugins::loadPlugins() { QDir settingsDir(DataPaths::currentProfilePath() + "/extensions/"); if (!settingsDir.exists()) { settingsDir.mkdir(settingsDir.absolutePath()); } foreach (const QString &pluginId, m_allowedPlugins) { Plugin plugin = loadPlugin(pluginId); if (plugin.type == Plugin::Invalid) { continue; } if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << pluginId << "plugin"; continue; } if (!initPlugin(PluginInterface::StartupInitState, &plugin)) { qWarning() << "Failed to init" << pluginId << "plugin"; continue; } registerAvailablePlugin(plugin); } refreshLoadedPlugins(); std::cout << "Falkon: " << m_loadedPlugins.count() << " extensions loaded" << std::endl; } void Plugins::loadAvailablePlugins() { if (m_pluginsLoaded) { return; } m_pluginsLoaded = true; const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins); // InternalPlugin registerAvailablePlugin(loadInternalPlugin(QSL("adblock"))); // SharedLibraryPlugin for (const QString &dir : dirs) { const auto files = QDir(dir).entryInfoList(QDir::Files); for (const QFileInfo &info : files) { if (info.baseName() == QL1S("PyFalkon")) { continue; } Plugin plugin = loadSharedLibraryPlugin(info.absoluteFilePath()); if (plugin.type == Plugin::Invalid) { continue; } if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin"; continue; } registerAvailablePlugin(plugin); } } // PythonPlugin if (m_pythonPlugin) { auto f = (QVector(*)()) m_pythonPlugin->resolve("pyfalkon_load_available_plugins"); if (!f) { qWarning() << "Failed to resolve" << "pyfalkon_load_available_plugins"; } else { const auto plugins = f(); for (const auto &plugin : plugins) { registerAvailablePlugin(plugin); } } } // QmlPlugin for (QString dir : dirs) { // Qml plugins will be loaded from subdirectory qml dir.append(QSL("/qml")); const auto qmlDirs = QDir(dir).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &info : qmlDirs) { Plugin plugin = QmlPlugin::loadPlugin(info.absoluteFilePath()); if (plugin.type == Plugin::Invalid) { continue; } if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin"; continue; } registerAvailablePlugin(plugin); } } } void Plugins::registerAvailablePlugin(const Plugin &plugin) { if (!m_availablePlugins.contains(plugin)) { m_availablePlugins.append(plugin); } } void Plugins::refreshLoadedPlugins() { m_loadedPlugins.clear(); foreach (const Plugin &plugin, m_availablePlugins) { if (plugin.isLoaded()) { m_loadedPlugins.append(plugin.instance); } } emit availablePluginsChanged(); } void Plugins::loadPythonSupport() { const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins); for (const QString &dir : dirs) { const auto files = QDir(dir).entryInfoList({QSL("PyFalkon*")}, QDir::Files); for (const QFileInfo &info : files) { m_pythonPlugin = new QLibrary(info.absoluteFilePath(), this); m_pythonPlugin->setLoadHints(QLibrary::ExportExternalSymbolsHint); if (!m_pythonPlugin->load()) { qWarning() << "Failed to load python support plugin" << m_pythonPlugin->errorString(); delete m_pythonPlugin; m_pythonPlugin = nullptr; } else { std::cout << "Falkon: Python plugin support initialized" << std::endl; return; } } } } Plugins::Plugin Plugins::loadPlugin(const QString &id) { QString name; Plugin::Type type = Plugin::Invalid; const int colon = id.indexOf(QL1C(':')); if (colon > -1) { const auto t = id.leftRef(colon); if (t == QL1S("internal")) { type = Plugin::InternalPlugin; } else if (t == QL1S("lib")) { type = Plugin::SharedLibraryPlugin; } else if (t == QL1S("python")) { type = Plugin::PythonPlugin; } else if (t == QL1S("qml")) { type = Plugin::QmlPlugin; } name = id.mid(colon + 1); } else { name = id; type = Plugin::SharedLibraryPlugin; } switch (type) { case Plugin::InternalPlugin: return loadInternalPlugin(name); case Plugin::SharedLibraryPlugin: return loadSharedLibraryPlugin(name); case Plugin::PythonPlugin: return loadPythonPlugin(name); case Plugin::QmlPlugin: return QmlPlugin::loadPlugin(name); default: return Plugin(); } } Plugins::Plugin Plugins::loadInternalPlugin(const QString &name) { if (name == QL1S("adblock")) { Plugin plugin; plugin.type = Plugin::InternalPlugin; plugin.pluginId = QSL("internal:adblock"); plugin.internalInstance = new AdBlockPlugin(); plugin.pluginSpec = createSpec(plugin.internalInstance->metaData()); return plugin; } else { return Plugin(); } } Plugins::Plugin Plugins::loadSharedLibraryPlugin(const QString &name) { QString fullPath; if (QFileInfo(name).isAbsolute()) { fullPath = name; } else { fullPath = DataPaths::locate(DataPaths::Plugins, name); if (fullPath.isEmpty()) { qWarning() << "Plugin" << name << "not found"; return Plugin(); } } QPluginLoader *loader = new QPluginLoader(fullPath); PluginInterface *iPlugin = qobject_cast(loader->instance()); if (!iPlugin) { qWarning() << "Loading" << fullPath << "failed:" << loader->errorString(); return Plugin(); } Plugin plugin; plugin.type = Plugin::SharedLibraryPlugin; plugin.pluginId = QSL("lib:%1").arg(QFileInfo(fullPath).fileName()); plugin.pluginPath = fullPath; plugin.pluginLoader = loader; plugin.pluginSpec = createSpec(iPlugin->metaData()); return plugin; } Plugins::Plugin Plugins::loadPythonPlugin(const QString &name) { if (!m_pythonPlugin) { qWarning() << "Python support plugin is not loaded"; return Plugin(); } auto f = (Plugin(*)(const QString &)) m_pythonPlugin->resolve("pyfalkon_load_plugin"); if (!f) { qWarning() << "Failed to resolve" << "pyfalkon_load_plugin"; return Plugin(); } return f(name); } bool Plugins::initPlugin(PluginInterface::InitState state, Plugin *plugin) { if (!plugin) { return false; } switch (plugin->type) { case Plugin::InternalPlugin: initInternalPlugin(plugin); break; case Plugin::SharedLibraryPlugin: initSharedLibraryPlugin(plugin); break; case Plugin::PythonPlugin: initPythonPlugin(plugin); break; case Plugin::QmlPlugin: QmlPlugin::initPlugin(plugin); break; default: return false; } if (!plugin->instance) { return false; } // DataPaths::currentProfilePath() + QL1S("/extensions") is duplicated in qmlsettings.cpp // If you change this, please change it there too. plugin->instance->init(state, DataPaths::currentProfilePath() + QL1S("/extensions")); if (!plugin->instance->testPlugin()) { emit pluginUnloaded(plugin->instance); plugin->instance = nullptr; return false; } return true; } void Plugins::initInternalPlugin(Plugin *plugin) { Q_ASSERT(plugin->type == Plugin::InternalPlugin); plugin->instance = plugin->internalInstance; } void Plugins::initSharedLibraryPlugin(Plugin *plugin) { Q_ASSERT(plugin->type == Plugin::SharedLibraryPlugin); plugin->instance = qobject_cast(plugin->pluginLoader->instance()); } void Plugins::initPythonPlugin(Plugin *plugin) { Q_ASSERT(plugin->type == Plugin::PythonPlugin); if (!m_pythonPlugin) { qWarning() << "Python support plugin is not loaded"; return; } auto f = (void(*)(Plugin *)) m_pythonPlugin->resolve("pyfalkon_init_plugin"); if (!f) { qWarning() << "Failed to resolve" << "pyfalkon_init_plugin"; return; } f(plugin); } diff --git a/src/lib/plugins/plugins.h b/src/lib/plugins/plugins.h index 43413975..45b61a89 100644 --- a/src/lib/plugins/plugins.h +++ b/src/lib/plugins/plugins.h @@ -1,137 +1,139 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 3 of the License, or * (at your option) any later version. * * 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 PLUGINLOADER_H #define PLUGINLOADER_H #include #include #include #include "qzcommon.h" #include "plugininterface.h" class QLibrary; class QPluginLoader; class SpeedDial; struct PluginSpec { QString name; QString description; QString author; QString version; QPixmap icon; bool hasSettings = false; bool operator==(const PluginSpec &other) const { return (this->name == other.name && this->description == other.description && this->author == other.author && this->version == other.version); } }; class FALKON_EXPORT Plugins : public QObject { Q_OBJECT public: struct Plugin { enum Type { Invalid = 0, InternalPlugin, SharedLibraryPlugin, PythonPlugin, QmlPlugin }; Type type = Invalid; QString pluginId; QString pluginPath; PluginSpec pluginSpec; PluginInterface *instance = nullptr; // InternalPlugin PluginInterface *internalInstance = nullptr; // SharedLibraryPlugin QPluginLoader *pluginLoader = nullptr; // Other QVariant data; bool isLoaded() const; bool isRemovable() const; bool operator==(const Plugin &other) const; }; explicit Plugins(QObject* parent = 0); QList availablePlugins(); bool loadPlugin(Plugin* plugin); void unloadPlugin(Plugin* plugin); void removePlugin(Plugin *plugin); + bool addPlugin(const QString &id); + void shutdown(); // SpeedDial SpeedDial* speedDial() { return m_speedDial; } static PluginSpec createSpec(const DesktopFile &metaData); public Q_SLOTS: void loadSettings(); void loadPlugins(); protected: QList m_loadedPlugins; Q_SIGNALS: void pluginUnloaded(PluginInterface* plugin); void availablePluginsChanged(); private: void loadPythonSupport(); Plugin loadPlugin(const QString &id); Plugin loadInternalPlugin(const QString &name); Plugin loadSharedLibraryPlugin(const QString &name); Plugin loadPythonPlugin(const QString &name); bool initPlugin(PluginInterface::InitState state, Plugin *plugin); void initInternalPlugin(Plugin *plugin); void initSharedLibraryPlugin(Plugin *plugin); void initPythonPlugin(Plugin *plugin); void registerAvailablePlugin(const Plugin &plugin); void refreshLoadedPlugins(); void loadAvailablePlugins(); QList m_availablePlugins; QStringList m_allowedPlugins; bool m_pluginsLoaded; SpeedDial* m_speedDial; QList m_internalPlugins; QLibrary *m_pythonPlugin = nullptr; }; Q_DECLARE_METATYPE(Plugins::Plugin) #endif // PLUGINLOADER_H diff --git a/src/lib/webengine/webpage.cpp b/src/lib/webengine/webpage.cpp index af1461f8..5037b1cb 100644 --- a/src/lib/webengine/webpage.cpp +++ b/src/lib/webengine/webpage.cpp @@ -1,753 +1,758 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 3 of the License, or * (at your option) any later version. * * 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 "webpage.h" #include "tabbedwebview.h" #include "browserwindow.h" #include "pluginproxy.h" #include "downloadmanager.h" #include "mainapplication.h" #include "checkboxdialog.h" #include "qztools.h" #include "speeddial.h" #include "autofill.h" #include "popupwebview.h" #include "popupwindow.h" #include "iconprovider.h" #include "qzsettings.h" #include "useragentmanager.h" #include "delayedfilewatcher.h" #include "searchenginesmanager.h" #include "html5permissions/html5permissionsmanager.h" #include "javascript/externaljsobject.h" #include "tabwidget.h" #include "networkmanager.h" #include "webhittestresult.h" #include "ui_jsconfirm.h" #include "ui_jsalert.h" #include "ui_jsprompt.h" #include "passwordmanager.h" #include "scripts.h" +#include "ocssupport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 11, 0) #include #endif QString WebPage::s_lastUploadLocation = QDir::homePath(); QUrl WebPage::s_lastUnsupportedUrl; QTime WebPage::s_lastUnsupportedUrlTime; QStringList s_supportedSchemes; static const bool kEnableJsOutput = qEnvironmentVariableIsSet("FALKON_ENABLE_JS_OUTPUT"); static const bool kEnableJsNonBlockDialogs = qEnvironmentVariableIsSet("FALKON_ENABLE_JS_NONBLOCK_DIALOGS"); WebPage::WebPage(QObject* parent) : QWebEnginePage(mApp->webProfile(), parent) , m_fileWatcher(0) , m_runningLoop(0) , m_loadProgress(100) , m_blockAlerts(false) , m_secureStatus(false) { QWebChannel *channel = new QWebChannel(this); ExternalJsObject::setupWebChannel(channel, this); setWebChannel(channel, SafeJsWorld); connect(this, &QWebEnginePage::loadProgress, this, &WebPage::progress); connect(this, &QWebEnginePage::loadFinished, this, &WebPage::finished); connect(this, &QWebEnginePage::urlChanged, this, &WebPage::urlChanged); connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebPage::featurePermissionRequested); connect(this, &QWebEnginePage::windowCloseRequested, this, &WebPage::windowCloseRequested); connect(this, &QWebEnginePage::fullScreenRequested, this, &WebPage::fullScreenRequested); connect(this, &QWebEnginePage::renderProcessTerminated, this, &WebPage::renderProcessTerminated); connect(this, &QWebEnginePage::authenticationRequired, this, [this](const QUrl &url, QAuthenticator *auth) { mApp->networkManager()->authentication(url, auth, view()); }); connect(this, &QWebEnginePage::proxyAuthenticationRequired, this, [this](const QUrl &, QAuthenticator *auth, const QString &proxyHost) { mApp->networkManager()->proxyAuthentication(proxyHost, auth, view()); }); // Workaround QWebEnginePage not scrolling to anchors when opened in background tab m_contentsResizedConnection = connect(this, &QWebEnginePage::contentsSizeChanged, this, [this]() { const QString fragment = url().fragment(); if (!fragment.isEmpty()) { runJavaScript(Scripts::scrollToAnchor(fragment)); } disconnect(m_contentsResizedConnection); }); // Workaround for broken load started/finished signals in QtWebEngine 5.10, 5.11 connect(this, &QWebEnginePage::loadProgress, this, [this](int progress) { if (progress == 100) { emit loadFinished(true); } }); #if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 11, 0) connect(this, &QWebEnginePage::registerProtocolHandlerRequested, this, [this](QWebEngineRegisterProtocolHandlerRequest request) { delete m_registerProtocolHandlerRequest; m_registerProtocolHandlerRequest = new QWebEngineRegisterProtocolHandlerRequest(request); }); #endif #if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 12, 0) connect(this, &QWebEnginePage::printRequested, this, &WebPage::printRequested); connect(this, &QWebEnginePage::selectClientCertificate, this, [this](QWebEngineClientCertificateSelection selection) { // TODO: It should prompt user selection.select(selection.certificates().at(0)); }); #endif } WebPage::~WebPage() { #if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 11, 0) delete m_registerProtocolHandlerRequest; #endif if (m_runningLoop) { m_runningLoop->exit(1); m_runningLoop = 0; } } WebView *WebPage::view() const { return static_cast(QWebEnginePage::view()); } bool WebPage::execPrintPage(QPrinter *printer, int timeout) { QPointer loop = new QEventLoop; bool result = false; QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit); print(printer, [loop, &result](bool res) { if (loop && loop->isRunning()) { result = res; loop->quit(); } }); loop->exec(); delete loop; return result; } QVariant WebPage::execJavaScript(const QString &scriptSource, quint32 worldId, int timeout) { QPointer loop = new QEventLoop; QVariant result; QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit); runJavaScript(scriptSource, worldId, [loop, &result](const QVariant &res) { if (loop && loop->isRunning()) { result = res; loop->quit(); } }); loop->exec(QEventLoop::ExcludeUserInputEvents); delete loop; return result; } QPointF WebPage::mapToViewport(const QPointF &pos) const { return QPointF(pos.x() / zoomFactor(), pos.y() / zoomFactor()); } WebHitTestResult WebPage::hitTestContent(const QPoint &pos) const { return WebHitTestResult(this, pos); } void WebPage::scroll(int x, int y) { runJavaScript(QSL("window.scrollTo(window.scrollX + %1, window.scrollY + %2)").arg(x).arg(y), SafeJsWorld); } void WebPage::setScrollPosition(const QPointF &pos) { const QPointF v = mapToViewport(pos.toPoint()); runJavaScript(QSL("window.scrollTo(%1, %2)").arg(v.x()).arg(v.y()), SafeJsWorld); } bool WebPage::isRunningLoop() { return m_runningLoop; } bool WebPage::isLoading() const { return m_loadProgress < 100; } // static QStringList WebPage::internalSchemes() { return QStringList{ QSL("http"), QSL("https"), QSL("file"), QSL("ftp"), QSL("data"), QSL("about"), QSL("view-source"), QSL("chrome") }; } // static QStringList WebPage::supportedSchemes() { if (s_supportedSchemes.isEmpty()) { s_supportedSchemes = internalSchemes(); } return s_supportedSchemes; } // static void WebPage::addSupportedScheme(const QString &scheme) { s_supportedSchemes = supportedSchemes(); if (!s_supportedSchemes.contains(scheme)) { s_supportedSchemes.append(scheme); } } // static void WebPage::removeSupportedScheme(const QString &scheme) { s_supportedSchemes.removeOne(scheme); } void WebPage::urlChanged(const QUrl &url) { Q_UNUSED(url) if (isLoading()) { m_blockAlerts = false; } } void WebPage::progress(int prog) { m_loadProgress = prog; bool secStatus = url().scheme() == QL1S("https"); if (secStatus != m_secureStatus) { m_secureStatus = secStatus; emit privacyChanged(secStatus); } } void WebPage::finished() { progress(100); // File scheme watcher if (url().scheme() == QLatin1String("file")) { QFileInfo info(url().toLocalFile()); if (info.isFile()) { if (!m_fileWatcher) { m_fileWatcher = new DelayedFileWatcher(this); connect(m_fileWatcher, &DelayedFileWatcher::delayedFileChanged, this, &WebPage::watchedFileChanged); } const QString filePath = url().toLocalFile(); if (QFile::exists(filePath) && !m_fileWatcher->files().contains(filePath)) { m_fileWatcher->addPath(filePath); } } } else if (m_fileWatcher && !m_fileWatcher->files().isEmpty()) { m_fileWatcher->removePaths(m_fileWatcher->files()); } // AutoFill m_autoFillUsernames = mApp->autoFill()->completePage(this, url()); } void WebPage::watchedFileChanged(const QString &file) { if (url().toLocalFile() == file) { triggerAction(QWebEnginePage::Reload); } } void WebPage::handleUnknownProtocol(const QUrl &url) { const QString protocol = url.scheme(); if (protocol == QLatin1String("mailto")) { desktopServicesOpen(url); return; } if (qzSettings->blockedProtocols.contains(protocol)) { qDebug() << "WebPage::handleUnknownProtocol Protocol" << protocol << "is blocked!"; return; } if (qzSettings->autoOpenProtocols.contains(protocol)) { desktopServicesOpen(url); return; } CheckBoxDialog dialog(QMessageBox::Yes | QMessageBox::No, view()); dialog.setDefaultButton(QMessageBox::Yes); const QString wrappedUrl = QzTools::alignTextToWidth(url.toString(), "
", dialog.fontMetrics(), 450); const QString text = tr("Falkon cannot handle %1: links. The requested link " "is
  • %2
Do you want Falkon to try " "open this link in system application?").arg(protocol, wrappedUrl); dialog.setText(text); dialog.setCheckBoxText(tr("Remember my choice for this protocol")); dialog.setWindowTitle(tr("External Protocol Request")); dialog.setIcon(QMessageBox::Question); switch (dialog.exec()) { case QMessageBox::Yes: if (dialog.isChecked()) { qzSettings->autoOpenProtocols.append(protocol); qzSettings->saveSettings(); } QDesktopServices::openUrl(url); break; case QMessageBox::No: if (dialog.isChecked()) { qzSettings->blockedProtocols.append(protocol); qzSettings->saveSettings(); } break; default: break; } } void WebPage::desktopServicesOpen(const QUrl &url) { // Open same url only once in 2 secs const int sameUrlTimeout = 2 * 1000; if (s_lastUnsupportedUrl != url || s_lastUnsupportedUrlTime.isNull() || s_lastUnsupportedUrlTime.elapsed() > sameUrlTimeout) { s_lastUnsupportedUrl = url; s_lastUnsupportedUrlTime.restart(); QDesktopServices::openUrl(url); } else { qWarning() << "WebPage::desktopServicesOpen Url" << url << "has already been opened!\n" "Ignoring it to prevent infinite loop!"; } } void WebPage::windowCloseRequested() { if (!view()) return; view()->closeView(); } void WebPage::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) { view()->requestFullScreen(fullScreenRequest.toggleOn()); const bool accepted = fullScreenRequest.toggleOn() == view()->isFullScreen(); if (accepted) fullScreenRequest.accept(); else fullScreenRequest.reject(); } void WebPage::featurePermissionRequested(const QUrl &origin, const QWebEnginePage::Feature &feature) { if (feature == MouseLock && view()->isFullScreen()) setFeaturePermission(origin, feature, PermissionGrantedByUser); else mApp->html5PermissionsManager()->requestPermissions(this, origin, feature); } void WebPage::renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) { Q_UNUSED(exitCode) if (terminationStatus == NormalTerminationStatus) return; QTimer::singleShot(0, this, [this]() { QString page = QzTools::readAllFileContents(":html/tabcrash.html"); page.replace(QL1S("%IMAGE%"), QzTools::pixmapToDataUrl(IconProvider::standardIcon(QStyle::SP_MessageBoxWarning).pixmap(45)).toString()); page.replace(QL1S("%TITLE%"), tr("Failed loading page")); page.replace(QL1S("%HEADING%"), tr("Failed loading page")); page.replace(QL1S("%LI-1%"), tr("Something went wrong while loading this page.")); page.replace(QL1S("%LI-2%"), tr("Try reloading the page or closing some tabs to make more memory available.")); page.replace(QL1S("%RELOAD-PAGE%"), tr("Reload page")); page = QzTools::applyDirectionToPage(page); setHtml(page.toUtf8(), url()); }); } bool WebPage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) { if (mApp->isClosing()) { return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); } if (!mApp->plugins()->acceptNavigationRequest(this, url, type, isMainFrame)) return false; if (url.scheme() == QL1S("falkon")) { if (url.path() == QL1S("AddSearchProvider")) { QUrlQuery query(url); mApp->searchEnginesManager()->addEngine(QUrl(query.queryItemValue(QSL("url")))); return false; #if QTWEBENGINEWIDGETS_VERSION < QT_VERSION_CHECK(5, 12, 0) } else if (url.path() == QL1S("PrintPage")) { emit printRequested(); return false; #endif } } + if (url.scheme() == QL1S("ocs") && OcsSupport::instance()->handleUrl(url)) { + return false; + } + const bool result = QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); if (result) { if (isMainFrame) { const bool isWeb = url.scheme() == QL1S("http") || url.scheme() == QL1S("https") || url.scheme() == QL1S("file"); const bool globalJsEnabled = mApp->webSettings()->testAttribute(QWebEngineSettings::JavascriptEnabled); settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, isWeb ? globalJsEnabled : true); } emit navigationRequestAccepted(url, type, isMainFrame); } return result; } bool WebPage::certificateError(const QWebEngineCertificateError &error) { return mApp->networkManager()->certificateError(error, view()); } QStringList WebPage::chooseFiles(QWebEnginePage::FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes) { Q_UNUSED(acceptedMimeTypes); QStringList files; QString suggestedFileName = s_lastUploadLocation; if (!oldFiles.isEmpty()) suggestedFileName = oldFiles.at(0); switch (mode) { case FileSelectOpen: files = QStringList(QzTools::getOpenFileName("WebPage-ChooseFile", view(), tr("Choose file..."), suggestedFileName)); break; case FileSelectOpenMultiple: files = QzTools::getOpenFileNames("WebPage-ChooseFile", view(), tr("Choose files..."), suggestedFileName); break; default: files = QWebEnginePage::chooseFiles(mode, oldFiles, acceptedMimeTypes); break; } if (!files.isEmpty()) s_lastUploadLocation = files.at(0); return files; } QStringList WebPage::autoFillUsernames() const { return m_autoFillUsernames; } QUrl WebPage::registerProtocolHandlerRequestUrl() const { #if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 11, 0) if (m_registerProtocolHandlerRequest && url().host() == m_registerProtocolHandlerRequest->origin().host()) { return m_registerProtocolHandlerRequest->origin(); } #endif return QUrl(); } QString WebPage::registerProtocolHandlerRequestScheme() const { #if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 11, 0) if (m_registerProtocolHandlerRequest && url().host() == m_registerProtocolHandlerRequest->origin().host()) { return m_registerProtocolHandlerRequest->scheme(); } #endif return QString(); } bool WebPage::javaScriptPrompt(const QUrl &securityOrigin, const QString &msg, const QString &defaultValue, QString* result) { if (!kEnableJsNonBlockDialogs) { return QWebEnginePage::javaScriptPrompt(securityOrigin, msg, defaultValue, result); } if (m_runningLoop) { return false; } QFrame *widget = new QFrame(view()->overlayWidget()); widget->setObjectName("jsFrame"); Ui_jsPrompt* ui = new Ui_jsPrompt(); ui->setupUi(widget); ui->message->setText(msg); ui->lineEdit->setText(defaultValue); ui->lineEdit->setFocus(); widget->resize(view()->size()); widget->show(); QAbstractButton *clicked = nullptr; connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [&](QAbstractButton *button) { clicked = button; }); connect(view(), &WebView::viewportResized, widget, QOverload::of(&QFrame::resize)); connect(ui->lineEdit, SIGNAL(returnPressed()), ui->buttonBox->button(QDialogButtonBox::Ok), SLOT(animateClick())); QEventLoop eLoop; m_runningLoop = &eLoop; connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit); if (eLoop.exec() == 1) { return result; } m_runningLoop = 0; QString x = ui->lineEdit->text(); bool _result = ui->buttonBox->buttonRole(clicked) == QDialogButtonBox::AcceptRole; *result = x; delete widget; view()->setFocus(); return _result; } bool WebPage::javaScriptConfirm(const QUrl &securityOrigin, const QString &msg) { if (!kEnableJsNonBlockDialogs) { return QWebEnginePage::javaScriptConfirm(securityOrigin, msg); } if (m_runningLoop) { return false; } QFrame *widget = new QFrame(view()->overlayWidget()); widget->setObjectName("jsFrame"); Ui_jsConfirm* ui = new Ui_jsConfirm(); ui->setupUi(widget); ui->message->setText(msg); ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); widget->resize(view()->size()); widget->show(); QAbstractButton *clicked = nullptr; connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [&](QAbstractButton *button) { clicked = button; }); connect(view(), &WebView::viewportResized, widget, QOverload::of(&QFrame::resize)); QEventLoop eLoop; m_runningLoop = &eLoop; connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit); if (eLoop.exec() == 1) { return false; } m_runningLoop = 0; bool result = ui->buttonBox->buttonRole(clicked) == QDialogButtonBox::AcceptRole; delete widget; view()->setFocus(); return result; } void WebPage::javaScriptAlert(const QUrl &securityOrigin, const QString &msg) { Q_UNUSED(securityOrigin) if (m_blockAlerts || m_runningLoop) { return; } if (!kEnableJsNonBlockDialogs) { QString title = tr("JavaScript alert"); if (!url().host().isEmpty()) { title.append(QString(" - %1").arg(url().host())); } CheckBoxDialog dialog(QMessageBox::Ok, view()); dialog.setDefaultButton(QMessageBox::Ok); dialog.setWindowTitle(title); dialog.setText(msg); dialog.setCheckBoxText(tr("Prevent this page from creating additional dialogs")); dialog.setIcon(QMessageBox::Information); dialog.exec(); m_blockAlerts = dialog.isChecked(); return; } QFrame *widget = new QFrame(view()->overlayWidget()); widget->setObjectName("jsFrame"); Ui_jsAlert* ui = new Ui_jsAlert(); ui->setupUi(widget); ui->message->setText(msg); ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); widget->resize(view()->size()); widget->show(); connect(view(), &WebView::viewportResized, widget, QOverload::of(&QFrame::resize)); QEventLoop eLoop; m_runningLoop = &eLoop; connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit); if (eLoop.exec() == 1) { return; } m_runningLoop = 0; m_blockAlerts = ui->preventAlerts->isChecked(); delete widget; view()->setFocus(); } void WebPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) { if (!kEnableJsOutput) { return; } switch (level) { case InfoMessageLevel: std::cout << "[I] "; break; case WarningMessageLevel: std::cout << "[W] "; break; case ErrorMessageLevel: std::cout << "[E] "; break; } std::cout << qPrintable(sourceID) << ":" << lineNumber << " " << qPrintable(message); } QWebEnginePage* WebPage::createWindow(QWebEnginePage::WebWindowType type) { TabbedWebView *tView = qobject_cast(view()); BrowserWindow *window = tView ? tView->browserWindow() : mApp->getWindow(); auto createTab = [=](Qz::NewTabPositionFlags pos) { int index = window->tabWidget()->addView(QUrl(), pos); TabbedWebView* view = window->weView(index); view->setPage(new WebPage); if (tView) { tView->webTab()->addChildTab(view->webTab()); } // Workaround focus issue when creating tab if (pos.testFlag(Qz::NT_SelectedTab)) { QPointer pview = view; pview->setFocus(); QTimer::singleShot(100, this, [pview]() { if (pview && pview->webTab()->isCurrentTab()) { pview->setFocus(); } }); } return view->page(); }; switch (type) { case QWebEnginePage::WebBrowserWindow: { BrowserWindow *window = mApp->createWindow(Qz::BW_NewWindow); WebPage *page = new WebPage; window->setStartPage(page); return page; } case QWebEnginePage::WebDialog: if (!qzSettings->openPopupsInTabs) { PopupWebView* view = new PopupWebView; view->setPage(new WebPage); PopupWindow* popup = new PopupWindow(view); popup->show(); window->addDeleteOnCloseWidget(popup); return view->page(); } // else fallthrough case QWebEnginePage::WebBrowserTab: return createTab(Qz::NT_CleanSelectedTab); case QWebEnginePage::WebBrowserBackgroundTab: return createTab(Qz::NT_CleanNotSelectedTab); default: break; } return Q_NULLPTR; }