diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index f41508336..fe0cc6c36 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -1,539 +1,482 @@ dnl Copyright (c) 2013-2016 The Bitcoin Core developers dnl Distributed under the MIT software license, see the accompanying dnl file COPYING or http://www.opensource.org/licenses/mit-license.php. dnl Helper for cases where a qt dependency is not met. dnl Output: If qt version is auto, set bitcoin_enable_qt to false. Else, exit. AC_DEFUN([BITCOIN_QT_FAIL],[ if test "x$bitcoin_qt_want_version" = xauto && test "x$bitcoin_qt_force" != xyes; then if test "x$bitcoin_enable_qt" != xno; then AC_MSG_WARN([$1; bitcoin-qt frontend will not be built]) fi bitcoin_enable_qt=no bitcoin_enable_qt_test=no else AC_MSG_ERROR([$1]) fi ]) AC_DEFUN([BITCOIN_QT_CHECK],[ if test "x$bitcoin_enable_qt" != xno && test "x$bitcoin_qt_want_version" != xno; then true $1 else true $2 fi ]) dnl BITCOIN_QT_PATH_PROGS([FOO], [foo foo2], [/path/to/search/first], [continue if missing]) dnl Helper for finding the path of programs needed for Qt. dnl Inputs: $1: Variable to be set dnl Inputs: $2: List of programs to search for dnl Inputs: $3: Look for $2 here before $PATH dnl Inputs: $4: If "yes", don't fail if $2 is not found. dnl Output: $1 is set to the path of $2 if found. $2 are searched in order. AC_DEFUN([BITCOIN_QT_PATH_PROGS],[ BITCOIN_QT_CHECK([ if test "x$3" != x; then AC_PATH_PROGS($1,$2,,$3) else AC_PATH_PROGS($1,$2) fi if test "x$$1" = x && test "x$4" != xyes; then BITCOIN_QT_FAIL([$1 not found]) fi ]) ]) dnl Initialize qt input. dnl This must be called before any other BITCOIN_QT* macros to ensure that dnl input variables are set correctly. dnl CAUTION: Do not use this inside of a conditional. AC_DEFUN([BITCOIN_QT_INIT],[ dnl enable qt support AC_ARG_WITH([gui], - [AS_HELP_STRING([--with-gui@<:@=no|qt4|qt5|auto@:>@], - [build bitcoin-qt GUI (default=auto, qt5 tried first)])], + [AS_HELP_STRING([--with-gui@<:@=no|qt5|auto@:>@], + [build bitcoin-qt GUI (default=auto)])], [ bitcoin_qt_want_version=$withval if test "x$bitcoin_qt_want_version" = xyes; then bitcoin_qt_force=yes bitcoin_qt_want_version=auto fi ], [bitcoin_qt_want_version=auto]) AC_ARG_WITH([qt-incdir],[AS_HELP_STRING([--with-qt-incdir=INC_DIR],[specify qt include path (overridden by pkgconfig)])], [qt_include_path=$withval], []) AC_ARG_WITH([qt-libdir],[AS_HELP_STRING([--with-qt-libdir=LIB_DIR],[specify qt lib path (overridden by pkgconfig)])], [qt_lib_path=$withval], []) AC_ARG_WITH([qt-plugindir],[AS_HELP_STRING([--with-qt-plugindir=PLUGIN_DIR],[specify qt plugin path (overridden by pkgconfig)])], [qt_plugin_path=$withval], []) AC_ARG_WITH([qt-translationdir],[AS_HELP_STRING([--with-qt-translationdir=PLUGIN_DIR],[specify qt translation path (overridden by pkgconfig)])], [qt_translation_path=$withval], []) AC_ARG_WITH([qt-bindir],[AS_HELP_STRING([--with-qt-bindir=BIN_DIR],[specify qt bin path])], [qt_bin_path=$withval], []) AC_ARG_WITH([qtdbus], [AS_HELP_STRING([--with-qtdbus], [enable DBus support (default is yes if qt is enabled and QtDBus is found)])], [use_dbus=$withval], [use_dbus=auto]) AC_SUBST(QT_TRANSLATION_DIR,$qt_translation_path) ]) dnl Find the appropriate version of Qt libraries and includes. dnl Inputs: $1: Whether or not pkg-config should be used. yes|no. Default: yes. dnl Inputs: $2: If $1 is "yes" and --with-gui=auto, which qt version should be dnl tried first. dnl Outputs: See _BITCOIN_QT_FIND_LIBS_* dnl Outputs: Sets variables for all qt-related tools. dnl Outputs: bitcoin_enable_qt, bitcoin_enable_qt_dbus, bitcoin_enable_qt_test AC_DEFUN([BITCOIN_QT_CONFIGURE],[ use_pkgconfig=$1 if test "x$use_pkgconfig" = x; then use_pkgconfig=yes fi if test "x$use_pkgconfig" = xyes; then - BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG([$2])]) + BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG]) else BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG]) fi dnl This is ugly and complicated. Yuck. Works as follows: - dnl We can't discern whether Qt4 builds are static or not. For Qt5, we can - dnl check a header to find out. When Qt is built statically, some plugins must - dnl be linked into the final binary as well. These plugins have changed between - dnl Qt4 and Qt5. With Qt5, languages moved into core and the WindowsIntegration - dnl plugin was added. Since we can't tell if Qt4 is static or not, it is - dnl assumed for windows builds. + dnl For Qt5, we can check a header to find out whether Qt is build + dnl statically. When Qt is built statically, some plugins must be linked into + dnl the final binary as well. + dnl With Qt5, languages moved into core and the WindowsIntegration plugin was + dnl added. dnl _BITCOIN_QT_CHECK_STATIC_PLUGINS does a quick link-check and appends the dnl results to QT_LIBS. BITCOIN_QT_CHECK([ TEMP_CPPFLAGS=$CPPFLAGS TEMP_CXXFLAGS=$CXXFLAGS CPPFLAGS="$QT_INCLUDES $CPPFLAGS" CXXFLAGS="$PIC_FLAGS $CXXFLAGS" - if test "x$bitcoin_qt_got_major_vers" = x5; then - _BITCOIN_QT_IS_STATIC - if test "x$bitcoin_cv_static_qt" = xyes; then - _BITCOIN_QT_FIND_STATIC_PLUGINS - AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) - AC_CACHE_CHECK(for Qt < 5.4, bitcoin_cv_need_acc_widget,[ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ - #include - #ifndef QT_VERSION - # include - #endif - ]], - [[ - #if QT_VERSION >= 0x050400 - choke - #endif - ]])], - [bitcoin_cv_need_acc_widget=yes], - [bitcoin_cv_need_acc_widget=no]) - ]) - if test "x$bitcoin_cv_need_acc_widget" = xyes; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets]) - fi - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)],[-lqminimal]) - AC_DEFINE(QT_QPA_PLATFORM_MINIMAL, 1, [Define this symbol if the minimal qt platform exists]) - if test "x$TARGET_OS" = xwindows; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows]) - AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows]) - elif test "x$TARGET_OS" = xlinux; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)],[-lqxcb -lxcb-static]) - AC_DEFINE(QT_QPA_PLATFORM_XCB, 1, [Define this symbol if the qt platform is xcb]) - elif test "x$TARGET_OS" = xdarwin; then - AX_CHECK_LINK_FLAG([[-framework IOKit]],[QT_LIBS="$QT_LIBS -framework IOKit"],[AC_MSG_ERROR(could not iokit framework)]) - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)],[-lqcocoa]) - AC_DEFINE(QT_QPA_PLATFORM_COCOA, 1, [Define this symbol if the qt platform is cocoa]) - fi + _BITCOIN_QT_IS_STATIC + if test "x$bitcoin_cv_static_qt" = xyes; then + _BITCOIN_QT_FIND_STATIC_PLUGINS + AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) + AC_CACHE_CHECK(for Qt < 5.4, bitcoin_cv_need_acc_widget,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include + #ifndef QT_VERSION + # include + #endif + ]], + [[ + #if QT_VERSION >= 0x050400 + choke + #endif + ]])], + [bitcoin_cv_need_acc_widget=yes], + [bitcoin_cv_need_acc_widget=no]) + ]) + if test "x$bitcoin_cv_need_acc_widget" = xyes; then + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets]) fi - else + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)],[-lqminimal]) + AC_DEFINE(QT_QPA_PLATFORM_MINIMAL, 1, [Define this symbol if the minimal qt platform exists]) if test "x$TARGET_OS" = xwindows; then - AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) - _BITCOIN_QT_CHECK_STATIC_PLUGINS([ - Q_IMPORT_PLUGIN(qcncodecs) - Q_IMPORT_PLUGIN(qjpcodecs) - Q_IMPORT_PLUGIN(qtwcodecs) - Q_IMPORT_PLUGIN(qkrcodecs) - Q_IMPORT_PLUGIN(AccessibleFactory)], - [-lqcncodecs -lqjpcodecs -lqtwcodecs -lqkrcodecs -lqtaccessiblewidgets]) + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows]) + AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows]) + elif test "x$TARGET_OS" = xlinux; then + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)],[-lqxcb -lxcb-static]) + AC_DEFINE(QT_QPA_PLATFORM_XCB, 1, [Define this symbol if the qt platform is xcb]) + elif test "x$TARGET_OS" = xdarwin; then + AX_CHECK_LINK_FLAG([[-framework IOKit]],[QT_LIBS="$QT_LIBS -framework IOKit"],[AC_MSG_ERROR(could not iokit framework)]) + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)],[-lqcocoa]) + AC_DEFINE(QT_QPA_PLATFORM_COCOA, 1, [Define this symbol if the qt platform is cocoa]) fi fi CPPFLAGS=$TEMP_CPPFLAGS CXXFLAGS=$TEMP_CXXFLAGS ]) if test "x$use_pkgconfig$qt_bin_path" = xyes; then - if test "x$bitcoin_qt_got_major_vers" = x5; then - qt_bin_path="`$PKG_CONFIG --variable=host_bins Qt5Core 2>/dev/null`" - fi + qt_bin_path="`$PKG_CONFIG --variable=host_bins Qt5Core 2>/dev/null`" fi if test "x$use_hardening" != xno; then BITCOIN_QT_CHECK([ AC_MSG_CHECKING(whether -fPIE can be used with this Qt config) TEMP_CPPFLAGS=$CPPFLAGS TEMP_CXXFLAGS=$CXXFLAGS CPPFLAGS="$QT_INCLUDES $CPPFLAGS" CXXFLAGS="$PIE_FLAGS $CXXFLAGS" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #ifndef QT_VERSION # include #endif ]], [[ #if defined(QT_REDUCE_RELOCATIONS) choke #endif ]])], [ AC_MSG_RESULT(yes); QT_PIE_FLAGS=$PIE_FLAGS ], [ AC_MSG_RESULT(no); QT_PIE_FLAGS=$PIC_FLAGS] ) CPPFLAGS=$TEMP_CPPFLAGS CXXFLAGS=$TEMP_CXXFLAGS ]) else BITCOIN_QT_CHECK([ AC_MSG_CHECKING(whether -fPIC is needed with this Qt config) TEMP_CPPFLAGS=$CPPFLAGS CPPFLAGS="$QT_INCLUDES $CPPFLAGS" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #ifndef QT_VERSION # include #endif ]], [[ #if defined(QT_REDUCE_RELOCATIONS) choke #endif ]])], [ AC_MSG_RESULT(no)], [ AC_MSG_RESULT(yes); QT_PIE_FLAGS=$PIC_FLAGS] ) CPPFLAGS=$TEMP_CPPFLAGS ]) fi - BITCOIN_QT_PATH_PROGS([MOC], [moc-qt${bitcoin_qt_got_major_vers} moc${bitcoin_qt_got_major_vers} moc], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([UIC], [uic-qt${bitcoin_qt_got_major_vers} uic${bitcoin_qt_got_major_vers} uic], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([RCC], [rcc-qt${bitcoin_qt_got_major_vers} rcc${bitcoin_qt_got_major_vers} rcc], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([LRELEASE], [lrelease-qt${bitcoin_qt_got_major_vers} lrelease${bitcoin_qt_got_major_vers} lrelease], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([LUPDATE], [lupdate-qt${bitcoin_qt_got_major_vers} lupdate${bitcoin_qt_got_major_vers} lupdate],$qt_bin_path, yes) + BITCOIN_QT_PATH_PROGS([MOC], [moc-qt5 moc5 moc], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([UIC], [uic-qt5 uic5 uic], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([RCC], [rcc-qt5 rcc5 rcc], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([LRELEASE], [lrelease-qt5 lrelease5 lrelease], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([LUPDATE], [lupdate-qt5 lupdate5 lupdate],$qt_bin_path, yes) MOC_DEFS='-DHAVE_CONFIG_H -I$(srcdir)' case $host in *darwin*) BITCOIN_QT_CHECK([ MOC_DEFS="${MOC_DEFS} -DQ_OS_MAC" base_frameworks="-framework Foundation -framework ApplicationServices -framework AppKit" AX_CHECK_LINK_FLAG([[$base_frameworks]],[QT_LIBS="$QT_LIBS $base_frameworks"],[AC_MSG_ERROR(could not find base frameworks)]) ]) ;; *mingw*) BITCOIN_QT_CHECK([ AX_CHECK_LINK_FLAG([[-mwindows]],[QT_LDFLAGS="$QT_LDFLAGS -mwindows"],[AC_MSG_WARN(-mwindows linker support not detected)]) ]) esac dnl enable qt support AC_MSG_CHECKING(whether to build ]AC_PACKAGE_NAME[ GUI) BITCOIN_QT_CHECK([ bitcoin_enable_qt=yes bitcoin_enable_qt_test=yes if test "x$have_qt_test" = xno; then bitcoin_enable_qt_test=no fi bitcoin_enable_qt_dbus=no if test "x$use_dbus" != xno && test "x$have_qt_dbus" = xyes; then bitcoin_enable_qt_dbus=yes fi if test "x$use_dbus" = xyes && test "x$have_qt_dbus" = xno; then AC_MSG_ERROR([libQtDBus not found. Install libQtDBus or remove --with-qtdbus.]) fi if test "x$LUPDATE" = x; then AC_MSG_WARN([lupdate is required to update qt translations]) fi ],[ bitcoin_enable_qt=no ]) - AC_MSG_RESULT([$bitcoin_enable_qt (Qt${bitcoin_qt_got_major_vers})]) + AC_MSG_RESULT([$bitcoin_enable_qt (Qt5)]) AC_SUBST(QT_PIE_FLAGS) AC_SUBST(QT_INCLUDES) AC_SUBST(QT_LIBS) AC_SUBST(QT_LDFLAGS) AC_SUBST(QT_DBUS_INCLUDES) AC_SUBST(QT_DBUS_LIBS) AC_SUBST(QT_TEST_INCLUDES) AC_SUBST(QT_TEST_LIBS) - AC_SUBST(QT_SELECT, qt${bitcoin_qt_got_major_vers}) + AC_SUBST(QT_SELECT, qt5) AC_SUBST(MOC_DEFS) ]) dnl All macros below are internal and should _not_ be used from the main dnl configure.ac. dnl ---- dnl Internal. Check if the included version of Qt is Qt5. dnl Requires: INCLUDES must be populated as necessary. dnl Output: bitcoin_cv_qt5=yes|no AC_DEFUN([_BITCOIN_QT_CHECK_QT5],[ AC_CACHE_CHECK(for Qt 5, bitcoin_cv_qt5,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #ifndef QT_VERSION # include #endif ]], [[ #if QT_VERSION < 0x050000 choke #endif ]])], [bitcoin_cv_qt5=yes], [bitcoin_cv_qt5=no]) ])]) dnl Internal. Check if the linked version of Qt was built as static libs. -dnl Requires: Qt5. This check cannot determine if Qt4 is static. +dnl Requires: Qt5. dnl Requires: INCLUDES and LIBS must be populated as necessary. dnl Output: bitcoin_cv_static_qt=yes|no dnl Output: Defines QT_STATICPLUGIN if plugins are static. AC_DEFUN([_BITCOIN_QT_IS_STATIC],[ AC_CACHE_CHECK(for static Qt, bitcoin_cv_static_qt,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #ifndef QT_VERSION # include #endif ]], [[ #if !defined(QT_STATIC) choke #endif ]])], [bitcoin_cv_static_qt=yes], [bitcoin_cv_static_qt=no]) ]) if test "x$bitcoin_cv_static_qt" = xyes; then AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol for static Qt plugins]) fi ]) dnl Internal. Check if the link-requirements for static plugins are met. dnl Requires: INCLUDES and LIBS must be populated as necessary. dnl Inputs: $1: A series of Q_IMPORT_PLUGIN(). dnl Inputs: $2: The libraries that resolve $1. dnl Output: QT_LIBS is prepended or configure exits. AC_DEFUN([_BITCOIN_QT_CHECK_STATIC_PLUGINS],[ AC_MSG_CHECKING(for static Qt plugins: $2) CHECK_STATIC_PLUGINS_TEMP_LIBS="$LIBS" LIBS="$2 $QT_LIBS $LIBS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #define QT_STATICPLUGIN #include $1]], [[return 0;]])], [AC_MSG_RESULT(yes); QT_LIBS="$2 $QT_LIBS"], [AC_MSG_RESULT(no); BITCOIN_QT_FAIL(Could not resolve: $2)]) LIBS="$CHECK_STATIC_PLUGINS_TEMP_LIBS" ]) dnl Internal. Find paths necessary for linking qt static plugins -dnl Inputs: bitcoin_qt_got_major_vers. 4 or 5. dnl Inputs: qt_plugin_path. optional. dnl Outputs: QT_LIBS is appended AC_DEFUN([_BITCOIN_QT_FIND_STATIC_PLUGINS],[ - if test "x$bitcoin_qt_got_major_vers" = x5; then - if test "x$qt_plugin_path" != x; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms" - if test -d "$qt_plugin_path/accessible"; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" - fi + if test "x$qt_plugin_path" != x; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms" + if test -d "$qt_plugin_path/accessible"; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" fi - if test "x$use_pkgconfig" = xyes; then - : dnl - m4_ifdef([PKG_CHECK_MODULES],[ - PKG_CHECK_MODULES([QTPLATFORM], [Qt5PlatformSupport], [QT_LIBS="$QTPLATFORM_LIBS $QT_LIBS"]) - if test "x$TARGET_OS" = xlinux; then - PKG_CHECK_MODULES([X11XCB], [x11-xcb], [QT_LIBS="$X11XCB_LIBS $QT_LIBS"]) - if ${PKG_CONFIG} --exists "Qt5Core >= 5.5" 2>/dev/null; then - PKG_CHECK_MODULES([QTXCBQPA], [Qt5XcbQpa], [QT_LIBS="$QTXCBQPA_LIBS $QT_LIBS"]) - fi - elif test "x$TARGET_OS" = xdarwin; then - PKG_CHECK_MODULES([QTPRINT], [Qt5PrintSupport], [QT_LIBS="$QTPRINT_LIBS $QT_LIBS"]) + fi + if test "x$use_pkgconfig" = xyes; then + : dnl + m4_ifdef([PKG_CHECK_MODULES],[ + PKG_CHECK_MODULES([QTPLATFORM], [Qt5PlatformSupport], [QT_LIBS="$QTPLATFORM_LIBS $QT_LIBS"]) + if test "x$TARGET_OS" = xlinux; then + PKG_CHECK_MODULES([X11XCB], [x11-xcb], [QT_LIBS="$X11XCB_LIBS $QT_LIBS"]) + if ${PKG_CONFIG} --exists "Qt5Core >= 5.5" 2>/dev/null; then + PKG_CHECK_MODULES([QTXCBQPA], [Qt5XcbQpa], [QT_LIBS="$QTXCBQPA_LIBS $QT_LIBS"]) fi - ]) - else - if test "x$TARGET_OS" = xwindows; then - AC_CACHE_CHECK(for Qt >= 5.6, bitcoin_cv_need_platformsupport,[ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ - #include - #ifndef QT_VERSION - # include - #endif - ]], - [[ - #if QT_VERSION < 0x050600 - choke - #endif - ]])], - [bitcoin_cv_need_platformsupport=yes], - [bitcoin_cv_need_platformsupport=no]) - ]) - if test "x$bitcoin_cv_need_platformsupport" = xyes; then - BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}PlatformSupport],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}PlatformSupport not found))) - fi + elif test "x$TARGET_OS" = xdarwin; then + PKG_CHECK_MODULES([QTPRINT], [Qt5PrintSupport], [QT_LIBS="$QTPRINT_LIBS $QT_LIBS"]) + fi + ]) + else + if test "x$TARGET_OS" = xwindows; then + AC_CACHE_CHECK(for Qt >= 5.6, bitcoin_cv_need_platformsupport,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include + #ifndef QT_VERSION + # include + #endif + ]], + [[ + #if QT_VERSION < 0x050600 + choke + #endif + ]])], + [bitcoin_cv_need_platformsupport=yes], + [bitcoin_cv_need_platformsupport=no]) + ]) + if test "x$bitcoin_cv_need_platformsupport" = xyes; then + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}PlatformSupport],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}PlatformSupport not found))) fi fi - else - if test "x$qt_plugin_path" != x; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" - QT_LIBS="$QT_LIBS -L$qt_plugin_path/codecs" - fi - fi + fi ]) dnl Internal. Find Qt libraries using pkg-config. dnl Inputs: bitcoin_qt_want_version (from --with-gui=). The version to check dnl first. dnl Inputs: $1: If bitcoin_qt_want_version is "auto", check for this version dnl first. dnl Outputs: All necessary QT_* variables are set. -dnl Outputs: bitcoin_qt_got_major_vers is set to "4" or "5". dnl Outputs: have_qt_test and have_qt_dbus are set (if applicable) to yes|no. AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG],[ m4_ifdef([PKG_CHECK_MODULES],[ - auto_priority_version=$1 - if test "x$auto_priority_version" = x; then - auto_priority_version=qt5 - fi - if test "x$bitcoin_qt_want_version" = xqt5 || ( test "x$bitcoin_qt_want_version" = xauto && test "x$auto_priority_version" = xqt5 ); then - QT_LIB_PREFIX=Qt5 - bitcoin_qt_got_major_vers=5 - else - QT_LIB_PREFIX=Qt - bitcoin_qt_got_major_vers=4 - fi + QT_LIB_PREFIX=Qt5 qt5_modules="Qt5Core Qt5Gui Qt5Network Qt5Widgets" - qt4_modules="QtCore QtGui QtNetwork" BITCOIN_QT_CHECK([ - if test "x$bitcoin_qt_want_version" = xqt5 || ( test "x$bitcoin_qt_want_version" = xauto && test "x$auto_priority_version" = xqt5 ); then - PKG_CHECK_MODULES([QT5], [$qt5_modules], [QT_INCLUDES="$QT5_CFLAGS"; QT_LIBS="$QT5_LIBS" have_qt=yes],[have_qt=no]) - elif test "x$bitcoin_qt_want_version" = xqt4 || ( test "x$bitcoin_qt_want_version" = xauto && test "x$auto_priority_version" = xqt4 ); then - PKG_CHECK_MODULES([QT4], [$qt4_modules], [QT_INCLUDES="$QT4_CFLAGS"; QT_LIBS="$QT4_LIBS" ; have_qt=yes], [have_qt=no]) - fi + PKG_CHECK_MODULES([QT5], [$qt5_modules], [QT_INCLUDES="$QT5_CFLAGS"; QT_LIBS="$QT5_LIBS" have_qt=yes],[have_qt=no]) - dnl qt version is set to 'auto' and the preferred version wasn't found. Now try the other. - if test "x$have_qt" = xno && test "x$bitcoin_qt_want_version" = xauto; then - if test "x$auto_priority_version" = xqt5; then - PKG_CHECK_MODULES([QT4], [$qt4_modules], [QT_INCLUDES="$QT4_CFLAGS"; QT_LIBS="$QT4_LIBS" ; have_qt=yes; QT_LIB_PREFIX=Qt; bitcoin_qt_got_major_vers=4], [have_qt=no]) - else - PKG_CHECK_MODULES([QT5], [$qt5_modules], [QT_INCLUDES="$QT5_CFLAGS"; QT_LIBS="$QT5_LIBS" ; have_qt=yes; QT_LIB_PREFIX=Qt5; bitcoin_qt_got_major_vers=5], [have_qt=no]) - fi - fi if test "x$have_qt" != xyes; then have_qt=no BITCOIN_QT_FAIL([Qt dependencies not found]) fi ]) BITCOIN_QT_CHECK([ PKG_CHECK_MODULES([QT_TEST], [${QT_LIB_PREFIX}Test], [QT_TEST_INCLUDES="$QT_TEST_CFLAGS"; have_qt_test=yes], [have_qt_test=no]) if test "x$use_dbus" != xno; then PKG_CHECK_MODULES([QT_DBUS], [${QT_LIB_PREFIX}DBus], [QT_DBUS_INCLUDES="$QT_DBUS_CFLAGS"; have_qt_dbus=yes], [have_qt_dbus=no]) fi ]) ]) true; dnl ]) dnl Internal. Find Qt libraries without using pkg-config. Version is deduced dnl from the discovered headers. dnl Inputs: bitcoin_qt_want_version (from --with-gui=). The version to use. dnl If "auto", the version will be discovered by _BITCOIN_QT_CHECK_QT5. dnl Outputs: All necessary QT_* variables are set. -dnl Outputs: bitcoin_qt_got_major_vers is set to "4" or "5". dnl Outputs: have_qt_test and have_qt_dbus are set (if applicable) to yes|no. AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG],[ TEMP_CPPFLAGS="$CPPFLAGS" TEMP_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$PIC_FLAGS $CXXFLAGS" TEMP_LIBS="$LIBS" BITCOIN_QT_CHECK([ if test "x$qt_include_path" != x; then QT_INCLUDES="-I$qt_include_path -I$qt_include_path/QtCore -I$qt_include_path/QtGui -I$qt_include_path/QtWidgets -I$qt_include_path/QtNetwork -I$qt_include_path/QtTest -I$qt_include_path/QtDBus" CPPFLAGS="$QT_INCLUDES $CPPFLAGS" fi ]) BITCOIN_QT_CHECK([AC_CHECK_HEADER([QtPlugin],,BITCOIN_QT_FAIL(QtCore headers missing))]) BITCOIN_QT_CHECK([AC_CHECK_HEADER([QApplication],, BITCOIN_QT_FAIL(QtGui headers missing))]) BITCOIN_QT_CHECK([AC_CHECK_HEADER([QLocalSocket],, BITCOIN_QT_FAIL(QtNetwork headers missing))]) BITCOIN_QT_CHECK([ if test "x$bitcoin_qt_want_version" = xauto; then _BITCOIN_QT_CHECK_QT5 fi - if test "x$bitcoin_cv_qt5" = xyes || test "x$bitcoin_qt_want_version" = xqt5; then - QT_LIB_PREFIX=Qt5 - bitcoin_qt_got_major_vers=5 - else - QT_LIB_PREFIX=Qt - bitcoin_qt_got_major_vers=4 - fi + QT_LIB_PREFIX=Qt5 ]) BITCOIN_QT_CHECK([ LIBS= if test "x$qt_lib_path" != x; then LIBS="$LIBS -L$qt_lib_path" fi if test "x$TARGET_OS" = xwindows; then AC_CHECK_LIB([imm32], [main],, BITCOIN_QT_FAIL(libimm32 not found)) fi ]) BITCOIN_QT_CHECK(AC_CHECK_LIB([z] ,[main],,AC_MSG_WARN([zlib not found. Assuming qt has it built-in]))) BITCOIN_QT_CHECK(AC_SEARCH_LIBS([png_error] ,[qtpng png],,AC_MSG_WARN([libpng not found. Assuming qt has it built-in]))) BITCOIN_QT_CHECK(AC_SEARCH_LIBS([jpeg_create_decompress] ,[qtjpeg jpeg],,AC_MSG_WARN([libjpeg not found. Assuming qt has it built-in]))) BITCOIN_QT_CHECK(AC_SEARCH_LIBS([pcre16_exec], [qtpcre pcre16],,AC_MSG_WARN([libpcre16 not found. Assuming qt has it built-in]))) BITCOIN_QT_CHECK(AC_SEARCH_LIBS([hb_ot_tags_from_script] ,[qtharfbuzzng harfbuzz],,AC_MSG_WARN([libharfbuzz not found. Assuming qt has it built-in or support is disabled]))) BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Core] ,[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Core not found))) BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Gui] ,[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Gui not found))) BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Network],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Network not found))) - if test "x$bitcoin_qt_got_major_vers" = x5; then - BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Widgets],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Widgets not found))) - fi + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Widgets],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Widgets not found))) QT_LIBS="$LIBS" LIBS="$TEMP_LIBS" BITCOIN_QT_CHECK([ LIBS= if test "x$qt_lib_path" != x; then LIBS="-L$qt_lib_path" fi AC_CHECK_LIB([${QT_LIB_PREFIX}Test], [main],, have_qt_test=no) AC_CHECK_HEADER([QTest],, have_qt_test=no) QT_TEST_LIBS="$LIBS" if test "x$use_dbus" != xno; then LIBS= if test "x$qt_lib_path" != x; then LIBS="-L$qt_lib_path" fi AC_CHECK_LIB([${QT_LIB_PREFIX}DBus], [main],, have_qt_dbus=no) AC_CHECK_HEADER([QtDBus],, have_qt_dbus=no) QT_DBUS_LIBS="$LIBS" fi ]) CPPFLAGS="$TEMP_CPPFLAGS" CXXFLAGS="$TEMP_CXXFLAGS" LIBS="$TEMP_LIBS" ]) diff --git a/configure.ac b/configure.ac index 7059f00fa..261d80ca3 100644 --- a/configure.ac +++ b/configure.ac @@ -1,1368 +1,1367 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 18) define(_CLIENT_VERSION_REVISION, 3) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2018) define(_COPYRIGHT_HOLDERS,[The %s developers]) define(_COPYRIGHT_HOLDERS_SUBSTITUTION,[[Bitcoin]]) AC_INIT([Bitcoin ABC],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_REVISION],[https://github.com/Bitcoin-ABC/bitcoin-abc/issues],[bitcoin-abc],[https://bitcoinabc.org/]) AC_CONFIG_SRCDIR([src/validation.cpp]) AC_CONFIG_HEADERS([src/config/bitcoin-config.h]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([build-aux/m4]) BITCOIN_DAEMON_NAME=bitcoind BITCOIN_GUI_NAME=bitcoin-qt BITCOIN_CLI_NAME=bitcoin-cli BITCOIN_TX_NAME=bitcoin-tx BITCOIN_SEEDER_NAME=bitcoin-seeder AC_CANONICAL_HOST AH_TOP([#ifndef BITCOIN_BITCOIN_CONFIG_H]) AH_TOP([#define BITCOIN_BITCOIN_CONFIG_H]) AH_BOTTOM([#endif // BITCOIN_BITCOIN_CONFIG_H]) dnl faketime breaks configure and is only needed for make. Disable it here. unset FAKETIME dnl Automake init set-up and checks AM_INIT_AUTOMAKE([no-define subdir-objects foreign]) dnl faketime messes with timestamps and causes configure to be re-run. dnl --disable-maintainer-mode can be used to bypass this. AM_MAINTAINER_MODE([enable]) dnl make the compilation flags quiet unless V=1 is used m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) dnl Compiler checks (here before libtool). if test "x${CXXFLAGS+set}" = "xset"; then CXXFLAGS_overridden=yes else CXXFLAGS_overridden=no fi AC_PROG_CXX dnl By default, libtool for mingw refuses to link static libs into a dll for dnl fear of mixing pic/non-pic objects, and import/export complications. Since dnl we have those under control, re-enable that functionality. case $host in *mingw*) lt_cv_deplibs_check_method="pass_all" ;; esac dnl Require C++11 compiler (no GNU extensions) AX_CXX_COMPILE_STDCXX([11], [noext], [mandatory], [nodefault]) dnl Check if -latomic is required for CHECK_ATOMIC dnl Unless the user specified OBJCXX, force it to be the same as CXX. This ensures dnl that we get the same -std flags for both. m4_ifdef([AC_PROG_OBJCXX],[ if test "x${OBJCXX+set}" = "x"; then OBJCXX="${CXX}" fi AC_PROG_OBJCXX ]) dnl Libtool init checks. LT_INIT([pic-only]) dnl Check/return PATH for base programs. AC_PATH_TOOL(AR, ar) AC_PATH_TOOL(RANLIB, ranlib) AC_PATH_TOOL(STRIP, strip) AC_PATH_TOOL(GCOV, gcov) AC_PATH_PROG(LCOV, lcov) dnl Python 3.x is supported from 3.4 on (see https://github.com/bitcoin/bitcoin/issues/7893) AC_PATH_PROGS([PYTHON], [python3.6 python3.5 python3.4 python3 python2.7 python2 python]) AC_PATH_PROG(GENHTML, genhtml) AC_PATH_PROG([GIT], [git]) AC_PATH_PROG(CCACHE,ccache) AC_PATH_PROG(XGETTEXT,xgettext) AC_PATH_PROG(HEXDUMP,hexdump) AC_PATH_TOOL(READELF, readelf) AC_PATH_TOOL(CPPFILT, c++filt) AC_PATH_TOOL(OBJCOPY, objcopy) AC_ARG_VAR(PYTHONPATH, Augments the default search path for python module files) # Enable wallet AC_ARG_ENABLE([wallet], [AS_HELP_STRING([--disable-wallet], [disable wallet (enabled by default)])], [enable_wallet=$enableval], [enable_wallet=yes]) AC_ARG_WITH([miniupnpc], [AS_HELP_STRING([--with-miniupnpc], [enable UPNP (default is yes if libminiupnpc is found)])], [use_upnp=$withval], [use_upnp=auto]) AC_ARG_ENABLE([upnp-default], [AS_HELP_STRING([--enable-upnp-default], [if UPNP is enabled, turn it on at startup (default is no)])], [use_upnp_default=$enableval], [use_upnp_default=no]) AC_ARG_ENABLE(tests, AS_HELP_STRING([--disable-tests],[do not compile tests (default is to compile)]), [use_tests=$enableval], [use_tests=yes]) AC_ARG_ENABLE(gui-tests, AS_HELP_STRING([--disable-gui-tests],[do not compile GUI tests (default is to compile if GUI and tests enabled)]), [use_gui_tests=$enableval], [use_gui_tests=$use_tests]) AC_ARG_ENABLE(bench, AS_HELP_STRING([--disable-bench],[do not compile benchmarks (default is to compile)]), [use_bench=$enableval], [use_bench=yes]) AC_ARG_ENABLE([extended-functional-tests], AS_HELP_STRING([--enable-extended-functional-tests],[enable expensive functional tests when using lcov (default no)]), [use_extended_functional_tests=$enableval], [use_extended_functional_tests=no]) AC_ARG_WITH([qrencode], [AS_HELP_STRING([--with-qrencode], [enable QR code support (default is yes if qt is enabled and libqrencode is found)])], [use_qr=$withval], [use_qr=auto]) AC_ARG_ENABLE([hardening], [AS_HELP_STRING([--disable-hardening], [do not attempt to harden the resulting executables (default is to harden)])], [use_hardening=$enableval], [use_hardening=yes]) AC_ARG_ENABLE([reduce-exports], [AS_HELP_STRING([--enable-reduce-exports], [attempt to reduce exported symbols in the resulting executables (default is no)])], [use_reduce_exports=$enableval], [use_reduce_exports=no]) AC_ARG_ENABLE([ccache], [AS_HELP_STRING([--disable-ccache], [do not use ccache for building (default is to use if found)])], [use_ccache=$enableval], [use_ccache=auto]) AC_ARG_ENABLE([lcov], [AS_HELP_STRING([--enable-lcov], [enable lcov testing (default is no)])], [use_lcov=yes], [use_lcov=no]) AC_ARG_ENABLE([glibc-back-compat], [AS_HELP_STRING([--enable-glibc-back-compat], [enable backwards compatibility with glibc])], [use_glibc_compat=$enableval], [use_glibc_compat=no]) AC_ARG_ENABLE([asm], [AS_HELP_STRING([--enable-asm], [Enable assembly routines (default is yes)])], [use_asm=$enableval], [use_asm=yes]) if test "x$use_asm" = xyes; then AC_DEFINE(USE_ASM, 1, [Define this symbol to build in assembly routines]) fi AC_ARG_WITH([system-univalue], [AS_HELP_STRING([--with-system-univalue], [Build with system UniValue (default is no)])], [system_univalue=$withval], [system_univalue=no] ) AC_ARG_ENABLE([zmq], [AS_HELP_STRING([--disable-zmq], [disable ZMQ notifications])], [use_zmq=$enableval], [use_zmq=yes]) AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], []) AC_ARG_ENABLE(man, [AS_HELP_STRING([--disable-man], [do not install man pages (default is to install)])],, enable_man=yes) AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) # Enable debug AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [use debug compiler flags and macros (default is no)])], [enable_debug=$enableval], [enable_debug=no]) # Enable ASAN AC_ARG_ENABLE([asan], [AS_HELP_STRING([--enable-asan], [enable address sanitizer compiler flags (implies --enable-debug, default is no)])], [enable_asan=$enableval], [enable_asan=no]) # Enable TSAN AC_ARG_ENABLE([tsan], [AS_HELP_STRING([--enable-tsan], [enable thread sanitizer compiler flags; requires 64-bit target architecture (implies --enable-debug, default is no)])], [enable_tsan=$enableval], [enable_tsan=no]) # Enable UBSAN AC_ARG_ENABLE([ubsan], [AS_HELP_STRING([--enable-ubsan], [enable undefined behavior sanitizer compiler flags (implies --enable-debug, default is no)])], [enable_ubsan=$enableval], [enable_ubsan=no]) # Turn warnings into errors AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], [Treat certain compiler warnings as errors (default is no)])], [enable_werror=$enableval], [enable_werror=no]) AC_LANG_PUSH([C++]) AX_CHECK_COMPILE_FLAG([-Werror],[CXXFLAG_WERROR="-Werror"],[CXXFLAG_WERROR=""]) if test "x$enable_asan" = xyes; then enable_debug=yes asan_failed=no AX_CHECK_COMPILE_FLAG([-fsanitize=address], [CXXFLAGS="$CXXFLAGS -fsanitize=address"], [asan_failed=yes]) AX_CHECK_LINK_FLAG([-fsanitize=address], [LDFLAGS="$LDFLAGS -fsanitize=address"], [asan_failed=yes]) AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer], [CXXFLAGS="$CXXFLAGS -fno-omit-frame-pointer"], [asan_failed=yes]) if test "x$asan_failed" = xyes; then AC_MSG_ERROR("ASAN is not supported") fi # fix linkage in MacOs [LIBTOOL_APP_LDFLAGS="$LIBTOOL_APP_LDFLAGS -fsanitize=address"], fi if test "x$enable_tsan" = xyes; then enable_debug=yes tsan_failed=no AX_CHECK_COMPILE_FLAG([-fsanitize=thread], [CXXFLAGS="$CXXFLAGS -fsanitize=thread"], [tsan_failed=yes]) AX_CHECK_LINK_FLAG([-fsanitize=thread], [LDFLAGS="$LDFLAGS -fsanitize=thread"], [tsan_failed=yes]) AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer], [CXXFLAGS="$CXXFLAGS -fno-omit-frame-pointer"], [tsan_failed=yes]) if test "x$tsan_failed" = xyes; then AC_MSG_ERROR("TSAN is not supported") fi fi if test "x$enable_ubsan" = xyes; then enable_debug=yes ubsan_failed=no AX_CHECK_COMPILE_FLAG([-fsanitize=undefined], [CXXFLAGS="$CXXFLAGS -fsanitize=undefined"], [ubsan_failed=yes]) AX_CHECK_LINK_FLAG([-fsanitize=undefined], [LDFLAGS="$LDFLAGS -fsanitize=undefined"], [ubsan_failed=yes]) AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer], [CXXFLAGS="$CXXFLAGS -fno-omit-frame-pointer"], [ubsan_failed=yes]) if test "x$ubsan_failed" = xyes; then AC_MSG_ERROR("UBSAN is not supported") fi fi if test "x$enable_debug" = xyes; then CPPFLAGS="$CPPFLAGS -DDEBUG -DDEBUG_LOCKORDER" if test "x$GCC" = xyes; then CFLAGS="$CFLAGS -g3 -O0" fi if test "x$GXX" = xyes; then CXXFLAGS="$CXXFLAGS -g3 -O0" fi fi ERROR_CXXFLAGS= if test "x$enable_werror" = "xyes"; then if test "x$CXXFLAG_WERROR" = "x"; then AC_MSG_ERROR("enable-werror set but -Werror is not usable") fi AX_CHECK_COMPILE_FLAG([-Werror=vla],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=vla"],,[[$CXXFLAG_WERROR]]) fi if test "x$CXXFLAGS_overridden" = "xno"; then AX_CHECK_COMPILE_FLAG([-Wall],[CXXFLAGS="$CXXFLAGS -Wall"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wextra],[CXXFLAGS="$CXXFLAGS -Wextra"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wformat],[CXXFLAGS="$CXXFLAGS -Wformat"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wvla],[CXXFLAGS="$CXXFLAGS -Wvla"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wformat-security],[CXXFLAGS="$CXXFLAGS -Wformat-security"],,[[$CXXFLAG_WERROR]]) ## Some compilers (gcc) ignore unknown -Wno-* options, but warn about all ## unknown options if any other warning is produced. Test the -Wfoo case, and ## set the -Wno-foo case if it works. AX_CHECK_COMPILE_FLAG([-Wunused-parameter],[CXXFLAGS="$CXXFLAGS -Wno-unused-parameter"],,[[$CXXFLAG_WERROR]]) fi # Check for optional instruction set support. Enabling these does _not_ imply that all code will # be compiled with them, rather that specific objects/libs may use them after checking for runtime # compatibility. AX_CHECK_COMPILE_FLAG([-msse4.2],[[SSE42_CXXFLAGS="-msse4.2"]],,[[$CXXFLAG_WERROR]]) TEMP_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $SSE42_CXXFLAGS" AC_MSG_CHECKING(for assembler crc32 support) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #if defined(_MSC_VER) #include #elif defined(__GNUC__) && defined(__SSE4_2__) #include #endif ]],[[ uint64_t l = 0; l = _mm_crc32_u8(l, 0); l = _mm_crc32_u32(l, 0); l = _mm_crc32_u64(l, 0); return l; ]])], [ AC_MSG_RESULT(yes); enable_hwcrc32=yes], [ AC_MSG_RESULT(no)] ) CXXFLAGS="$TEMP_CXXFLAGS" CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], [build bitcoin-cli bitcoin-tx (default=yes)])], [build_bitcoin_utils=$withval], [build_bitcoin_utils=yes]) AC_ARG_WITH([libs], [AS_HELP_STRING([--with-libs], [build libraries (default=yes)])], [build_bitcoin_libs=$withval], [build_bitcoin_libs=yes]) AC_ARG_WITH([daemon], [AS_HELP_STRING([--with-daemon], [build bitcoind daemon (default=yes)])], [build_bitcoind=$withval], [build_bitcoind=yes]) AC_ARG_WITH([seeder], [AS_HELP_STRING([--with-seeder], [build seeder (default=yes)])], [build_bitcoin_seeder=$withval], [build_bitcoin_seeder=yes]) use_pkgconfig=yes case $host in *mingw*) #pkgconfig does more harm than good with MinGW use_pkgconfig=no TARGET_OS=windows AC_CHECK_LIB([mingwthrd], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([kernel32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([user32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([gdi32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([comdlg32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([winspool], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([winmm], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([shell32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([comctl32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([ole32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([oleaut32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([uuid], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([rpcrt4], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([advapi32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([ws2_32], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([mswsock], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([shlwapi], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([iphlpapi], [main],, AC_MSG_ERROR(lib missing)) AC_CHECK_LIB([crypt32], [main],, AC_MSG_ERROR(lib missing)) # -static is interpreted by libtool, where it has a different meaning. # In libtool-speak, it's -all-static. AX_CHECK_LINK_FLAG([[-static]],[LIBTOOL_APP_LDFLAGS="$LIBTOOL_APP_LDFLAGS -all-static"]) AC_PATH_PROG([MAKENSIS], [makensis], none) if test x$MAKENSIS = xnone; then AC_MSG_WARN("makensis not found. Cannot create installer.") fi AC_PATH_TOOL(WINDRES, windres, none) if test x$WINDRES = xnone; then AC_MSG_ERROR("windres not found") fi CPPFLAGS="$CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -DBOOST_THREAD_USE_LIB" LEVELDB_TARGET_FLAGS="-DOS_WINDOWS" if test "x$CXXFLAGS_overridden" = "xno"; then CXXFLAGS="$CXXFLAGS -w" fi case $host in i?86-*) WINDOWS_BITS=32 ;; x86_64-*) WINDOWS_BITS=64 ;; *) AC_MSG_ERROR("Could not determine win32/win64 for installer") ;; esac AC_SUBST(WINDOWS_BITS) dnl libtool insists upon adding -nostdlib and a list of objects/libs to link against. dnl That breaks our ability to build dll's with static libgcc/libstdc++/libssp. Override dnl its command here, with the predeps/postdeps removed, and -static inserted. Postdeps are dnl also overridden to prevent their insertion later. dnl This should only affect dll's. archive_cmds_CXX="\$CC -shared \$libobjs \$deplibs \$compiler_flags -static -o \$output_objdir/\$soname \${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker \$lib" postdeps_CXX= ;; *darwin*) TARGET_OS=darwin LEVELDB_TARGET_FLAGS="-DOS_MACOSX" if test x$cross_compiling != xyes; then BUILD_OS=darwin AC_CHECK_PROG([PORT],port, port) if test x$PORT = xport; then dnl add default macports paths CPPFLAGS="$CPPFLAGS -isystem /opt/local/include" LIBS="$LIBS -L/opt/local/lib" if test -d /opt/local/include/db48; then CPPFLAGS="$CPPFLAGS -I/opt/local/include/db48" LIBS="$LIBS -L/opt/local/lib/db48" fi fi AC_PATH_PROGS([RSVG_CONVERT], [rsvg-convert rsvg],rsvg-convert) AC_CHECK_PROG([BREW],brew, brew) if test x$BREW = xbrew; then dnl These Homebrew packages may be keg-only, meaning that they won't be found dnl in expected paths because they may conflict with system files. Ask dnl Homebrew where each one is located, then adjust paths accordingly. dnl It's safe to add these paths even if the functionality is disabled by dnl the user (--without-wallet or --without-gui for example). openssl_prefix=`$BREW --prefix openssl 2>/dev/null` bdb_prefix=`$BREW --prefix berkeley-db 2>/dev/null` qt5_prefix=`$BREW --prefix qt5 2>/dev/null` if test x$openssl_prefix != x; then PKG_CONFIG_PATH="$openssl_prefix/lib/pkgconfig:$PKG_CONFIG_PATH" export PKG_CONFIG_PATH fi if test x$bdb_prefix != x; then CPPFLAGS="$CPPFLAGS -I$bdb_prefix/include" LIBS="$LIBS -L$bdb_prefix/lib" fi if test x$qt5_prefix != x; then PKG_CONFIG_PATH="$qt5_prefix/lib/pkgconfig:$PKG_CONFIG_PATH" export PKG_CONFIG_PATH fi fi else case $build_os in *darwin*) BUILD_OS=darwin ;; *) AC_PATH_TOOL([INSTALLNAMETOOL], [install_name_tool], install_name_tool) AC_PATH_TOOL([OTOOL], [otool], otool) AC_PATH_PROGS([GENISOIMAGE], [genisoimage mkisofs],genisoimage) AC_PATH_PROGS([RSVG_CONVERT], [rsvg-convert rsvg],rsvg-convert) AC_PATH_PROGS([IMAGEMAGICK_CONVERT], [convert],convert) AC_PATH_PROGS([TIFFCP], [tiffcp],tiffcp) dnl libtool will try to strip the static lib, which is a problem for dnl cross-builds because strip attempts to call a hard-coded ld, dnl which may not exist in the path. Stripping the .a is not dnl necessary, so just disable it. old_striplib= ;; esac fi AX_CHECK_LINK_FLAG([[-Wl,-headerpad_max_install_names]], [LDFLAGS="$LDFLAGS -Wl,-headerpad_max_install_names"]) CPPFLAGS="$CPPFLAGS -DMAC_OSX" OBJCXXFLAGS="$CXXFLAGS" ;; *linux*) TARGET_OS=linux LEVELDB_TARGET_FLAGS="-DOS_LINUX" ;; *freebsd*) LEVELDB_TARGET_FLAGS="-DOS_FREEBSD" ;; *openbsd*) LEVELDB_TARGET_FLAGS="-DOS_OPENBSD" ;; *) OTHER_OS=`echo ${host_os} | awk '{print toupper($0)}'` AC_MSG_WARN([Guessing LevelDB OS as OS_${OTHER_OS}, please check whether this is correct, if not add an entry to configure.ac.]) LEVELDB_TARGET_FLAGS="-DOS_${OTHER_OS}" ;; esac if test x$use_pkgconfig = xyes; then m4_ifndef([PKG_PROG_PKG_CONFIG], [AC_MSG_ERROR(PKG_PROG_PKG_CONFIG macro not found. Please install pkg-config and re-run autogen.sh.)]) m4_ifdef([PKG_PROG_PKG_CONFIG], [ PKG_PROG_PKG_CONFIG if test x"$PKG_CONFIG" = "x"; then AC_MSG_ERROR(pkg-config not found.) fi ]) fi if test x$use_extended_functional_tests != xno; then AC_SUBST(EXTENDED_FUNCTIONAL_TESTS, --extended) fi if test x$use_lcov = xyes; then if test x$LCOV = x; then AC_MSG_ERROR("lcov testing requested but lcov not found") fi if test x$GCOV = x; then AC_MSG_ERROR("lcov testing requested but gcov not found") fi if test x$PYTHON = x; then AC_MSG_ERROR("lcov testing requested but python not found") fi if test x$GENHTML = x; then AC_MSG_ERROR("lcov testing requested but genhtml not found") fi LCOV="$LCOV --gcov-tool=$GCOV" AX_CHECK_LINK_FLAG([[--coverage]], [LDFLAGS="$LDFLAGS --coverage"], [AC_MSG_ERROR("lcov testing requested but --coverage linker flag does not work")]) AX_CHECK_COMPILE_FLAG([--coverage],[CXXFLAGS="$CXXFLAGS --coverage"], [AC_MSG_ERROR("lcov testing requested but --coverage flag does not work")]) fi dnl Check for endianness AC_C_BIGENDIAN dnl Check for pthread compile/link requirements AX_PTHREAD # The following macro will add the necessary defines to bitcoin-config.h, but # they also need to be passed down to any subprojects. Pull the results out of # the cache and add them to CPPFLAGS. AC_SYS_LARGEFILE # detect POSIX or GNU variant of strerror_r AC_FUNC_STRERROR_R if test x$ac_cv_sys_file_offset_bits != x && test x$ac_cv_sys_file_offset_bits != xno && test x$ac_cv_sys_file_offset_bits != xunknown; then CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" fi if test x$ac_cv_sys_large_files != x && test x$ac_cv_sys_large_files != xno && test x$ac_cv_sys_large_files != xunknown; then CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=$ac_cv_sys_large_files" fi AX_CHECK_LINK_FLAG([[-Wl,--large-address-aware]], [LDFLAGS="$LDFLAGS -Wl,--large-address-aware"]) AX_GCC_FUNC_ATTRIBUTE([visibility]) AX_GCC_FUNC_ATTRIBUTE([dllexport]) AX_GCC_FUNC_ATTRIBUTE([dllimport]) if test x$use_glibc_compat != xno; then #glibc absorbed clock_gettime in 2.17. librt (its previous location) is safe to link #in anyway for back-compat. AC_CHECK_LIB([rt],[clock_gettime],, AC_MSG_ERROR(lib missing)) #__fdelt_chk's params and return type have changed from long unsigned int to long int. # See which one is present here. AC_MSG_CHECKING(__fdelt_chk type) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#ifdef _FORTIFY_SOURCE #undef _FORTIFY_SOURCE #endif #define _FORTIFY_SOURCE 2 #include extern "C" long unsigned int __fdelt_warn(long unsigned int);]],[[]])], [ fdelt_type="long unsigned int"], [ fdelt_type="long int"]) AC_MSG_RESULT($fdelt_type) AC_DEFINE_UNQUOTED(FDELT_TYPE, $fdelt_type,[parameter and return value type for __fdelt_chk]) else AC_SEARCH_LIBS([clock_gettime],[rt]) fi if test x$TARGET_OS != xwindows; then # All windows code is PIC, forcing it on just adds useless compile warnings AX_CHECK_COMPILE_FLAG([-fPIC],[PIC_FLAGS="-fPIC"]) fi if test x$use_hardening != xno; then AX_CHECK_COMPILE_FLAG([-Wstack-protector],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -Wstack-protector"]) AX_CHECK_COMPILE_FLAG([-fstack-protector-all],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-protector-all"]) AX_CHECK_PREPROC_FLAG([-D_FORTIFY_SOURCE=2],[ AX_CHECK_PREPROC_FLAG([-U_FORTIFY_SOURCE],[ HARDENED_CPPFLAGS="$HARDENED_CPPFLAGS -U_FORTIFY_SOURCE" ]) HARDENED_CPPFLAGS="$HARDENED_CPPFLAGS -D_FORTIFY_SOURCE=2" ]) AX_CHECK_LINK_FLAG([[-Wl,--dynamicbase]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--dynamicbase"]) AX_CHECK_LINK_FLAG([[-Wl,--nxcompat]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--nxcompat"]) AX_CHECK_LINK_FLAG([[-Wl,--high-entropy-va]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--high-entropy-va"]) AX_CHECK_LINK_FLAG([[-Wl,-z,relro]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,relro"]) AX_CHECK_LINK_FLAG([[-Wl,-z,now]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,now"]) if test x$TARGET_OS != xwindows; then AX_CHECK_COMPILE_FLAG([-fPIE],[PIE_FLAGS="-fPIE"]) AX_CHECK_LINK_FLAG([[-pie]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -pie"]) fi case $host in *mingw*) AC_CHECK_LIB([ssp], [main],, AC_MSG_ERROR(lib missing)) ;; esac fi dnl this flag screws up non-darwin gcc even when the check fails. special-case it. if test x$TARGET_OS = xdarwin; then AX_CHECK_LINK_FLAG([[-Wl,-dead_strip]], [LDFLAGS="$LDFLAGS -Wl,-dead_strip"]) fi AC_CHECK_HEADERS([endian.h sys/endian.h byteswap.h stdio.h stdlib.h unistd.h strings.h sys/types.h sys/stat.h sys/select.h sys/prctl.h]) AC_CHECK_DECLS([strnlen]) # Check for daemon(3), unrelated to --with-daemon (although used by it) AC_CHECK_DECLS([daemon]) AC_CHECK_DECLS([le16toh, le32toh, le64toh, htole16, htole32, htole64, be16toh, be32toh, be64toh, htobe16, htobe32, htobe64],,, [#if HAVE_ENDIAN_H #include #elif HAVE_SYS_ENDIAN_H #include #endif]) AC_CHECK_DECLS([bswap_16, bswap_32, bswap_64],,, [#if HAVE_BYTESWAP_H #include #endif]) AC_CHECK_DECLS([__builtin_clz, __builtin_clzl, __builtin_clzll]) dnl Check for MSG_NOSIGNAL AC_MSG_CHECKING(for MSG_NOSIGNAL) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ int f = MSG_NOSIGNAL; ]])], [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_MSG_NOSIGNAL, 1,[Define this symbol if you have MSG_NOSIGNAL]) ], [ AC_MSG_RESULT(no)] ) dnl Check for mallopt(M_ARENA_MAX) (to set glibc arenas) AC_MSG_CHECKING(for mallopt M_ARENA_MAX) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ mallopt(M_ARENA_MAX, 1); ]])], [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_MALLOPT_ARENA_MAX, 1,[Define this symbol if you have mallopt with M_ARENA_MAX]) ], [ AC_MSG_RESULT(no)] ) AC_MSG_CHECKING([for visibility attribute]) AC_LINK_IFELSE([AC_LANG_SOURCE([ int foo_def( void ) __attribute__((visibility("default"))); int main(){} ])], [ AC_DEFINE(HAVE_VISIBILITY_ATTRIBUTE,1,[Define if the visibility attribute is supported.]) AC_MSG_RESULT(yes) ], [ AC_MSG_RESULT(no) if test x$use_reduce_exports = xyes; then AC_MSG_ERROR([Cannot find a working visibility attribute. Use --disable-reduce-exports.]) fi ] ) # Check for different ways of gathering OS randomness AC_MSG_CHECKING(for Linux getrandom syscall) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include #include ]], [[ syscall(SYS_getrandom, nullptr, 32, 0); ]])], [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_SYS_GETRANDOM, 1,[Define this symbol if the Linux getrandom system call is available]) ], [ AC_MSG_RESULT(no)] ) AC_MSG_CHECKING(for getentropy) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ getentropy(nullptr, 32) ]])], [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_GETENTROPY, 1,[Define this symbol if the BSD getentropy system call is available]) ], [ AC_MSG_RESULT(no)] ) AC_MSG_CHECKING(for getentropy via random.h) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[ getentropy(nullptr, 32) ]])], [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_GETENTROPY_RAND, 1,[Define this symbol if the BSD getentropy system call is available with sys/random.h]) ], [ AC_MSG_RESULT(no)] ) AC_MSG_CHECKING(for sysctl KERN_ARND) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[ static const int name[2] = {CTL_KERN, KERN_ARND}; sysctl(name, 2, nullptr, nullptr, nullptr, 0); ]])], [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_SYSCTL_ARND, 1,[Define this symbol if the BSD sysctl(KERN_ARND) is available]) ], [ AC_MSG_RESULT(no)] ) # Check for reduced exports if test x$use_reduce_exports = xyes; then AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[RE_CXXFLAGS="-fvisibility=hidden"], [AC_MSG_ERROR([Cannot set default symbol visibility. Use --disable-reduce-exports.])]) fi LEVELDB_CPPFLAGS= LIBLEVELDB= LIBMEMENV= AM_CONDITIONAL([EMBEDDED_LEVELDB],[true]) AC_SUBST(LEVELDB_CPPFLAGS) AC_SUBST(LIBLEVELDB) AC_SUBST(LIBMEMENV) if test x$enable_wallet != xno; then dnl Check for libdb_cxx only if wallet enabled BITCOIN_FIND_BDB53 fi dnl Check for libminiupnpc (optional) if test x$use_upnp != xno; then AC_CHECK_HEADERS( [miniupnpc/miniwget.h miniupnpc/miniupnpc.h miniupnpc/upnpcommands.h miniupnpc/upnperrors.h], [AC_CHECK_LIB([miniupnpc], [main],[MINIUPNPC_LIBS=-lminiupnpc], [have_miniupnpc=no])], [have_miniupnpc=no] ) fi BITCOIN_QT_INIT dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus -BITCOIN_QT_CONFIGURE([$use_pkgconfig], [qt5]) +BITCOIN_QT_CONFIGURE([$use_pkgconfig]) if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$build_bitcoin_seeder$use_tests$use_bench = xnononononono; then use_boost=no else use_boost=yes fi if test x$use_boost = xyes; then dnl Minimum required Boost version define(MINIMUM_REQUIRED_BOOST, 1.58.0) dnl Check for boost libs AX_BOOST_BASE([MINIMUM_REQUIRED_BOOST]) AX_BOOST_SYSTEM AX_BOOST_FILESYSTEM AX_BOOST_PROGRAM_OPTIONS AX_BOOST_THREAD AX_BOOST_CHRONO dnl Boost 1.56 through 1.62 allow using std::atomic instead of its own atomic dnl counter implementations. In 1.63 and later the std::atomic approach is default. m4_pattern_allow(DBOOST_AC_USE_STD_ATOMIC) dnl otherwise it's treated like a macro BOOST_CPPFLAGS="-DBOOST_SP_USE_STD_ATOMIC -DBOOST_AC_USE_STD_ATOMIC $BOOST_CPPFLAGS" if test x$use_reduce_exports = xyes; then AC_MSG_CHECKING([for working boost reduced exports]) TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ #if BOOST_VERSION >= 104900 // Everything is okay #else # error Boost version is too old #endif ]])],[ AC_MSG_RESULT(yes) ],[ AC_MSG_ERROR([boost versions < 1.49 are known to be broken with reduced exports. Use --disable-reduce-exports.]) ]) CPPFLAGS="$TEMP_CPPFLAGS" fi fi if test x$use_reduce_exports = xyes; then CXXFLAGS="$CXXFLAGS $RE_CXXFLAGS" AX_CHECK_LINK_FLAG([[-Wl,--exclude-libs,ALL]], [RELDFLAGS="-Wl,--exclude-libs,ALL"]) fi if test x$use_tests = xyes; then if test x$HEXDUMP = x; then AC_MSG_ERROR(hexdump is required for tests) fi if test x$use_boost = xyes; then AX_BOOST_UNIT_TEST_FRAMEWORK dnl Determine if -DBOOST_TEST_DYN_LINK is needed AC_MSG_CHECKING([for dynamic linked boost test]) TEMP_LIBS="$LIBS" LIBS="$LIBS $BOOST_LDFLAGS $BOOST_UNIT_TEST_FRAMEWORK_LIB" TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_LINK_IFELSE([AC_LANG_SOURCE([ #define BOOST_TEST_DYN_LINK #define BOOST_TEST_MAIN #include ])], [AC_MSG_RESULT(yes)] [TESTDEFS="$TESTDEFS -DBOOST_TEST_DYN_LINK"], [AC_MSG_RESULT(no)]) LIBS="$TEMP_LIBS" CPPFLAGS="$TEMP_CPPFLAGS" fi fi if test x$use_boost = xyes; then BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB $BOOST_PROGRAM_OPTIONS_LIB $BOOST_THREAD_LIB $BOOST_CHRONO_LIB" dnl If boost (prior to 1.57) was built without c++11, it emulated scoped enums dnl using c++98 constructs. Unfortunately, this implementation detail leaked into dnl the abi. This was fixed in 1.57. dnl When building against that installed version using c++11, the headers pick up dnl on the native c++11 scoped enum support and enable it, however it will fail to dnl link. This can be worked around by disabling c++11 scoped enums if linking will dnl fail. dnl BOOST_NO_SCOPED_ENUMS was changed to BOOST_NO_CXX11_SCOPED_ENUMS in 1.51. TEMP_LIBS="$LIBS" LIBS="$BOOST_LIBS $LIBS" TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_MSG_CHECKING([for mismatched boost c++11 scoped enums]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #if !defined(BOOST_NO_SCOPED_ENUMS) && !defined(BOOST_NO_CXX11_SCOPED_ENUMS) && BOOST_VERSION < 105700 #define BOOST_NO_SCOPED_ENUMS #define BOOST_NO_CXX11_SCOPED_ENUMS #define CHECK #endif #include ]],[[ #if defined(CHECK) boost::filesystem::copy_file("foo", "bar"); #else choke; #endif ]])], [AC_MSG_RESULT(mismatched); BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_NO_SCOPED_ENUMS -DBOOST_NO_CXX11_SCOPED_ENUMS"], [AC_MSG_RESULT(ok)]) LIBS="$TEMP_LIBS" CPPFLAGS="$TEMP_CPPFLAGS" dnl Boost >= 1.50 uses sleep_for rather than the now-deprecated sleep, however dnl it was broken from 1.50 to 1.52 when backed by nanosleep. Use sleep_for if dnl a working version is available, else fall back to sleep. sleep was removed dnl after 1.56. dnl If neither is available, abort. TEMP_LIBS="$LIBS" LIBS="$BOOST_LIBS $LIBS" TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include ]],[[ #if BOOST_VERSION >= 105000 && (!defined(BOOST_HAS_NANOSLEEP) || BOOST_VERSION >= 105200) boost::this_thread::sleep_for(boost::chrono::milliseconds(0)); #else choke me #endif ]])], [boost_sleep=yes; AC_DEFINE(HAVE_WORKING_BOOST_SLEEP_FOR, 1, [Define this symbol if boost sleep_for works])], [boost_sleep=no]) LIBS="$TEMP_LIBS" CPPFLAGS="$TEMP_CPPFLAGS" if test x$boost_sleep != xyes; then TEMP_LIBS="$LIBS" LIBS="$BOOST_LIBS $LIBS" TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]],[[ #if BOOST_VERSION <= 105600 boost::this_thread::sleep(boost::posix_time::milliseconds(0)); #else choke me #endif ]])], [boost_sleep=yes; AC_DEFINE(HAVE_WORKING_BOOST_SLEEP, 1, [Define this symbol if boost sleep works])], [boost_sleep=no]) LIBS="$TEMP_LIBS" CPPFLAGS="$TEMP_CPPFLAGS" fi if test x$boost_sleep != xyes; then AC_MSG_ERROR(No working boost sleep implementation found.) fi fi if test x$use_pkgconfig = xyes; then : dnl m4_ifdef( [PKG_CHECK_MODULES], [ PKG_CHECK_MODULES([SSL], [libssl],, [AC_MSG_ERROR(openssl not found.)]) PKG_CHECK_MODULES([CRYPTO], [libcrypto],,[AC_MSG_ERROR(libcrypto not found.)]) BITCOIN_QT_CHECK([PKG_CHECK_MODULES([PROTOBUF], [protobuf], [have_protobuf=yes], [BITCOIN_QT_FAIL(libprotobuf not found)])]) if test x$use_qr != xno; then BITCOIN_QT_CHECK([PKG_CHECK_MODULES([QR], [libqrencode], [have_qrencode=yes], [have_qrencode=no])]) fi if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then PKG_CHECK_MODULES([EVENT], [libevent],, [AC_MSG_ERROR(libevent not found.)]) if test x$TARGET_OS != xwindows; then PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads],, [AC_MSG_ERROR(libevent_pthreads not found.)]) fi fi if test "x$use_zmq" = "xyes"; then PKG_CHECK_MODULES([ZMQ],[libzmq >= 4], [AC_DEFINE([ENABLE_ZMQ],[1],[Define to 1 to enable ZMQ functions])], [AC_DEFINE([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions]) AC_MSG_WARN([libzmq version 4.x or greater not found, disabling]) use_zmq=no]) else AC_DEFINE_UNQUOTED([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions]) fi ] ) else AC_CHECK_HEADER([openssl/crypto.h],,AC_MSG_ERROR(libcrypto headers missing)) AC_CHECK_LIB([crypto], [main],CRYPTO_LIBS=-lcrypto, AC_MSG_ERROR(libcrypto missing)) AC_CHECK_HEADER([openssl/ssl.h],, AC_MSG_ERROR(libssl headers missing),) AC_CHECK_LIB([ssl], [main],SSL_LIBS=-lssl, AC_MSG_ERROR(libssl missing)) if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then AC_CHECK_HEADER([event2/event.h],, AC_MSG_ERROR(libevent headers missing),) AC_CHECK_LIB([event],[main],EVENT_LIBS=-levent,AC_MSG_ERROR(libevent missing)) if test x$TARGET_OS != xwindows; then AC_CHECK_LIB([event_pthreads],[main],EVENT_PTHREADS_LIBS=-levent_pthreads,AC_MSG_ERROR(libevent_pthreads missing)) fi fi if test "x$use_zmq" = "xyes"; then AC_CHECK_HEADER([zmq.h], [AC_DEFINE([ENABLE_ZMQ],[1],[Define to 1 to enable ZMQ functions])], [AC_MSG_WARN([zmq.h not found, disabling zmq support]) use_zmq=no AC_DEFINE([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions])]) AC_CHECK_LIB([zmq],[zmq_ctx_shutdown],ZMQ_LIBS=-lzmq, [AC_MSG_WARN([libzmq >= 4.0 not found, disabling zmq support]) use_zmq=no AC_DEFINE([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions])]) else AC_DEFINE_UNQUOTED([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions]) fi if test "x$use_zmq" = "xyes"; then dnl Assume libzmq was built for static linking case $host in *mingw*) ZMQ_CFLAGS="$ZMQ_CFLAGS -DZMQ_STATIC" ;; esac fi BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], BITCOIN_QT_FAIL(libprotobuf not found))) if test x$use_qr != xno; then BITCOIN_QT_CHECK([AC_CHECK_LIB([qrencode], [main],[QR_LIBS=-lqrencode], [have_qrencode=no])]) BITCOIN_QT_CHECK([AC_CHECK_HEADER([qrencode.h],, have_qrencode=no)]) fi fi save_CXXFLAGS="${CXXFLAGS}" CXXFLAGS="${CXXFLAGS} ${CRYPTO_CFLAGS} ${SSL_CFLAGS}" AC_CHECK_DECLS([EVP_MD_CTX_new],,,[AC_INCLUDES_DEFAULT #include ]) CXXFLAGS="${save_CXXFLAGS}" dnl univalue check need_bundled_univalue=yes if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononono; then need_bundled_univalue=no else if test x$system_univalue != xno ; then found_univalue=no if test x$use_pkgconfig = xyes; then : #NOP m4_ifdef( [PKG_CHECK_MODULES], [ PKG_CHECK_MODULES([UNIVALUE],[libunivalue],[found_univalue=yes],[true]) ] ) else AC_CHECK_HEADER([univalue.h],[ AC_CHECK_LIB([univalue], [main],[ UNIVALUE_LIBS=-lunivalue found_univalue=yes ],[true]) ],[true]) fi if test x$found_univalue = xyes ; then system_univalue=yes need_bundled_univalue=no elif test x$system_univalue = xyes ; then AC_MSG_ERROR([univalue not found]) else system_univalue=no fi fi if test x$need_bundled_univalue = xyes ; then UNIVALUE_CFLAGS='-I$(srcdir)/univalue/include' UNIVALUE_LIBS='univalue/libunivalue.la' fi fi AM_CONDITIONAL([EMBEDDED_UNIVALUE],[test x$need_bundled_univalue = xyes]) AC_SUBST(UNIVALUE_CFLAGS) AC_SUBST(UNIVALUE_LIBS) BITCOIN_QT_PATH_PROGS([PROTOC], [protoc],$protoc_bin_path) AC_MSG_CHECKING([whether to build bitcoind]) AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes]) AC_MSG_RESULT($build_bitcoind) AC_MSG_CHECKING([whether to build bitcoin-seeder]) AM_CONDITIONAL([BUILD_BITCOIN_SEEDER], [test x$build_bitcoin_seeder = xyes]) AC_MSG_RESULT($build_bitcoin_seeder) AC_MSG_CHECKING([whether to build utils (bitcoin-cli bitcoin-tx)]) AM_CONDITIONAL([BUILD_BITCOIN_UTILS], [test x$build_bitcoin_utils = xyes]) AC_MSG_RESULT($build_bitcoin_utils) AC_MSG_CHECKING([whether to build libraries]) AM_CONDITIONAL([BUILD_BITCOIN_LIBS], [test x$build_bitcoin_libs = xyes]) if test x$build_bitcoin_libs = xyes; then AC_DEFINE(HAVE_CONSENSUS_LIB, 1, [Define this symbol if the consensus lib has been built]) AC_CONFIG_FILES([libbitcoinconsensus.pc:libbitcoinconsensus.pc.in]) fi AC_MSG_RESULT($build_bitcoin_libs) AC_LANG_POP if test "x$use_ccache" != "xno"; then AC_MSG_CHECKING(if ccache should be used) if test x$CCACHE = x; then if test "x$use_ccache" = "xyes"; then AC_MSG_ERROR([ccache not found.]); else use_ccache=no fi else use_ccache=yes CC="$ac_cv_path_CCACHE $CC" CXX="$ac_cv_path_CCACHE $CXX" fi AC_MSG_RESULT($use_ccache) fi if test "x$use_ccache" = "xyes"; then AX_CHECK_PREPROC_FLAG([-Qunused-arguments],[CPPFLAGS="-Qunused-arguments $CPPFLAGS"]) fi dnl enable wallet AC_MSG_CHECKING([if wallet should be enabled]) if test x$enable_wallet != xno; then AC_MSG_RESULT(yes) AC_DEFINE_UNQUOTED([ENABLE_WALLET],[1],[Define to 1 to enable wallet functions]) else AC_MSG_RESULT(no) fi dnl enable upnp support AC_MSG_CHECKING([whether to build with support for UPnP]) if test x$have_miniupnpc = xno; then if test x$use_upnp = xyes; then AC_MSG_ERROR("UPnP requested but cannot be built. use --without-miniupnpc") fi AC_MSG_RESULT(no) else if test x$use_upnp != xno; then AC_MSG_RESULT(yes) AC_MSG_CHECKING([whether to build with UPnP enabled by default]) use_upnp=yes upnp_setting=0 if test x$use_upnp_default != xno; then use_upnp_default=yes upnp_setting=1 fi AC_MSG_RESULT($use_upnp_default) AC_DEFINE_UNQUOTED([USE_UPNP],[$upnp_setting],[UPnP support not compiled if undefined, otherwise value (0 or 1) determines default state]) if test x$TARGET_OS = xwindows; then MINIUPNPC_CPPFLAGS="-DSTATICLIB -DMINIUPNP_STATICLIB" fi else AC_MSG_RESULT(no) fi fi dnl these are only used when qt is enabled BUILD_TEST_QT="" if test x$bitcoin_enable_qt != xno; then dnl enable dbus support AC_MSG_CHECKING([whether to build GUI with support for D-Bus]) if test x$bitcoin_enable_qt_dbus != xno; then AC_DEFINE([USE_DBUS],[1],[Define if dbus support should be compiled in]) fi AC_MSG_RESULT($bitcoin_enable_qt_dbus) dnl enable qr support AC_MSG_CHECKING([whether to build GUI with support for QR codes]) if test x$have_qrencode = xno; then if test x$use_qr = xyes; then AC_MSG_ERROR("QR support requested but cannot be built. use --without-qrencode") fi AC_MSG_RESULT(no) else if test x$use_qr != xno; then AC_MSG_RESULT(yes) AC_DEFINE([USE_QRCODE],[1],[Define if QR support should be compiled in]) use_qr=yes else AC_MSG_RESULT(no) fi fi if test x$XGETTEXT = x; then AC_MSG_WARN("xgettext is required to update qt translations") fi AC_MSG_CHECKING([whether to build test_bitcoin-qt]) if test x$use_gui_tests$bitcoin_enable_qt_test = xyesyes; then AC_MSG_RESULT([yes]) BUILD_TEST_QT="yes" else AC_MSG_RESULT([no]) fi fi AM_CONDITIONAL([ENABLE_ZMQ], [test "x$use_zmq" = "xyes"]) AC_MSG_CHECKING([whether to build test_bitcoin]) if test x$use_tests = xyes; then AC_MSG_RESULT([yes]) BUILD_TEST="yes" else AC_MSG_RESULT([no]) BUILD_TEST="" fi AC_MSG_CHECKING([whether to reduce exports]) if test x$use_reduce_exports = xyes; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi if test x$build_bitcoin_utils$build_bitcoin_libs$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests = xnononononono; then AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-seeder --with-gui --enable-bench or --enable-tests]) fi AM_CONDITIONAL([TARGET_DARWIN], [test x$TARGET_OS = xdarwin]) AM_CONDITIONAL([BUILD_DARWIN], [test x$BUILD_OS = xdarwin]) AM_CONDITIONAL([TARGET_WINDOWS], [test x$TARGET_OS = xwindows]) AM_CONDITIONAL([ENABLE_WALLET],[test x$enable_wallet = xyes]) AM_CONDITIONAL([ENABLE_TESTS],[test x$BUILD_TEST = xyes]) AM_CONDITIONAL([ENABLE_QT],[test x$bitcoin_enable_qt = xyes]) AM_CONDITIONAL([ENABLE_QT_TESTS],[test x$BUILD_TEST_QT = xyes]) AM_CONDITIONAL([ENABLE_BENCH],[test x$use_bench = xyes]) AM_CONDITIONAL([USE_QRCODE], [test x$use_qr = xyes]) AM_CONDITIONAL([USE_LCOV],[test x$use_lcov = xyes]) AM_CONDITIONAL([GLIBC_BACK_COMPAT],[test x$use_glibc_compat = xyes]) AM_CONDITIONAL([HARDEN],[test x$use_hardening = xyes]) AM_CONDITIONAL([ENABLE_HWCRC32],[test x$enable_hwcrc32 = xyes]) AM_CONDITIONAL([USE_ASM],[test x$use_asm = xyes]) AC_DEFINE(CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MAJOR, [Major version]) AC_DEFINE(CLIENT_VERSION_MINOR, _CLIENT_VERSION_MINOR, [Minor version]) AC_DEFINE(CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION, [Build revision]) AC_DEFINE(CLIENT_VERSION_BUILD, _CLIENT_VERSION_BUILD, [Version Build]) AC_DEFINE(CLIENT_VERSION_IS_RELEASE, _CLIENT_VERSION_IS_RELEASE, [Version is release]) AC_DEFINE(COPYRIGHT_YEAR, _COPYRIGHT_YEAR, [Copyright year]) AC_DEFINE(COPYRIGHT_HOLDERS, "_COPYRIGHT_HOLDERS", [Copyright holder(s) before %s replacement]) AC_DEFINE(COPYRIGHT_HOLDERS_SUBSTITUTION, "_COPYRIGHT_HOLDERS_SUBSTITUTION", [Replacement for %s in copyright holders string]) define(_COPYRIGHT_HOLDERS_FINAL, [patsubst(_COPYRIGHT_HOLDERS, [%s], [_COPYRIGHT_HOLDERS_SUBSTITUTION])]) AC_DEFINE(COPYRIGHT_HOLDERS_FINAL, "_COPYRIGHT_HOLDERS_FINAL", [Copyright holder(s)]) AC_SUBST(CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MAJOR) AC_SUBST(CLIENT_VERSION_MINOR, _CLIENT_VERSION_MINOR) AC_SUBST(CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION) AC_SUBST(CLIENT_VERSION_BUILD, _CLIENT_VERSION_BUILD) AC_SUBST(CLIENT_VERSION_IS_RELEASE, _CLIENT_VERSION_IS_RELEASE) AC_SUBST(COPYRIGHT_YEAR, _COPYRIGHT_YEAR) AC_SUBST(COPYRIGHT_HOLDERS, "_COPYRIGHT_HOLDERS") AC_SUBST(COPYRIGHT_HOLDERS_SUBSTITUTION, "_COPYRIGHT_HOLDERS_SUBSTITUTION") AC_SUBST(COPYRIGHT_HOLDERS_FINAL, "_COPYRIGHT_HOLDERS_FINAL") AC_SUBST(BITCOIN_DAEMON_NAME) AC_SUBST(BITCOIN_GUI_NAME) AC_SUBST(BITCOIN_CLI_NAME) AC_SUBST(BITCOIN_TX_NAME) AC_SUBST(BITCOIN_SEEDER_NAME) AC_SUBST(RELDFLAGS) AC_SUBST(ERROR_CXXFLAGS) AC_SUBST(HARDENED_CXXFLAGS) AC_SUBST(HARDENED_CPPFLAGS) AC_SUBST(HARDENED_LDFLAGS) AC_SUBST(PIC_FLAGS) AC_SUBST(PIE_FLAGS) AC_SUBST(SSE42_CXXFLAGS) AC_SUBST(LIBTOOL_APP_LDFLAGS) AC_SUBST(USE_UPNP) AC_SUBST(USE_QRCODE) AC_SUBST(BOOST_LIBS) AC_SUBST(TESTDEFS) AC_SUBST(LEVELDB_TARGET_FLAGS) AC_SUBST(MINIUPNPC_CPPFLAGS) AC_SUBST(MINIUPNPC_LIBS) AC_SUBST(CRYPTO_LIBS) AC_SUBST(SSL_LIBS) AC_SUBST(EVENT_LIBS) AC_SUBST(EVENT_PTHREADS_LIBS) AC_SUBST(ZMQ_LIBS) AC_SUBST(PROTOBUF_LIBS) AC_SUBST(QR_LIBS) AC_CONFIG_FILES([Makefile src/Makefile doc/man/Makefile share/setup.nsi share/qt/Info.plist test/config.ini]) AC_CONFIG_FILES([contrib/devtools/split-debug.sh],[chmod +x contrib/devtools/split-debug.sh]) AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py]) AC_CONFIG_LINKS([test/util/bitcoin-util-test.py:test/util/bitcoin-util-test.py]) dnl boost's m4 checks do something really nasty: they export these vars. As a dnl result, they leak into secp256k1's configure and crazy things happen. dnl Until this is fixed upstream and we've synced, we'll just un-export them. CPPFLAGS_TEMP="$CPPFLAGS" unset CPPFLAGS CPPFLAGS="$CPPFLAGS_TEMP" LDFLAGS_TEMP="$LDFLAGS" unset LDFLAGS LDFLAGS="$LDFLAGS_TEMP" LIBS_TEMP="$LIBS" unset LIBS LIBS="$LIBS_TEMP" PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH" unset PKG_CONFIG_PATH PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP" PKGCONFIG_LIBDIR_TEMP="$PKG_CONFIG_LIBDIR" unset PKG_CONFIG_LIBDIR PKG_CONFIG_LIBDIR="$PKGCONFIG_LIBDIR_TEMP" if test x$need_bundled_univalue = xyes; then AC_CONFIG_SUBDIRS([src/univalue]) fi ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --enable-module-multiset --disable-jni" AC_CONFIG_SUBDIRS([src/secp256k1]) AC_OUTPUT dnl Taken from https://wiki.debian.org/RpathIssue case $host in *-*-linux-gnu) AC_MSG_RESULT([Fixing libtool for -rpath problems.]) sed < libtool > libtool-2 \ 's/^hardcode_libdir_flag_spec.*$'/'hardcode_libdir_flag_spec=" -D__LIBTOOL_IS_A_FOOL__ "/' mv libtool-2 libtool chmod 755 libtool ;; esac dnl Replace the BUILDDIR path with the correct Windows path if compiling on Native Windows case ${OS} in *Windows*) sed 's/BUILDDIR="\/\([[a-z]]\)/BUILDDIR="\1:/' test/config.ini > test/config-2.ini mv test/config-2.ini test/config.ini ;; esac echo echo "Options used to compile and link:" echo " with wallet = $enable_wallet" echo " with gui / qt = $bitcoin_enable_qt" if test x$bitcoin_enable_qt != xno; then - echo " qt version = $bitcoin_qt_got_major_vers" echo " with qr = $use_qr" fi echo " with zmq = $use_zmq" echo " with test = $use_tests" echo " with bench = $use_bench" echo " with upnp = $use_upnp" echo " use asm = $use_asm" echo " debug enabled = $enable_debug" echo " werror = $enable_werror" echo echo " sanitizers " echo " asan = $enable_asan" echo " tsan = $enable_tsan" echo " ubsan = $enable_ubsan" echo echo " target os = $TARGET_OS" echo " build os = $BUILD_OS" echo echo " CC = $CC" echo " CFLAGS = $CFLAGS" echo " CPPFLAGS = $CPPFLAGS" echo " CXX = $CXX" echo " CXXFLAGS = $CXXFLAGS" echo " LDFLAGS = $LDFLAGS" echo diff --git a/doc/build-unix.md b/doc/build-unix.md index f5ba561ab..4bf09eec4 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -1,302 +1,297 @@ UNIX BUILD NOTES ==================== Some notes on how to build Bitcoin ABC in Unix. (for OpenBSD specific instructions, see [build-openbsd.md](build-openbsd.md)) Note --------------------- Always use absolute paths to configure and compile bitcoin and the dependencies, for example, when specifying the path of the dependency: ../dist/configure --enable-cxx --disable-shared --with-pic --prefix=$BDB_PREFIX Here BDB_PREFIX must be an absolute path - it is defined using $(pwd) which ensures the usage of the absolute path. To Build --------------------- ```bash ./autogen.sh ./configure make make install # optional ``` This will build bitcoin-qt as well if the dependencies are met. Dependencies --------------------- These dependencies are required: Library | Purpose | Description ------------|------------------|---------------------- libssl | Crypto | Random Number Generation, Elliptic Curve Cryptography libboost | Utility | Library for threading, data structures, etc libevent | Networking | OS independent asynchronous networking Optional dependencies: Library | Purpose | Description ------------|------------------|---------------------- miniupnpc | UPnP Support | Firewall-jumping support libdb | Berkeley DB | Wallet storage (only needed when wallet enabled) qt | GUI | GUI toolkit (only needed when GUI enabled) protobuf | Payments in GUI | Data interchange format used for payment protocol (only needed when GUI enabled) libqrencode | QR codes in GUI | Optional for generating QR codes (only needed when GUI enabled) univalue | Utility | JSON parsing and encoding (bundled version will be used unless --with-system-univalue passed to configure) libzmq3 | ZMQ notification | Optional, allows generating ZMQ notifications (requires ZMQ version >= 4.x) For the versions used in the release, see [release-process.md](release-process.md) under *Fetch and build inputs*. Memory Requirements -------------------- C++ compilers are memory-hungry. It is recommended to have at least 1.5 GB of memory available when compiling Bitcoin ABC. On systems with less, gcc can be tuned to conserve memory with additional CXXFLAGS: ./configure CXXFLAGS="--param ggc-min-expand=1 --param ggc-min-heapsize=32768" Dependency Build Instructions: Ubuntu & Debian ---------------------------------------------- Build requirements: sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils Options when installing required Boost library files: 1. On at least Ubuntu 14.04+ and Debian 7+ there are generic names for the individual boost development packages, so the following can be used to only install necessary parts of boost: sudo apt-get install libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-program-options-dev libboost-test-dev libboost-thread-dev 2. If that doesn't work, you can install all boost development packages with: sudo apt-get install libboost-all-dev BerkeleyDB 5.3 or later is required for the wallet. This can be installed with: sudo apt-get install libdb-dev libdb++-dev See the section "Disable-wallet mode" to build Bitcoin ABC without wallet. Optional (see --with-miniupnpc and --enable-upnp-default): sudo apt-get install libminiupnpc-dev ZMQ dependencies (provides ZMQ API 4.x): sudo apt-get install libzmq3-dev Dependencies for the GUI: Ubuntu & Debian ----------------------------------------- If you want to build Bitcoin-Qt, make sure that the required packages for Qt development -are installed. Either Qt 5 or Qt 4 are necessary to build the GUI. -If both Qt 4 and Qt 5 are installed, Qt 5 will be used. Pass `--with-gui=qt4` to configure to choose Qt4. +are installed. Qt 5 is necessary to build the GUI. To build without GUI pass `--without-gui`. -To build with Qt 5 (recommended) you need the following: +To build with Qt 5 you need the following: sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libprotobuf-dev protobuf-compiler -Alternatively, to build with Qt 4 you need the following: - - sudo apt-get install libqt4-dev libprotobuf-dev protobuf-compiler - libqrencode (optional) can be installed with: sudo apt-get install libqrencode-dev Once these are installed, they will be found by configure and a bitcoin-qt executable will be built by default. Dependency Build Instructions: Fedora ------------------------------------- Build requirements: sudo dnf install gcc-c++ libtool make autoconf automake openssl-devel libevent-devel boost-devel libdb-devel libdb-cxx-devel Optional: sudo dnf install miniupnpc-devel -To build with Qt 5 (recommended) you need the following: +To build with Qt 5 you need the following: sudo dnf install qt5-qttools-devel qt5-qtbase-devel protobuf-devel libqrencode (optional) can be installed with: sudo dnf install qrencode-devel Notes ----- The release is built with GCC and then "strip bitcoind" to strip the debug symbols, which reduces the executable size by about 90%. miniupnpc --------- [miniupnpc](http://miniupnp.free.fr/) may be used for UPnP port mapping. It can be downloaded from [here]( http://miniupnp.tuxfamily.org/files/). UPnP support is compiled in and turned off by default. See the configure options for upnp behavior desired: --without-miniupnpc No UPnP support miniupnp not required --disable-upnp-default (the default) UPnP support turned off by default at runtime --enable-upnp-default UPnP support turned on by default at runtime Boost ----- For documentation on building Boost look at their official documentation: http://www.boost.org/build/doc/html/bbv2/installation.html Security -------- To help make your bitcoin installation more secure by making certain attacks impossible to exploit even if a vulnerability is found, binaries are hardened by default. This can be disabled with: Hardening Flags: ./configure --enable-hardening ./configure --disable-hardening Hardening enables the following features: * Position Independent Executable Build position independent code to take advantage of Address Space Layout Randomization offered by some kernels. Attackers who can cause execution of code at an arbitrary memory location are thwarted if they don't know where anything useful is located. The stack and heap are randomly located by default but this allows the code section to be randomly located as well. On an AMD64 processor where a library was not compiled with -fPIC, this will cause an error such as: "relocation R_X86_64_32 against `......' can not be used when making a shared object;" To test that you have built PIE executable, install scanelf, part of paxutils, and use: scanelf -e ./bitcoin The output should contain: TYPE ET_DYN * Non-executable Stack If the stack is executable then trivial stack based buffer overflow exploits are possible if vulnerable buffers are found. By default, bitcoin should be built with a non-executable stack but if one of the libraries it uses asks for an executable stack or someone makes a mistake and uses a compiler extension which requires an executable stack, it will silently build an executable without the non-executable stack protection. To verify that the stack is non-executable after compiling use: `scanelf -e ./bitcoin` the output should contain: STK/REL/PTL RW- R-- RW- The STK RW- means that the stack is readable and writeable but not executable. Disable-wallet mode -------------------- When the intention is to run only a P2P node without a wallet, bitcoin may be compiled in disable-wallet mode with: ./configure --disable-wallet Mining is also possible in disable-wallet mode, but only using the `getblocktemplate` RPC call not `getwork`. Additional Configure Flags -------------------------- A list of additional configure flags can be displayed with: ./configure --help Setup and Build Example: Arch Linux ----------------------------------- This example lists the steps necessary to setup and build a command line only, non-wallet distribution of the latest changes on Arch Linux: pacman -S git base-devel boost libevent python git clone https://github.com/Bitcoin-ABC/bitcoin-abc.git cd bitcoin-abc/ ./autogen.sh ./configure --disable-wallet --without-gui --without-miniupnpc make check ARM Cross-compilation ------------------- These steps can be performed on, for example, an Ubuntu VM. The depends system will also work on other Linux distributions, however the commands for installing the toolchain will be different. Make sure you install the build requirements mentioned above. Then, install the toolchain and curl: sudo apt-get install g++-arm-linux-gnueabihf curl To build executables for ARM: cd depends make HOST=arm-linux-gnueabihf NO_QT=1 cd .. ./configure --prefix=$PWD/depends/arm-linux-gnueabihf --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++ make For further documentation on the depends system see [README.md](../depends/README.md) in the depends directory. Building on FreeBSD -------------------- (Updated as of FreeBSD 11.0) Clang is installed by default as `cc` compiler, this makes it easier to get started than on [OpenBSD](build-openbsd.md). Installing dependencies: pkg install autoconf automake libtool pkgconf pkg install boost-libs openssl libevent gmake (`libressl` instead of `openssl` will also work) For the wallet (optional): pkg install db5 This will give a warning "configure: WARNING: Found Berkeley DB other than 4.8; wallets opened by this build will not be portable!", but as FreeBSD never had a binary release, this may not matter. If backwards compatibility with 4.8-built Bitcoin Core is needed follow the steps under "Berkeley DB" above. Also, if you intend to run the regression tests (qa tests): pkg install python3 Then build using: ./autogen.sh With wallet support: ./configure --without-gui --without-miniupnpc --with-incompatible-bdb BDB_CFLAGS="-I/usr/local/include/db5" BDB_LIBS="-L/usr/local/lib -ldb_cxx-5" Without wallet support: ./configure --without-gui --without-miniupnpc --disable-wallet Then to compile: gmake *Note on debugging*: The version of `gdb` installed by default is [ancient and considered harmful](https://wiki.freebsd.org/GdbRetirement). It is not suitable for debugging a multi-threaded C++ program, not even for getting backtraces. Please install the package `gdb` and use the versioned gdb command e.g. `gdb7111`. diff --git a/doc/release-notes.md b/doc/release-notes.md index 015b8cc29..93efeed00 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,5 +1,6 @@ Bitcoin ABC version 0.18.3 is now available from: This release includes the following features and fixes: + - Remove support for Qt4 diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 5e83a43a3..b4c71da3c 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -1,309 +1,302 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "addressbookpage.h" #include "ui_addressbookpage.h" #include "addresstablemodel.h" #include "bitcoingui.h" #include "csvmodelwriter.h" #include "editaddressdialog.h" #include "guiutil.h" #include "platformstyle.h" #include #include #include #include AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), model(0), mode(_mode), tab(_tab) { ui->setupUi(this); if (!platformStyle->getImagesOnButtons()) { ui->newAddress->setIcon(QIcon()); ui->copyAddress->setIcon(QIcon()); ui->deleteAddress->setIcon(QIcon()); ui->exportButton->setIcon(QIcon()); } else { ui->newAddress->setIcon(platformStyle->SingleColorIcon(":/icons/add")); ui->copyAddress->setIcon( platformStyle->SingleColorIcon(":/icons/editcopy")); ui->deleteAddress->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->exportButton->setIcon( platformStyle->SingleColorIcon(":/icons/export")); } switch (mode) { case ForSelection: switch (tab) { case SendingTab: setWindowTitle(tr("Choose the address to send coins to")); break; case ReceivingTab: setWindowTitle( tr("Choose the address to receive coins with")); break; } connect(ui->tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(accept())); ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableView->setFocus(); ui->closeButton->setText(tr("C&hoose")); ui->exportButton->hide(); break; case ForEditing: switch (tab) { case SendingTab: setWindowTitle(tr("Sending addresses")); break; case ReceivingTab: setWindowTitle(tr("Receiving addresses")); break; } break; } switch (tab) { case SendingTab: ui->labelExplanation->setText( tr("These are your Bitcoin addresses for sending payments. " "Always check the amount and the receiving address before " "sending coins.")); ui->deleteAddress->setVisible(true); break; case ReceivingTab: ui->labelExplanation->setText( tr("These are your Bitcoin addresses for receiving payments. " "It is recommended to use a new receiving address for each " "transaction.")); ui->deleteAddress->setVisible(false); break; } // Context menu actions QAction *copyAddressAction = new QAction(tr("&Copy Address"), this); QAction *copyLabelAction = new QAction(tr("Copy &Label"), this); QAction *editAction = new QAction(tr("&Edit"), this); deleteAction = new QAction(ui->deleteAddress->text(), this); // Build context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(editAction); if (tab == SendingTab) contextMenu->addAction(deleteAction); contextMenu->addSeparator(); // Connect signals for context menu actions connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(on_copyAddress_clicked())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(onCopyLabelAction())); connect(editAction, SIGNAL(triggered()), this, SLOT(onEditAction())); connect(deleteAction, SIGNAL(triggered()), this, SLOT(on_deleteAddress_clicked())); connect(ui->tableView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); connect(ui->closeButton, SIGNAL(clicked()), this, SLOT(accept())); } AddressBookPage::~AddressBookPage() { delete ui; } void AddressBookPage::setModel(AddressTableModel *_model) { this->model = _model; if (!_model) return; proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(_model); proxyModel->setDynamicSortFilter(true); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); switch (tab) { case ReceivingTab: // Receive filter proxyModel->setFilterRole(AddressTableModel::TypeRole); proxyModel->setFilterFixedString(AddressTableModel::Receive); break; case SendingTab: // Send filter proxyModel->setFilterRole(AddressTableModel::TypeRole); proxyModel->setFilterFixedString(AddressTableModel::Send); break; } ui->tableView->setModel(proxyModel); ui->tableView->sortByColumn(0, Qt::AscendingOrder); -// Set column widths -#if QT_VERSION < 0x050000 - ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Label, - QHeaderView::Stretch); - ui->tableView->horizontalHeader()->setResizeMode( - AddressTableModel::Address, QHeaderView::ResizeToContents); -#else + // Set column widths ui->tableView->horizontalHeader()->setSectionResizeMode( AddressTableModel::Label, QHeaderView::Stretch); ui->tableView->horizontalHeader()->setSectionResizeMode( AddressTableModel::Address, QHeaderView::ResizeToContents); -#endif connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(selectionChanged())); // Select row for newly created address connect(_model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(selectNewAddress(QModelIndex, int, int))); selectionChanged(); } void AddressBookPage::on_copyAddress_clicked() { GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address); } void AddressBookPage::onCopyLabelAction() { GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label); } void AddressBookPage::onEditAction() { if (!model) return; if (!ui->tableView->selectionModel()) return; QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows(); if (indexes.isEmpty()) return; EditAddressDialog dlg(tab == SendingTab ? EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress, this); dlg.setModel(model); QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0)); dlg.loadRow(origIndex.row()); dlg.exec(); } void AddressBookPage::on_newAddress_clicked() { if (!model) return; EditAddressDialog dlg(tab == SendingTab ? EditAddressDialog::NewSendingAddress : EditAddressDialog::NewReceivingAddress, this); dlg.setModel(model); if (dlg.exec()) { newAddressToSelect = dlg.getAddress(); } } void AddressBookPage::on_deleteAddress_clicked() { QTableView *table = ui->tableView; if (!table->selectionModel()) return; QModelIndexList indexes = table->selectionModel()->selectedRows(); if (!indexes.isEmpty()) { table->model()->removeRow(indexes.at(0).row()); } } void AddressBookPage::selectionChanged() { // Set button states based on selected tab and selection QTableView *table = ui->tableView; if (!table->selectionModel()) return; if (table->selectionModel()->hasSelection()) { switch (tab) { case SendingTab: // In sending tab, allow deletion of selection ui->deleteAddress->setEnabled(true); ui->deleteAddress->setVisible(true); deleteAction->setEnabled(true); break; case ReceivingTab: // Deleting receiving addresses, however, is not allowed ui->deleteAddress->setEnabled(false); ui->deleteAddress->setVisible(false); deleteAction->setEnabled(false); break; } ui->copyAddress->setEnabled(true); } else { ui->deleteAddress->setEnabled(false); ui->copyAddress->setEnabled(false); } } void AddressBookPage::done(int retval) { QTableView *table = ui->tableView; if (!table->selectionModel() || !table->model()) return; // Figure out which address was selected, and return it QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); for (const QModelIndex &index : indexes) { QVariant address = table->model()->data(index); returnValue = address.toString(); } if (returnValue.isEmpty()) { // If no address entry selected, return rejected retval = Rejected; } QDialog::done(retval); } void AddressBookPage::on_exportButton_clicked() { // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName(this, tr("Export Address List"), QString(), tr("Comma separated file (*.csv)"), nullptr); if (filename.isNull()) return; CSVModelWriter writer(filename); // name, column, role writer.setModel(proxyModel); writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole); if (!writer.write()) { QMessageBox::critical(this, tr("Exporting Failed"), tr("There was an error trying to save the " "address list to %1. Please try again.") .arg(filename)); } } void AddressBookPage::contextualMenu(const QPoint &point) { QModelIndex index = ui->tableView->indexAt(point); if (index.isValid()) { contextMenu->exec(QCursor::pos()); } } void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int /*end*/) { QModelIndex idx = proxyModel->mapFromSource( model->index(begin, AddressTableModel::Address, parent)); if (idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) { // Select row of newly created address, once ui->tableView->setFocus(); ui->tableView->selectRow(idx.row()); newAddressToSelect.clear(); } } diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index ef6a0d135..4838682f4 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -1,161 +1,159 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bantablemodel.h" #include "clientmodel.h" #include "guiconstants.h" #include "guiutil.h" #include "sync.h" #include "utiltime.h" #include #include bool BannedNodeLessThan::operator()(const CCombinedBan &left, const CCombinedBan &right) const { const CCombinedBan *pLeft = &left; const CCombinedBan *pRight = &right; if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); switch (column) { case BanTableModel::Address: return pLeft->subnet.ToString().compare(pRight->subnet.ToString()) < 0; case BanTableModel::Bantime: return pLeft->banEntry.nBanUntil < pRight->banEntry.nBanUntil; } return false; } // private implementation class BanTablePriv { public: /** Local cache of peer information */ QList cachedBanlist; /** Column to sort nodes by */ int sortColumn; /** Order (ascending or descending) to sort nodes by */ Qt::SortOrder sortOrder; /** Pull a full list of banned nodes from CNode into our cache */ void refreshBanlist() { banmap_t banMap; if (g_connman) g_connman->GetBanned(banMap); cachedBanlist.clear(); -#if QT_VERSION >= 0x040700 cachedBanlist.reserve(banMap.size()); -#endif - for (banmap_t::iterator it = banMap.begin(); it != banMap.end(); it++) { + for (const auto &entry : banMap) { CCombinedBan banEntry; - banEntry.subnet = (*it).first; - banEntry.banEntry = (*it).second; + banEntry.subnet = entry.first; + banEntry.banEntry = entry.second; cachedBanlist.append(banEntry); } if (sortColumn >= 0) // sort cachedBanlist (use stable sort to prevent rows jumping // around unnecessarily) qStableSort(cachedBanlist.begin(), cachedBanlist.end(), BannedNodeLessThan(sortColumn, sortOrder)); } int size() const { return cachedBanlist.size(); } CCombinedBan *index(int idx) { if (idx >= 0 && idx < cachedBanlist.size()) return &cachedBanlist[idx]; return 0; } }; BanTableModel::BanTableModel(ClientModel *parent) : QAbstractTableModel(parent), clientModel(parent) { columns << tr("IP/Netmask") << tr("Banned Until"); priv.reset(new BanTablePriv()); // default to unsorted priv->sortColumn = -1; // load initial data refresh(); } BanTableModel::~BanTableModel() { // Intentionally left empty } int BanTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return priv->size(); } int BanTableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return columns.length(); } QVariant BanTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); CCombinedBan *rec = static_cast(index.internalPointer()); if (role == Qt::DisplayRole) { switch (index.column()) { case Address: return QString::fromStdString(rec->subnet.ToString()); case Bantime: QDateTime date = QDateTime::fromMSecsSinceEpoch(0); date = date.addSecs(rec->banEntry.nBanUntil); return date.toString(Qt::SystemLocaleLongDate); } } return QVariant(); } QVariant BanTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && section < columns.size()) { return columns[section]; } } return QVariant(); } Qt::ItemFlags BanTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return 0; Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; return retval; } QModelIndex BanTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); CCombinedBan *data = priv->index(row); if (data) return createIndex(row, column, data); return QModelIndex(); } void BanTableModel::refresh() { Q_EMIT layoutAboutToBeChanged(); priv->refreshBanlist(); Q_EMIT layoutChanged(); } void BanTableModel::sort(int column, Qt::SortOrder order) { priv->sortColumn = column; priv->sortOrder = order; refresh(); } bool BanTableModel::shouldShow() { return priv->size() > 0; } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index a051d2331..985ea969c 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -1,816 +1,784 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "bitcoingui.h" #include "chainparams.h" #include "clientmodel.h" #include "config.h" #include "guiconstants.h" #include "guiutil.h" #include "httprpc.h" #include "intro.h" #include "networkstyle.h" #include "optionsmodel.h" #include "platformstyle.h" #include "splashscreen.h" #include "utilitydialog.h" #include "winshutdownmonitor.h" #ifdef ENABLE_WALLET #include "paymentserver.h" #include "walletmodel.h" #endif #include "init.h" #include "rpc/server.h" #include "scheduler.h" #include "ui_interface.h" #include "util.h" #include "warnings.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(QT_STATICPLUGIN) #include -#if QT_VERSION < 0x050000 -Q_IMPORT_PLUGIN(qcncodecs) -Q_IMPORT_PLUGIN(qjpcodecs) -Q_IMPORT_PLUGIN(qtwcodecs) -Q_IMPORT_PLUGIN(qkrcodecs) -Q_IMPORT_PLUGIN(qtaccessiblewidgets) -#else #if QT_VERSION < 0x050400 Q_IMPORT_PLUGIN(AccessibleFactory) #endif #if defined(QT_QPA_PLATFORM_XCB) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_WINDOWS) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif -#endif - -#if QT_VERSION < 0x050000 -#include -#endif // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool *) Q_DECLARE_METATYPE(Amount) // Config is non-copyable so we can only register pointers to it Q_DECLARE_METATYPE(Config *) static void InitMessage(const std::string &message) { LogPrintf("init message: %s\n", message); } /** * Translate string to current locale using Qt. */ static std::string Translate(const char *psz) { return QCoreApplication::translate("bitcoin-abc", psz).toStdString(); } static QString GetLangTerritory() { QSettings settings; // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = QLocale::system().name(); // 2) Language from QSettings QString lang_territory_qsettings = settings.value("language", "").toString(); if (!lang_territory_qsettings.isEmpty()) lang_territory = lang_territory_qsettings; // 3) -lang command line argument lang_territory = QString::fromStdString( gArgs.GetArg("-lang", lang_territory.toStdString())); return lang_territory; } /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { // Remove old translators QApplication::removeTranslator(&qtTranslatorBase); QApplication::removeTranslator(&qtTranslator); QApplication::removeTranslator(&translatorBase); QApplication::removeTranslator(&translator); // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = GetLangTerritory(); // Convert to "de" only by truncating "_DE" QString lang = lang_territory; lang.truncate(lang_territory.lastIndexOf('_')); // Load language files for configured locale: // - First load the translator for the base language, without territory // - Then load the more specific locale translator // Load e.g. qt_de.qm if (qtTranslatorBase.load( "qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) QApplication::installTranslator(&qtTranslatorBase); // Load e.g. qt_de_DE.qm if (qtTranslator.load( "qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) QApplication::installTranslator(&qtTranslator); // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in // bitcoin.qrc) if (translatorBase.load(lang, ":/translations/")) QApplication::installTranslator(&translatorBase); // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in // bitcoin.qrc) if (translator.load(lang_territory, ":/translations/")) QApplication::installTranslator(&translator); } /* qDebug() message handler --> debug.log */ -#if QT_VERSION < 0x050000 -void DebugMessageHandler(QtMsgType type, const char *msg) { - if (type == QtDebugMsg) { - LogPrint(BCLog::QT, "GUI: %s\n", msg); - } else { - LogPrintf("GUI: %s\n", msg); - } -} -#else void DebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context); if (type == QtDebugMsg) { LogPrint(BCLog::QT, "GUI: %s\n", msg.toStdString()); } else { LogPrintf("GUI: %s\n", msg.toStdString()); } } -#endif /** * Class encapsulating Bitcoin ABC startup and shutdown. * Allows running startup and shutdown in a different thread from the UI thread. */ class BitcoinABC : public QObject { Q_OBJECT public: explicit BitcoinABC(); public Q_SLOTS: void initialize(Config *config, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void shutdown(); Q_SIGNALS: void initializeResult(int retval); void shutdownResult(int retval); void runawayException(const QString &message); private: boost::thread_group threadGroup; CScheduler scheduler; /// Pass fatal exception message to UI thread void handleRunawayException(const std::exception *e); }; /** Main Bitcoin application object */ class BitcoinApplication : public QApplication { Q_OBJECT public: explicit BitcoinApplication(int &argc, char **argv); ~BitcoinApplication(); #ifdef ENABLE_WALLET /// Create payment server void createPaymentServer(); #endif /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model void createOptionsModel(bool resetSettings); /// Create main window void createWindow(const Config *, const NetworkStyle *networkStyle); /// Create splash screen void createSplashScreen(const NetworkStyle *networkStyle); /// Request core initialization void requestInitialize(Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor); /// Request core shutdown void requestShutdown(Config &config); /// Get process return value int getReturnValue() { return returnValue; } /// Get window identifier of QMainWindow (BitcoinGUI) WId getMainWinId() const; public Q_SLOTS: void initializeResult(int retval); void shutdownResult(int retval); /// Handle runaway exceptions. Shows a message box with the problem and /// quits the program. void handleRunawayException(const QString &message); Q_SIGNALS: void requestedInitialize(Config *config, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); private: QThread *coreThread; OptionsModel *optionsModel; ClientModel *clientModel; BitcoinGUI *window; QTimer *pollShutdownTimer; #ifdef ENABLE_WALLET PaymentServer *paymentServer; WalletModel *walletModel; #endif int returnValue; const PlatformStyle *platformStyle; std::unique_ptr shutdownWindow; void startThread(); }; #include "bitcoin.moc" BitcoinABC::BitcoinABC() : QObject() {} void BitcoinABC::handleRunawayException(const std::exception *e) { PrintExceptionContinue(e, "Runaway exception"); Q_EMIT runawayException(QString::fromStdString(GetWarnings("gui"))); } void BitcoinABC::initialize(Config *cfg, HTTPRPCRequestProcessor *httpRPCRequestProcessor) { Config &config(*cfg); try { qDebug() << __func__ << ": Running AppInit2 in thread"; if (!AppInitBasicSetup()) { Q_EMIT initializeResult(false); return; } if (!AppInitParameterInteraction(config)) { Q_EMIT initializeResult(false); return; } if (!AppInitSanityChecks()) { Q_EMIT initializeResult(false); return; } int rv = AppInitMain(config, *httpRPCRequestProcessor, threadGroup, scheduler); Q_EMIT initializeResult(rv); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } void BitcoinABC::shutdown() { try { qDebug() << __func__ << ": Running Shutdown in thread"; Interrupt(threadGroup); threadGroup.join_all(); Shutdown(); qDebug() << __func__ << ": Shutdown finished"; Q_EMIT shutdownResult(1); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } BitcoinApplication::BitcoinApplication(int &argc, char **argv) : QApplication(argc, argv), coreThread(0), optionsModel(0), clientModel(0), window(0), pollShutdownTimer(0), #ifdef ENABLE_WALLET paymentServer(0), walletModel(0), #endif returnValue(0) { setQuitOnLastWindowClosed(false); // UI per-platform customization. // This must be done inside the BitcoinApplication constructor, or after it, // because PlatformStyle::instantiate requires a QApplication. std::string platformName; platformName = gArgs.GetArg("-uiplatform", BitcoinGUI::DEFAULT_UIPLATFORM); platformStyle = PlatformStyle::instantiate(QString::fromStdString(platformName)); // Fall back to "other" if specified name not found. if (!platformStyle) platformStyle = PlatformStyle::instantiate("other"); assert(platformStyle); } BitcoinApplication::~BitcoinApplication() { if (coreThread) { qDebug() << __func__ << ": Stopping thread"; Q_EMIT stopThread(); coreThread->wait(); qDebug() << __func__ << ": Stopped thread"; } delete window; window = 0; #ifdef ENABLE_WALLET delete paymentServer; paymentServer = 0; #endif delete optionsModel; optionsModel = 0; delete platformStyle; platformStyle = 0; } #ifdef ENABLE_WALLET void BitcoinApplication::createPaymentServer() { paymentServer = new PaymentServer(this); } #endif void BitcoinApplication::createOptionsModel(bool resetSettings) { optionsModel = new OptionsModel(nullptr, resetSettings); } void BitcoinApplication::createWindow(const Config *config, const NetworkStyle *networkStyle) { window = new BitcoinGUI(config, platformStyle, networkStyle, 0); pollShutdownTimer = new QTimer(window); connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); pollShutdownTimer->start(200); } void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle) { SplashScreen *splash = new SplashScreen(0, networkStyle); // We don't hold a direct pointer to the splash screen after creation, but // the splash screen will take care of deleting itself when slotFinish // happens. splash->show(); connect(this, SIGNAL(splashFinished(QWidget *)), splash, SLOT(slotFinish(QWidget *))); connect(this, SIGNAL(requestedShutdown()), splash, SLOT(close())); } void BitcoinApplication::startThread() { if (coreThread) return; coreThread = new QThread(this); BitcoinABC *executor = new BitcoinABC(); executor->moveToThread(coreThread); /* communication to and from thread */ connect(executor, SIGNAL(initializeResult(int)), this, SLOT(initializeResult(int))); connect(executor, SIGNAL(shutdownResult(int)), this, SLOT(shutdownResult(int))); connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); // Note on how Qt works: it tries to directly invoke methods if the signal // is emitted on the same thread that the target object 'lives' on. // But if the target object 'lives' on another thread (executor here does) // the SLOT will be invoked asynchronously at a later time in the thread // of the target object. So.. we pass a pointer around. If you pass // a reference around (even if it's non-const) you'll get Qt generating // code to copy-construct the parameter in question (Q_DECLARE_METATYPE // and qRegisterMetaType generate this code). For the Config class, // which is noncopyable, we can't do this. So.. we have to pass // pointers to Config around. Make sure Config &/Config * isn't a // temporary (eg it lives somewhere aside from the stack) or this will // crash because initialize() gets executed in another thread at some // unspecified time (after) requestedInitialize() is emitted! connect(this, SIGNAL(requestedInitialize(Config *, HTTPRPCRequestProcessor *)), executor, SLOT(initialize(Config *, HTTPRPCRequestProcessor *))); connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); /* make sure executor object is deleted in its own thread */ connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); coreThread->start(); } void BitcoinApplication::parameterSetup() { InitLogging(); InitParameterInteraction(); } void BitcoinApplication::requestInitialize( Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor) { qDebug() << __func__ << ": Requesting initialize"; startThread(); // IMPORTANT: config must NOT be a reference to a temporary because below // signal may be connected to a slot that will be executed as a queued // connection in another thread! Q_EMIT requestedInitialize(&config, &httpRPCRequestProcessor); } void BitcoinApplication::requestShutdown(Config &config) { // Show a simple window indicating shutdown status. Do this first as some of // the steps may take some time below, for example the RPC console may still // be executing a command. shutdownWindow.reset(ShutdownWindow::showShutdownWindow(window)); qDebug() << __func__ << ": Requesting shutdown"; startThread(); window->hide(); window->setClientModel(0); pollShutdownTimer->stop(); #ifdef ENABLE_WALLET window->removeAllWallets(); delete walletModel; walletModel = 0; #endif delete clientModel; clientModel = 0; StartShutdown(); // Request shutdown from core thread Q_EMIT requestedShutdown(); } void BitcoinApplication::initializeResult(int retval) { qDebug() << __func__ << ": Initialization result: " << retval; // Set exit result: 0 if successful, 1 if failure returnValue = retval ? 0 : 1; if (retval) { // Log this only after AppInit2 finishes, as then logging setup is // guaranteed complete. qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET PaymentServer::LoadRootCAs(); paymentServer->setOptionsModel(optionsModel); #endif clientModel = new ClientModel(optionsModel); window->setClientModel(clientModel); #ifdef ENABLE_WALLET // TODO: Expose secondary wallets if (!vpwallets.empty()) { walletModel = new WalletModel(platformStyle, vpwallets[0], optionsModel); window->addWallet(BitcoinGUI::DEFAULT_WALLET, walletModel); window->setCurrentWallet(BitcoinGUI::DEFAULT_WALLET); connect(walletModel, SIGNAL(coinsSent(CWallet *, SendCoinsRecipient, QByteArray)), paymentServer, SLOT(fetchPaymentACK(CWallet *, const SendCoinsRecipient &, QByteArray))); } #endif // If -min option passed, start window minimized. if (gArgs.GetBoolArg("-min", false)) { window->showMinimized(); } else { window->show(); } Q_EMIT splashFinished(window); #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // bitcoincash: URIs or payment requests: connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), window, SLOT(handlePaymentRequest(SendCoinsRecipient))); connect(window, SIGNAL(receivedURI(QString)), paymentServer, SLOT(handleURIOrFile(QString))); connect(paymentServer, SIGNAL(message(QString, QString, unsigned int)), window, SLOT(message(QString, QString, unsigned int))); QTimer::singleShot(100, paymentServer, SLOT(uiReady())); #endif } else { // Exit main loop. quit(); } } void BitcoinApplication::shutdownResult(int retval) { qDebug() << __func__ << ": Shutdown result: " << retval; // Exit main loop after shutdown finished. quit(); } void BitcoinApplication::handleRunawayException(const QString &message) { QMessageBox::critical( 0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue " "safely and will quit.") + QString("\n\n") + message); ::exit(EXIT_FAILURE); } WId BitcoinApplication::getMainWinId() const { if (!window) return 0; return window->winId(); } #ifndef BITCOIN_QT_TEST static void MigrateSettings() { assert(!QApplication::applicationName().isEmpty()); static const QString legacyAppName("Bitcoin-Qt"), #ifdef Q_OS_DARWIN // Macs and/or iOS et al use a domain-style name for Settings // files. All other platforms use a simple orgname. This // difference is documented in the QSettings class documentation. legacyOrg("bitcoin.org"); #else legacyOrg("Bitcoin"); #endif QSettings // below picks up settings file location based on orgname,appname legacy(legacyOrg, legacyAppName), // default c'tor below picks up settings file location based on // QApplication::applicationName(), et al -- which was already set // in main() abc; #ifdef Q_OS_DARWIN // Disable bogus OSX keys from MacOS system-wide prefs that may cloud our // judgement ;) (this behavior is also documented in QSettings docs) legacy.setFallbacksEnabled(false); abc.setFallbacksEnabled(false); #endif const QStringList legacyKeys(legacy.allKeys()); // We only migrate settings if we have Core settings but no Bitcoin-ABC // settings if (!legacyKeys.isEmpty() && abc.allKeys().isEmpty()) { for (const QString &key : legacyKeys) { // now, copy settings over abc.setValue(key, legacy.value(key)); } } } int main(int argc, char *argv[]) { SetupEnvironment(); /// 1. Parse command-line options. These take precedence over anything else. // Command-line options take precedence: gArgs.ParseParameters(argc, argv); -// Do not refer to data directory yet, this can be overridden by -// Intro::pickDataDirectory - -/// 2. Basic Qt initialization (not dependent on parameters or configuration) -#if QT_VERSION < 0x050000 - // Internal string conversion is all UTF-8 - QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); - QTextCodec::setCodecForCStrings(QTextCodec::codecForTr()); -#endif + // Do not refer to data directory yet, this can be overridden by + // Intro::pickDataDirectory + /// 2. Basic Qt initialization (not dependent on parameters or + /// configuration) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); BitcoinApplication app(argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if QT_VERSION >= 0x050600 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #ifdef Q_OS_MAC QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif #if QT_VERSION >= 0x050500 // Because of the POODLE attack it is recommended to disable SSLv3 // (https://disablessl3.com/), so set SSL protocols to TLS1.0+. QSslConfiguration sslconf = QSslConfiguration::defaultConfiguration(); sslconf.setProtocol(QSsl::TlsV1_0OrLater); QSslConfiguration::setDefaultConfiguration(sslconf); #endif // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType(); // Need to pass name here as Amount is a typedef (see // http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType("Amount"); qRegisterMetaType>("std::function"); // Need to register any types Qt doesn't know about if you intend // to use them with the signal/slot mechanism Qt provides. Even pointers. // Note that class Config is noncopyable and so we can't register a // non-pointer version of it with Qt, because Qt expects to be able to // copy-construct non-pointers to objects for invoking slots // behind-the-scenes in the 'Queued' connection case. qRegisterMetaType(); /// 3. Application identification // must be set before OptionsModel is initialized or translations are // loaded, as it is used to locate QSettings. // Note: If you move these calls somewhere else, be sure to bring // MigrateSettings() below along for the ride. QApplication::setOrganizationName(QAPP_ORG_NAME); QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN); QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT); // Migrate settings from core's/our old GUI settings to Bitcoin ABC // only if core's exist but Bitcoin ABC's doesn't. // NOTE -- this function needs to be called *after* the above 3 lines // that set the app orgname and app name! If you move the above 3 lines // to elsewhere, take this call with you! MigrateSettings(); GUIUtil::SubstituteFonts(GetLangTerritory()); /// 4. Initialization of translations, so that intro dialog is in user's /// language. Now that QSettings are accessible, initialize translations. QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); translationInterface.Translate.connect(Translate); // Show help message immediately after parsing command-line options (for // "-lang") and setting locale, but before showing splash screen. if (gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version")) { HelpMessageDialog help(nullptr, gArgs.IsArgSet("-version")); help.showOrPrint(); return EXIT_SUCCESS; } /// 5. Now that settings and translations are available, ask user for data /// directory. User language is set up: pick a data directory. if (!Intro::pickDataDirectory()) return EXIT_SUCCESS; /// 6. Determine availability of data directory and parse bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes. if (!fs::is_directory(GetDataDir(false))) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr( "Error: Specified data directory \"%1\" does not exist.") .arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } try { gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); } catch (const std::exception &e) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: Cannot parse configuration file: %1. Only use " "key=value syntax.") .arg(e.what())); return EXIT_FAILURE; } /// 7. Determine network (and switch to network specific options) // - Do not call Params() before this step. // - Do this after parsing the configuration file, as the network can be // switched there. // - QSettings() will use the new application name after this, resulting in // network-specific settings. // - Needs to be done before createOptionsModel. // Check for -testnet or -regtest parameter (Params() calls are only valid // after this clause) try { SelectParams(ChainNameFromCommandLine()); } catch (std::exception &e) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); return EXIT_FAILURE; } #ifdef ENABLE_WALLET // Parse URIs on command line -- this can affect Params() PaymentServer::ipcParseCommandLine(argc, argv); #endif QScopedPointer networkStyle(NetworkStyle::instantiate( QString::fromStdString(Params().NetworkIDString()))); assert(!networkStyle.isNull()); // Allow for separate UI settings for testnets QApplication::setApplicationName(networkStyle->getAppName()); // Re-initialize translations after changing application name (language in // network-specific settings can be different) initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); #ifdef ENABLE_WALLET /// 8. URI IPC sending // - Do this early as we don't want to bother initializing if we are just // calling IPC // - Do this *after* setting up the data directory, as the data directory // hash is used in the name // of the server. // - Do this after creating app and setting up translations, so errors are // translated properly. if (PaymentServer::ipcSendCommandLine()) exit(EXIT_SUCCESS); // Start up the payment server early, too, so impatient users that click on // bitcoincash: links repeatedly have their payment requests routed to this // process: app.createPaymentServer(); #endif /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be // word-wrapped. app.installEventFilter( new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); -#if QT_VERSION < 0x050000 - // Install qDebug() message handler to route to debug.log - qInstallMsgHandler(DebugMessageHandler); -#else #if defined(Q_OS_WIN) // Install global event filter for processing Windows session related // Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) qApp->installNativeEventFilter(new WinShutdownMonitor()); #endif // Install qDebug() message handler to route to debug.log qInstallMessageHandler(DebugMessageHandler); -#endif // Allow parameter interaction before we create the options model app.parameterSetup(); // Load GUI settings from QSettings app.createOptionsModel(gArgs.IsArgSet("-resetguisettings")); // Subscribe to global signals from core uiInterface.InitMessage.connect(InitMessage); // Get global config Config &config = const_cast(GetConfig()); if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); HTTPRPCRequestProcessor httpRPCRequestProcessor(config); try { app.createWindow(&config, networkStyle.data()); app.requestInitialize(config, httpRPCRequestProcessor); -#if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 +#if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason( QObject::tr("%1 didn't yet exit safely...") .arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); #endif app.exec(); app.requestShutdown(config); app.exec(); } catch (const std::exception &e) { PrintExceptionContinue(&e, "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } catch (...) { PrintExceptionContinue(nullptr, "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } return app.getReturnValue(); } #endif // BITCOIN_QT_TEST diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index f8bfd8650..7fab4a8f6 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1,1298 +1,1286 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "bitcoingui.h" #include "bitcoinunits.h" #include "clientmodel.h" #include "guiconstants.h" #include "guiutil.h" #include "modaloverlay.h" #include "networkstyle.h" #include "notificator.h" #include "openuridialog.h" #include "optionsdialog.h" #include "optionsmodel.h" #include "platformstyle.h" #include "rpcconsole.h" #include "utilitydialog.h" #ifdef ENABLE_WALLET #include "walletframe.h" #include "walletmodel.h" #endif // ENABLE_WALLET #ifdef Q_OS_MAC #include "macdockiconhandler.h" #endif #include "chainparams.h" #include "init.h" #include "ui_interface.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include - -#if QT_VERSION < 0x050000 -#include -#include -#else #include -#endif +#include const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) "macosx" #elif defined(Q_OS_WIN) "windows" #else "other" #endif ; /** * Display name for default wallet name. Uses tilde to avoid name collisions in * the future with additional wallets */ const QString BitcoinGUI::DEFAULT_WALLET = "~Default"; BitcoinGUI::BitcoinGUI(const Config *cfg, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), enableWallet(false), clientModel(0), walletFrame(0), unitDisplayControl(0), labelWalletEncryptionIcon(0), labelWalletHDStatusIcon(0), connectionsControl(0), labelBlocksIcon(0), progressBarLabel(0), progressBar(0), progressDialog(0), appMenuBar(0), overviewAction(0), historyAction(0), quitAction(0), sendCoinsAction(0), sendCoinsMenuAction(0), usedSendingAddressesAction(0), usedReceivingAddressesAction(0), signMessageAction(0), verifyMessageAction(0), aboutAction(0), receiveCoinsAction(0), receiveCoinsMenuAction(0), optionsAction(0), toggleHideAction(0), encryptWalletAction(0), backupWalletAction(0), changePassphraseAction(0), aboutQtAction(0), openRPCConsoleAction(0), openAction(0), showHelpMessageAction(0), trayIcon(0), trayIconMenu(0), notificator(0), rpcConsole(0), helpMessageDialog(0), modalOverlay(0), prevBlocks(0), spinnerFrame(0), platformStyle(_platformStyle), cfg(cfg) { GUIUtil::restoreWindowGeometry("nWindow", QSize(850, 550), this); QString windowTitle = tr(PACKAGE_NAME) + " - "; #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET if (enableWallet) { windowTitle += tr("Wallet"); } else { windowTitle += tr("Node"); } windowTitle += " " + networkStyle->getTitleAddText(); #ifndef Q_OS_MAC QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon()); setWindowIcon(networkStyle->getTrayAndWindowIcon()); #else MacDockIconHandler::instance()->setIcon(networkStyle->getAppIcon()); #endif setWindowTitle(windowTitle); -#if defined(Q_OS_MAC) && QT_VERSION < 0x050000 - // This property is not implemented in Qt 5. Setting it has no effect. - // A replacement API (QtMacUnifiedToolBar) is available in QtMacExtras. - setUnifiedTitleAndToolBarOnMac(true); -#endif - rpcConsole = new RPCConsole(_platformStyle, 0); helpMessageDialog = new HelpMessageDialog(this, false); #ifdef ENABLE_WALLET if (enableWallet) { /** Create wallet frame and make it the central widget */ walletFrame = new WalletFrame(_platformStyle, cfg, this); setCentralWidget(walletFrame); } else #endif // ENABLE_WALLET { /** * When compiled without wallet or -disablewallet is provided, the * central widget is the rpc console. */ setCentralWidget(rpcConsole); } // Accept D&D of URIs setAcceptDrops(true); // Create actions for the toolbar, menu bar and tray/dock icon // Needs walletFrame to be initialized createActions(); // Create application menu bar createMenuBar(); // Create the toolbars createToolBars(); // Create system tray icon and notification createTrayIcon(networkStyle); // Create status bar statusBar(); // Disable size grip because it looks ugly and nobody needs it statusBar()->setSizeGripEnabled(false); // Status bar notification icons QFrame *frameBlocks = new QFrame(); frameBlocks->setContentsMargins(0, 0, 0, 0); frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks); frameBlocksLayout->setContentsMargins(3, 0, 3, 0); frameBlocksLayout->setSpacing(3); unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle); labelWalletEncryptionIcon = new QLabel(); labelWalletHDStatusIcon = new QLabel(); connectionsControl = new GUIUtil::ClickableLabel(); labelBlocksIcon = new GUIUtil::ClickableLabel(); if (enableWallet) { frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(unitDisplayControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelWalletEncryptionIcon); frameBlocksLayout->addWidget(labelWalletHDStatusIcon); } frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(connectionsControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelBlocksIcon); frameBlocksLayout->addStretch(); // Progress bar and label for blocks download progressBarLabel = new QLabel(); progressBarLabel->setVisible(false); progressBar = new GUIUtil::ProgressBar(); progressBar->setAlignment(Qt::AlignCenter); progressBar->setVisible(false); // Override style sheet for progress bar for styles that have a segmented // progress bar, as they make the text unreadable (workaround for issue // #1071) - // See https://qt-project.org/doc/qt-4.8/gallery.html + // See https://doc.qt.io/qt-5/gallery.html QString curStyle = QApplication::style()->metaObject()->className(); if (curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") { progressBar->setStyleSheet( "QProgressBar { background-color: #e8e8e8; border: 1px solid grey; " "border-radius: 7px; padding: 1px; text-align: center; } " "QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, " "x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: " "7px; margin: 0px; }"); } statusBar()->addWidget(progressBarLabel); statusBar()->addWidget(progressBar); statusBar()->addPermanentWidget(frameBlocks); // Install event filter to be able to catch status tip events // (QEvent::StatusTip) this->installEventFilter(this); // Initially wallet actions should be disabled setWalletActionsEnabled(false); // Subscribe to notifications from core subscribeToCoreSignals(); connect(connectionsControl, SIGNAL(clicked(QPoint)), this, SLOT(toggleNetworkActive())); modalOverlay = new ModalOverlay(this->centralWidget()); #ifdef ENABLE_WALLET if (enableWallet) { connect(walletFrame, SIGNAL(requestedSyncWarningInfo()), this, SLOT(showModalOverlay())); connect(labelBlocksIcon, SIGNAL(clicked(QPoint)), this, SLOT(showModalOverlay())); connect(progressBar, SIGNAL(clicked(QPoint)), this, SLOT(showModalOverlay())); } #endif } BitcoinGUI::~BitcoinGUI() { // Unsubscribe from notifications from core unsubscribeFromCoreSignals(); GUIUtil::saveWindowGeometry("nWindow", this); // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) if (trayIcon) { trayIcon->hide(); } #ifdef Q_OS_MAC delete appMenuBar; MacDockIconHandler::cleanup(); #endif delete rpcConsole; } void BitcoinGUI::createActions() { QActionGroup *tabGroup = new QActionGroup(this); overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this); overviewAction->setStatusTip(tr("Show general overview of wallet")); overviewAction->setToolTip(overviewAction->statusTip()); overviewAction->setCheckable(true); overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); tabGroup->addAction(overviewAction); sendCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this); sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); sendCoinsAction->setCheckable(true); sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); sendCoinsMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/send"), sendCoinsAction->text(), this); sendCoinsMenuAction->setStatusTip(sendCoinsAction->statusTip()); sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip()); receiveCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this); receiveCoinsAction->setStatusTip( tr("Request payments (generates QR codes and %1: URIs)") .arg(GUIUtil::bitcoinURIScheme(*cfg))); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); tabGroup->addAction(receiveCoinsAction); receiveCoinsMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/receiving_addresses"), receiveCoinsAction->text(), this); receiveCoinsMenuAction->setStatusTip(receiveCoinsAction->statusTip()); receiveCoinsMenuAction->setToolTip(receiveCoinsMenuAction->statusTip()); historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setStatusTip(tr("Browse transaction history")); historyAction->setToolTip(historyAction->statusTip()); historyAction->setCheckable(true); historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(historyAction); #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive // Coins can be triggered from the tray menu, and need to show the GUI to be // useful. connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); connect(sendCoinsMenuAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(sendCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); #endif // ENABLE_WALLET quitAction = new QAction(platformStyle->TextColorIcon(":/icons/quit"), tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); quitAction->setMenuRole(QAction::QuitRole); aboutAction = new QAction(platformStyle->TextColorIcon(":/icons/about"), tr("&About %1").arg(tr(PACKAGE_NAME)), this); aboutAction->setStatusTip( tr("Show information about %1").arg(tr(PACKAGE_NAME))); aboutAction->setMenuRole(QAction::AboutRole); aboutAction->setEnabled(false); aboutQtAction = new QAction(platformStyle->TextColorIcon(":/icons/about_qt"), tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(platformStyle->TextColorIcon(":/icons/options"), tr("&Options..."), this); optionsAction->setStatusTip( tr("Modify configuration options for %1").arg(tr(PACKAGE_NAME))); optionsAction->setMenuRole(QAction::PreferencesRole); optionsAction->setEnabled(false); toggleHideAction = new QAction(platformStyle->TextColorIcon(":/icons/about"), tr("&Show / Hide"), this); toggleHideAction->setStatusTip(tr("Show or hide the main Window")); encryptWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this); encryptWalletAction->setStatusTip( tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); backupWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/filesave"), tr("&Backup Wallet..."), this); backupWalletAction->setStatusTip(tr("Backup wallet to another location")); changePassphraseAction = new QAction(platformStyle->TextColorIcon(":/icons/key"), tr("&Change Passphrase..."), this); changePassphraseAction->setStatusTip( tr("Change the passphrase used for wallet encryption")); signMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/edit"), tr("Sign &message..."), this); signMessageAction->setStatusTip( tr("Sign messages with your Bitcoin addresses to prove you own them")); verifyMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/verify"), tr("&Verify message..."), this); verifyMessageAction->setStatusTip( tr("Verify messages to ensure they were signed with specified Bitcoin " "addresses")); openRPCConsoleAction = new QAction(platformStyle->TextColorIcon(":/icons/debugwindow"), tr("&Debug window"), this); openRPCConsoleAction->setStatusTip( tr("Open debugging and diagnostic console")); // initially disable the debug window menu item openRPCConsoleAction->setEnabled(false); usedSendingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Sending addresses..."), this); usedSendingAddressesAction->setStatusTip( tr("Show the list of used sending addresses and labels")); usedReceivingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Receiving addresses..."), this); usedReceivingAddressesAction->setStatusTip( tr("Show the list of used receiving addresses and labels")); openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"), tr("Open &URI..."), this); openAction->setStatusTip(tr("Open a %1: URI or payment request") .arg(GUIUtil::bitcoinURIScheme(*cfg))); showHelpMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/info"), tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip( tr("Show the %1 help message to get a list with possible Bitcoin " "command-line options") .arg(tr(PACKAGE_NAME))); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked())); connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden())); connect(showHelpMessageAction, SIGNAL(triggered()), this, SLOT(showHelpMessageClicked())); connect(openRPCConsoleAction, SIGNAL(triggered()), this, SLOT(showDebugWindow())); // prevents an open debug window from becoming stuck/unusable on client // shutdown connect(quitAction, SIGNAL(triggered()), rpcConsole, SLOT(hide())); #ifdef ENABLE_WALLET if (walletFrame) { connect(encryptWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(encryptWallet(bool))); connect(backupWalletAction, SIGNAL(triggered()), walletFrame, SLOT(backupWallet())); connect(changePassphraseAction, SIGNAL(triggered()), walletFrame, SLOT(changePassphrase())); connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab())); connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab())); connect(usedSendingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedSendingAddresses())); connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses())); connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked())); } #endif // ENABLE_WALLET new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this, SLOT(showDebugWindowActivateConsole())); new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this, SLOT(showDebugWindow())); } void BitcoinGUI::createMenuBar() { #ifdef Q_OS_MAC // Create a decoupled menu bar on Mac which stays even if the window is // closed appMenuBar = new QMenuBar(); #else // Get the main window's menu bar on other platforms appMenuBar = menuBar(); #endif // Configure the menus QMenu *file = appMenuBar->addMenu(tr("&File")); if (walletFrame) { file->addAction(openAction); file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); file->addSeparator(); file->addAction(usedSendingAddressesAction); file->addAction(usedReceivingAddressesAction); file->addSeparator(); } file->addAction(quitAction); QMenu *settings = appMenuBar->addMenu(tr("&Settings")); if (walletFrame) { settings->addAction(encryptWalletAction); settings->addAction(changePassphraseAction); settings->addSeparator(); } settings->addAction(optionsAction); QMenu *help = appMenuBar->addMenu(tr("&Help")); if (walletFrame) { help->addAction(openRPCConsoleAction); } help->addAction(showHelpMessageAction); help->addSeparator(); help->addAction(aboutAction); help->addAction(aboutQtAction); } void BitcoinGUI::createToolBars() { if (walletFrame) { QToolBar *toolbar = addToolBar(tr("Tabs toolbar")); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(overviewAction); toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); overviewAction->setChecked(true); } } void BitcoinGUI::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; if (_clientModel) { // Create system tray menu (or setup the dock menu) that late to prevent // users from calling actions, while the client has not yet fully loaded createTrayIconMenu(); // Keep up to date with client updateNetworkState(); connect(_clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); connect(_clientModel, SIGNAL(networkActiveChanged(bool)), this, SLOT(setNetworkActive(bool))); setNumBlocks(_clientModel->getNumBlocks(), _clientModel->getLastBlockDate(), _clientModel->getVerificationProgress(nullptr), false); connect(_clientModel, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this, SLOT(setNumBlocks(int, QDateTime, double, bool))); // Receive and report messages from client model connect(_clientModel, SIGNAL(message(QString, QString, unsigned int)), this, SLOT(message(QString, QString, unsigned int))); // Show progress dialog connect(_clientModel, SIGNAL(showProgress(QString, int)), this, SLOT(showProgress(QString, int))); rpcConsole->setClientModel(_clientModel); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(_clientModel); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); OptionsModel *optionsModel = _clientModel->getOptionsModel(); if (optionsModel) { // be aware of the tray icon disable state change reported by the // OptionsModel object. connect(optionsModel, SIGNAL(hideTrayIconChanged(bool)), this, SLOT(setTrayIconVisible(bool))); // initialize the disable state of the tray icon with the current // value in the model. setTrayIconVisible(optionsModel->getHideTrayIcon()); } modalOverlay->setKnownBestHeight( clientModel->getHeaderTipHeight(), QDateTime::fromTime_t(clientModel->getHeaderTipTime())); } else { // Disable possibility to show main window via action toggleHideAction->setEnabled(false); if (trayIconMenu) { // Disable context menu on tray icon trayIconMenu->clear(); } // Propagate cleared model to child objects rpcConsole->setClientModel(nullptr); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(nullptr); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(nullptr); } } #ifdef ENABLE_WALLET bool BitcoinGUI::addWallet(const QString &name, WalletModel *walletModel) { if (!walletFrame) return false; setWalletActionsEnabled(true); return walletFrame->addWallet(name, walletModel); } bool BitcoinGUI::setCurrentWallet(const QString &name) { if (!walletFrame) return false; return walletFrame->setCurrentWallet(name); } void BitcoinGUI::removeAllWallets() { if (!walletFrame) return; setWalletActionsEnabled(false); walletFrame->removeAllWallets(); } #endif // ENABLE_WALLET void BitcoinGUI::setWalletActionsEnabled(bool enabled) { overviewAction->setEnabled(enabled); sendCoinsAction->setEnabled(enabled); sendCoinsMenuAction->setEnabled(enabled); receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); verifyMessageAction->setEnabled(enabled); usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); openAction->setEnabled(enabled); } void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) { #ifndef Q_OS_MAC trayIcon = new QSystemTrayIcon(this); QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText(); trayIcon->setToolTip(toolTip); trayIcon->setIcon(networkStyle->getTrayAndWindowIcon()); trayIcon->hide(); #endif notificator = new Notificator(QApplication::applicationName(), trayIcon, this); } void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC // return if trayIcon is unset (only on non-Mac OSes) if (!trayIcon) return; trayIconMenu = new QMenu(this); trayIcon->setContextMenu(trayIconMenu); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason))); #else // Note: On Mac, the dock icon is used to provide the tray's functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); dockIconHandler->setMainWindow((QMainWindow *)this); trayIconMenu = dockIconHandler->dockMenu(); #endif // Configuration of the tray icon (or dock icon) icon menu trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(sendCoinsMenuAction); trayIconMenu->addAction(receiveCoinsMenuAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(optionsAction); trayIconMenu->addAction(openRPCConsoleAction); #ifndef Q_OS_MAC // This is built-in on Mac trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif } #ifndef Q_OS_MAC void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { // Click on system tray icon triggers show/hide of the main window toggleHidden(); } } #endif void BitcoinGUI::optionsClicked() { if (!clientModel || !clientModel->getOptionsModel()) return; OptionsDialog dlg(this, enableWallet); dlg.setModel(clientModel->getOptionsModel()); dlg.exec(); } void BitcoinGUI::aboutClicked() { if (!clientModel) return; HelpMessageDialog dlg(this, true); dlg.exec(); } void BitcoinGUI::showDebugWindow() { rpcConsole->showNormal(); rpcConsole->show(); rpcConsole->raise(); rpcConsole->activateWindow(); } void BitcoinGUI::showDebugWindowActivateConsole() { rpcConsole->setTabFocus(RPCConsole::TAB_CONSOLE); showDebugWindow(); } void BitcoinGUI::showHelpMessageClicked() { helpMessageDialog->show(); } #ifdef ENABLE_WALLET void BitcoinGUI::openClicked() { OpenURIDialog dlg(cfg, this); if (dlg.exec()) { Q_EMIT receivedURI(dlg.getURI()); } } void BitcoinGUI::gotoOverviewPage() { overviewAction->setChecked(true); if (walletFrame) walletFrame->gotoOverviewPage(); } void BitcoinGUI::gotoHistoryPage() { historyAction->setChecked(true); if (walletFrame) walletFrame->gotoHistoryPage(); } void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); if (walletFrame) walletFrame->gotoReceiveCoinsPage(); } void BitcoinGUI::gotoSendCoinsPage(QString addr) { sendCoinsAction->setChecked(true); if (walletFrame) walletFrame->gotoSendCoinsPage(addr); } void BitcoinGUI::gotoSignMessageTab(QString addr) { if (walletFrame) walletFrame->gotoSignMessageTab(addr); } void BitcoinGUI::gotoVerifyMessageTab(QString addr) { if (walletFrame) walletFrame->gotoVerifyMessageTab(addr); } #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() { int count = clientModel->getNumConnections(); QString icon; switch (count) { case 0: icon = ":/icons/connect_0"; break; case 1: case 2: case 3: icon = ":/icons/connect_1"; break; case 4: case 5: case 6: icon = ":/icons/connect_2"; break; case 7: case 8: case 9: icon = ":/icons/connect_3"; break; default: icon = ":/icons/connect_4"; break; } QString tooltip; if (clientModel->getNetworkActive()) { tooltip = tr("%n active connection(s) to Bitcoin network", "", count) + QString(".
") + tr("Click to disable network activity."); } else { tooltip = tr("Network activity disabled.") + QString("
") + tr("Click to enable network activity again."); icon = ":/icons/network_disabled"; } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("") + tooltip + QString(""); connectionsControl->setToolTip(tooltip); connectionsControl->setPixmap(platformStyle->SingleColorIcon(icon).pixmap( STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); } void BitcoinGUI::setNumConnections(int count) { updateNetworkState(); } void BitcoinGUI::setNetworkActive(bool networkActive) { updateNetworkState(); } void BitcoinGUI::updateHeadersSyncProgressLabel() { int64_t headersTipTime = clientModel->getHeaderTipTime(); int headersTipHeight = clientModel->getHeaderTipHeight(); int estHeadersLeft = (GetTime() - headersTipTime) / Params().GetConsensus().nPowTargetSpacing; if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC) progressBarLabel->setText( tr("Syncing Headers (%1%)...") .arg(QString::number(100.0 / (headersTipHeight + estHeadersLeft) * headersTipHeight, 'f', 1))); } void BitcoinGUI::setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool header) { if (modalOverlay) { if (header) modalOverlay->setKnownBestHeight(count, blockDate); else modalOverlay->tipUpdate(count, blockDate, nVerificationProgress); } if (!clientModel) return; // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait // until chain-sync starts -> garbled text) statusBar()->clearMessage(); // Acquire current block source enum BlockSource blockSource = clientModel->getBlockSource(); switch (blockSource) { case BLOCK_SOURCE_NETWORK: if (header) { updateHeadersSyncProgressLabel(); return; } progressBarLabel->setText(tr("Synchronizing with network...")); updateHeadersSyncProgressLabel(); break; case BLOCK_SOURCE_DISK: if (header) { progressBarLabel->setText(tr("Indexing blocks on disk...")); } else { progressBarLabel->setText(tr("Processing blocks on disk...")); } break; case BLOCK_SOURCE_REINDEX: progressBarLabel->setText(tr("Reindexing blocks on disk...")); break; case BLOCK_SOURCE_NONE: if (header) { return; } progressBarLabel->setText(tr("Connecting to peers...")); break; } QString tooltip; QDateTime currentDate = QDateTime::currentDateTime(); qint64 secs = blockDate.secsTo(currentDate); tooltip = tr("Processed %n block(s) of transaction history.", "", count); // Set icon state: spinning if catching up, tick otherwise if (secs < 90 * 60) { tooltip = tr("Up to date") + QString(".
") + tooltip; labelBlocksIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/synced") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(false); modalOverlay->showHide(true, true); } #endif // ENABLE_WALLET progressBarLabel->setVisible(false); progressBar->setVisible(false); } else { QString timeBehindText = GUIUtil::formatNiceTimeOffset(secs); progressBarLabel->setVisible(true); progressBar->setFormat(tr("%1 behind").arg(timeBehindText)); progressBar->setMaximum(1000000000); progressBar->setValue(nVerificationProgress * 1000000000.0 + 0.5); progressBar->setVisible(true); tooltip = tr("Catching up...") + QString("
") + tooltip; if (count != prevBlocks) { labelBlocksIcon->setPixmap( platformStyle ->SingleColorIcon(QString(":/movies/spinner-%1") .arg(spinnerFrame, 3, 10, QChar('0'))) .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES; } prevBlocks = count; #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(true); modalOverlay->showHide(); } #endif // ENABLE_WALLET tooltip += QString("
"); tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText); tooltip += QString("
"); tooltip += tr("Transactions after this will not yet be visible."); } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("") + tooltip + QString(""); labelBlocksIcon->setToolTip(tooltip); progressBarLabel->setToolTip(tooltip); progressBar->setToolTip(tooltip); } void BitcoinGUI::message(const QString &title, const QString &message, unsigned int style, bool *ret) { // default title QString strTitle = tr("Bitcoin"); // Default to information icon int nMBoxIcon = QMessageBox::Information; int nNotifyIcon = Notificator::Information; QString msgType; // Prefer supplied title over style based title if (!title.isEmpty()) { msgType = title; } else { switch (style) { case CClientUIInterface::MSG_ERROR: msgType = tr("Error"); break; case CClientUIInterface::MSG_WARNING: msgType = tr("Warning"); break; case CClientUIInterface::MSG_INFORMATION: msgType = tr("Information"); break; default: break; } } // Append title to "Bitcoin - " if (!msgType.isEmpty()) strTitle += " - " + msgType; // Check for error/warning icon if (style & CClientUIInterface::ICON_ERROR) { nMBoxIcon = QMessageBox::Critical; nNotifyIcon = Notificator::Critical; } else if (style & CClientUIInterface::ICON_WARNING) { nMBoxIcon = QMessageBox::Warning; nNotifyIcon = Notificator::Warning; } // Display message if (style & CClientUIInterface::MODAL) { // Check for buttons, use OK as default, if none was supplied QMessageBox::StandardButton buttons; if (!(buttons = (QMessageBox::StandardButton)( style & CClientUIInterface::BTN_MASK))) buttons = QMessageBox::Ok; showNormalIfMinimized(); QMessageBox mBox((QMessageBox::Icon)nMBoxIcon, strTitle, message, buttons, this); int r = mBox.exec(); if (ret != nullptr) *ret = r == QMessageBox::Ok; } else notificator->notify((Notificator::Class)nNotifyIcon, strTitle, message); } void BitcoinGUI::changeEvent(QEvent *e) { QMainWindow::changeEvent(e); #ifndef Q_OS_MAC // Ignored on Mac if (e->type() == QEvent::WindowStateChange) { if (clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) { QWindowStateChangeEvent *wsevt = static_cast(e); if (!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized()) { QTimer::singleShot(0, this, SLOT(hide())); e->ignore(); } } } #endif } void BitcoinGUI::closeEvent(QCloseEvent *event) { #ifndef Q_OS_MAC // Ignored on Mac if (clientModel && clientModel->getOptionsModel()) { if (!clientModel->getOptionsModel()->getMinimizeOnClose()) { // close rpcConsole in case it was open to make some space for the // shutdown window rpcConsole->close(); QApplication::quit(); } else { QMainWindow::showMinimized(); event->ignore(); } } #else QMainWindow::closeEvent(event); #endif } void BitcoinGUI::showEvent(QShowEvent *event) { // enable the debug window when the main window shows up openRPCConsoleAction->setEnabled(true); aboutAction->setEnabled(true); optionsAction->setEnabled(true); } #ifdef ENABLE_WALLET void BitcoinGUI::incomingTransaction(const QString &date, int unit, const Amount amount, const QString &type, const QString &address, const QString &label) { // On new transaction, make an info balloon QString msg = tr("Date: %1\n").arg(date) + tr("Amount: %1\n") .arg(BitcoinUnits::formatWithUnit(unit, amount, true)) + tr("Type: %1\n").arg(type); if (!label.isEmpty()) msg += tr("Label: %1\n").arg(label); else if (!address.isEmpty()) msg += tr("Address: %1\n").arg(address); message(amount < Amount::zero() ? tr("Sent transaction") : tr("Incoming transaction"), msg, CClientUIInterface::MSG_INFORMATION); } #endif // ENABLE_WALLET void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URIs if (event->mimeData()->hasUrls()) event->acceptProposedAction(); } void BitcoinGUI::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { for (const QUrl &uri : event->mimeData()->urls()) { Q_EMIT receivedURI(uri.toString()); } } event->acceptProposedAction(); } bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) { // Catch status tip events if (event->type() == QEvent::StatusTip) { // Prevent adding text from setStatusTip(), if we currently use the // status bar for displaying other stuff if (progressBarLabel->isVisible() || progressBar->isVisible()) return true; } return QMainWindow::eventFilter(object, event); } #ifdef ENABLE_WALLET bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient &recipient) { // URI has to be valid if (walletFrame && walletFrame->handlePaymentRequest(recipient)) { showNormalIfMinimized(); gotoSendCoinsPage(); return true; } return false; } void BitcoinGUI::setHDStatus(int hdEnabled) { labelWalletHDStatusIcon->setPixmap( platformStyle ->SingleColorIcon(hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletHDStatusIcon->setToolTip( hdEnabled ? tr("HD key generation is enabled") : tr("HD key generation is disabled")); // eventually disable the QLabel to set its opacity to 50% labelWalletHDStatusIcon->setEnabled(hdEnabled); } void BitcoinGUI::setEncryptionStatus(int status) { switch (status) { case WalletModel::Unencrypted: labelWalletEncryptionIcon->hide(); encryptWalletAction->setChecked(false); changePassphraseAction->setEnabled(false); encryptWalletAction->setEnabled(true); break; case WalletModel::Unlocked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_open") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently unlocked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported break; case WalletModel::Locked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_closed") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently locked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported break; } } #endif // ENABLE_WALLET void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { if (!clientModel) return; // activateWindow() (sometimes) helps with keyboard focus on Windows if (isHidden()) { show(); activateWindow(); } else if (isMinimized()) { showNormal(); activateWindow(); } else if (GUIUtil::isObscured(this)) { raise(); activateWindow(); } else if (fToggleHidden) hide(); } void BitcoinGUI::toggleHidden() { showNormalIfMinimized(true); } void BitcoinGUI::detectShutdown() { if (ShutdownRequested()) { if (rpcConsole) rpcConsole->hide(); qApp->quit(); } } void BitcoinGUI::showProgress(const QString &title, int nProgress) { if (nProgress == 0) { progressDialog = new QProgressDialog(title, "", 0, 100); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); progressDialog->setCancelButton(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); } else if (nProgress == 100) { if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); } } else if (progressDialog) progressDialog->setValue(nProgress); } void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) { if (trayIcon) { trayIcon->setVisible(!fHideTrayIcon); } } void BitcoinGUI::showModalOverlay() { if (modalOverlay && (progressBar->isVisible() || modalOverlay->isLayerVisible())) modalOverlay->toggleVisibility(); } static bool ThreadSafeMessageBox(BitcoinGUI *gui, const std::string &message, const std::string &caption, unsigned int style) { bool modal = (style & CClientUIInterface::MODAL); // The SECURE flag has no effect in the Qt GUI. // bool secure = (style & CClientUIInterface::SECURE); style &= ~CClientUIInterface::SECURE; bool ret = false; // In case of modal message, use blocking connection to wait for user to // click a button QMetaObject::invokeMethod(gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message)), Q_ARG(unsigned int, style), Q_ARG(bool *, &ret)); return ret; } void BitcoinGUI::subscribeToCoreSignals() { // Connect signals to client uiInterface.ThreadSafeMessageBox.connect( boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); uiInterface.ThreadSafeQuestion.connect( boost::bind(ThreadSafeMessageBox, this, _1, _3, _4)); } void BitcoinGUI::unsubscribeFromCoreSignals() { // Disconnect signals from client uiInterface.ThreadSafeMessageBox.disconnect( boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); uiInterface.ThreadSafeQuestion.disconnect( boost::bind(ThreadSafeMessageBox, this, _1, _3, _4)); } void BitcoinGUI::toggleNetworkActive() { if (clientModel) { clientModel->setNetworkActive(!clientModel->getNetworkActive()); } } UnitDisplayStatusBarControl::UnitDisplayStatusBarControl( const PlatformStyle *platformStyle) : optionsModel(0), menu(0) { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); QList units = BitcoinUnits::availableUnits(); int max_width = 0; const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { max_width = qMax(max_width, fm.width(BitcoinUnits::name(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); setStyleSheet(QString("QLabel { color : %1 }") .arg(platformStyle->SingleColor().name())); } /** So that it responds to button clicks */ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) { onDisplayUnitsClicked(event->pos()); } /** Creates context menu, its actions, and wires up all the relevant signals for * mouse events. */ void UnitDisplayStatusBarControl::createContextMenu() { menu = new QMenu(this); for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { QAction *menuAction = new QAction(QString(BitcoinUnits::name(u)), this); menuAction->setData(QVariant(u)); menu->addAction(menuAction); } connect(menu, SIGNAL(triggered(QAction *)), this, SLOT(onMenuSelection(QAction *))); } /** Lets the control know about the Options Model (and its signals) */ void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) { if (_optionsModel) { this->optionsModel = _optionsModel; // be aware of a display unit change reported by the OptionsModel // object. connect(_optionsModel, SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit(int))); // initialize the display units label with the current value in the // model. updateDisplayUnit(_optionsModel->getDisplayUnit()); } } /** When Display Units are changed on OptionsModel it will refresh the display * text of the control on the status bar */ void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) { setText(BitcoinUnits::name(newUnits)); } /** Shows context menu with Display Unit options by the mouse coordinates */ void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint &point) { QPoint globalPos = mapToGlobal(point); menu->exec(globalPos); } /** Tells underlying optionsModel to update its current display unit. */ void UnitDisplayStatusBarControl::onMenuSelection(QAction *action) { if (action) { optionsModel->setDisplayUnit(action->data()); } } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index a6cf4b6fd..f3c90b04a 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -1,847 +1,841 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "coincontroldialog.h" #include "ui_coincontroldialog.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "txmempool.h" #include "walletmodel.h" #include "dstencode.h" #include "init.h" #include "policy/policy.h" #include "validation.h" // For mempool #include "wallet/coincontrol.h" #include "wallet/fees.h" #include "wallet/wallet.h" #include #include #include #include #include #include #include #include #include #include QList CoinControlDialog::payAmounts; CCoinControl *CoinControlDialog::coinControl = new CCoinControl(); bool CoinControlDialog::fSubtractFeeFromAmount = false; bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); if (column == CoinControlDialog::COLUMN_AMOUNT || column == CoinControlDialog::COLUMN_DATE || column == CoinControlDialog::COLUMN_CONFIRMATIONS) return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); return QTreeWidgetItem::operator<(other); } CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CoinControlDialog), model(0), platformStyle(_platformStyle) { ui->setupUi(this); // context menu actions QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); // we need to enable/disable this copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this unlockAction = new QAction(tr("Unlock unspent"), this); // context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTransactionHashAction); contextMenu->addSeparator(); contextMenu->addAction(lockAction); contextMenu->addAction(unlockAction); // context menu signals connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); // clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // toggle tree/list mode connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); // click on checkbox connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(viewItemChanged(QTreeWidgetItem *, int))); // click on header -#if QT_VERSION < 0x050000 - ui->treeWidget->header()->setClickable(true); -#else ui->treeWidget->header()->setSectionsClickable(true); -#endif connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); // ok button connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonBoxClicked(QAbstractButton *))); // (un)select all connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); // change coin control first column label due Qt4 bug. // see https://github.com/bitcoin/bitcoin/issues/5716 ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); // store transaction hash in this column, but don't show it ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store vout index in this column, but don't show it ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // default view is sorted by amount desc sortView(COLUMN_AMOUNT, Qt::DescendingOrder); // restore list mode and sortorder as a convenience feature QSettings settings; if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) ui->radioTreeMode->click(); if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) sortView( settings.value("nCoinControlSortColumn").toInt(), ((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt())); } CoinControlDialog::~CoinControlDialog() { QSettings settings; settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); settings.setValue("nCoinControlSortColumn", sortColumn); settings.setValue("nCoinControlSortOrder", (int)sortOrder); delete ui; } void CoinControlDialog::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel() && _model->getAddressTableModel()) { updateView(); updateLabelLocked(); CoinControlDialog::updateLabels(_model, this); } } // ok button void CoinControlDialog::buttonBoxClicked(QAbstractButton *button) { if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { // closes the dialog done(QDialog::Accepted); } } // (un)select all void CoinControlDialog::buttonSelectAllClicked() { Qt::CheckState state = Qt::Checked; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) { state = Qt::Unchecked; break; } } ui->treeWidget->setEnabled(false); for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) { ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); } ui->treeWidget->setEnabled(true); if (state == Qt::Unchecked) { // just to be sure coinControl->UnSelectAll(); } CoinControlDialog::updateLabels(model, this); } // context menu void CoinControlDialog::showMenu(const QPoint &point) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item) { contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree // roots in context menu if (item->text(COLUMN_TXHASH).length() == 64) { TxId txid; txid.SetHex(item->text(COLUMN_TXHASH).toStdString()); // transaction hash is 64 characters (this means its a child node, // so its not a parent node in tree mode) copyTransactionHashAction->setEnabled(true); if (model->isLockedCoin(txid, item->text(COLUMN_VOUT_INDEX).toUInt())) { lockAction->setEnabled(false); unlockAction->setEnabled(true); } else { lockAction->setEnabled(true); unlockAction->setEnabled(false); } } else { // this means click on parent node in tree mode -> disable all copyTransactionHashAction->setEnabled(false); lockAction->setEnabled(false); unlockAction->setEnabled(false); } // show context menu contextMenu->exec(QCursor::pos()); } } // context menu action: copy amount void CoinControlDialog::copyAmount() { GUIUtil::setClipboard( BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); } // context menu action: copy label void CoinControlDialog::copyLabel() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); } } // context menu action: copy address void CoinControlDialog::copyAddress() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); } } // context menu action: copy transaction id void CoinControlDialog::copyTransactionHash() { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); } // context menu action: lock coin void CoinControlDialog::lockCoin() { if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) { contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } COutPoint outpt( uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->lockCoin(outpt); contextMenuItem->setDisabled(true); contextMenuItem->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); updateLabelLocked(); } // context menu action: unlock coin void CoinControlDialog::unlockCoin() { COutPoint outpt( uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->unlockCoin(outpt); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); updateLabelLocked(); } // copy label "Quantity" to clipboard void CoinControlDialog::clipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // copy label "Amount" to clipboard void CoinControlDialog::clipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // copy label "Fee" to clipboard void CoinControlDialog::clipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "After fee" to clipboard void CoinControlDialog::clipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "Bytes" to clipboard void CoinControlDialog::clipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // copy label "Dust" to clipboard void CoinControlDialog::clipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // copy label "Change" to clipboard void CoinControlDialog::clipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // treeview: sort void CoinControlDialog::sortView(int column, Qt::SortOrder order) { sortColumn = column; sortOrder = order; ui->treeWidget->sortItems(column, order); ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } // treeview: clicked on header void CoinControlDialog::headerSectionClicked(int logicalIndex) { // click on most left column -> do nothing if (logicalIndex == COLUMN_CHECKBOX) { ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } else { if (sortColumn == logicalIndex) { sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); } else { sortColumn = logicalIndex; // if label or address then default => asc, else default => desc sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); } sortView(sortColumn, sortOrder); } } // toggle tree mode void CoinControlDialog::radioTreeMode(bool checked) { if (checked && model) { updateView(); } } // toggle list mode void CoinControlDialog::radioListMode(bool checked) { if (checked && model) { updateView(); } } // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem *item, int column) { // transaction hash is 64 characters (this means its a child node, so its // not a parent node in tree mode) if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) { COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) { coinControl->UnSelect(outpt); } else if (item->isDisabled()) { // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } else { coinControl->Select(outpt); } // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all CoinControlDialog::updateLabels(model, this); } } // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used. // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 -#if QT_VERSION >= 0x050000 else if (column == COLUMN_CHECKBOX && item->childCount() > 0) { if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) { item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } -#endif } // shows count of locked unspent outputs void CoinControlDialog::updateLabelLocked() { std::vector vOutpts; model->listLockedCoins(vOutpts); if (vOutpts.size() > 0) { ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); ui->labelLocked->setVisible(true); } else { ui->labelLocked->setVisible(false); } } void CoinControlDialog::updateLabels(WalletModel *model, QDialog *dialog) { if (!model) { return; } // nPayAmount Amount nPayAmount = Amount::zero(); bool fDust = false; CMutableTransaction txDummy; for (const Amount amount : CoinControlDialog::payAmounts) { nPayAmount += amount; if (amount > Amount::zero()) { CTxOut txout(Amount(amount), (CScript)std::vector(24, 0)); txDummy.vout.push_back(txout); if (txout.IsDust(dustRelayFee)) { fDust = true; } } } Amount nAmount = Amount::zero(); Amount nPayFee = Amount::zero(); Amount nAfterFee = Amount::zero(); Amount nChange = Amount::zero(); unsigned int nBytes = 0; unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; int nQuantityUncompressed = 0; std::vector vCoinControl; std::vector vOutputs; coinControl->ListSelected(vCoinControl); model->getOutputs(vCoinControl, vOutputs); for (const COutput &out : vOutputs) { // unselect already spent, very unlikely scenario, this could happen // when selected are spent elsewhere, like rpc or another computer uint256 txhash = out.tx->GetId(); COutPoint outpt(txhash, out.i); if (model->isSpent(outpt)) { coinControl->UnSelect(outpt); continue; } // Quantity nQuantity++; // Amount nAmount += out.tx->tx->vout[out.i].nValue; // Bytes CTxDestination address; if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) { CPubKey pubkey; CKeyID *keyid = boost::get(&address); if (keyid && model->getPubKey(*keyid, pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); if (!pubkey.IsCompressed()) { nQuantityUncompressed++; } } else { // in all error cases, simply assume 148 here nBytesInputs += 148; } } else { nBytesInputs += 148; } } // calculation if (nQuantity > 0) { // Bytes // always assume +1 output for change here nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // in the subtract fee from amount case, we can tell if zero change // already and subtract the bytes, so that fee calculation afterwards is // accurate if (CoinControlDialog::fSubtractFeeFromAmount) { if (nAmount - nPayAmount == Amount::zero()) { nBytes -= 34; } } // Fee nPayFee = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); if (nPayFee > Amount::zero() && coinControl->nMinimumTotalFee > nPayFee) { nPayFee = coinControl->nMinimumTotalFee; } if (nPayAmount > Amount::zero()) { nChange = nAmount - nPayAmount; if (!CoinControlDialog::fSubtractFeeFromAmount) { nChange -= nPayFee; } // Never create dust outputs; if we would, just add the dust to the // fee. if (nChange > Amount::zero() && nChange < MIN_CHANGE) { CTxOut txout(nChange, (CScript)std::vector(24, 0)); if (txout.IsDust(dustRelayFee)) { // dust-change will be raised until no dust if (CoinControlDialog::fSubtractFeeFromAmount) { nChange = txout.GetDustThreshold(dustRelayFee); } else { nPayFee += nChange; nChange = Amount::zero(); } } } if (nChange == Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { nBytes -= 34; } } // after fee nAfterFee = std::max(nAmount - nPayFee, Amount::zero()); } // actually update labels int nDisplayUnit = BitcoinUnits::BCH; if (model && model->getOptionsModel()) { nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); } QLabel *l1 = dialog->findChild("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild("labelCoinControlAmount"); QLabel *l3 = dialog->findChild("labelCoinControlFee"); QLabel *l4 = dialog->findChild("labelCoinControlAfterFee"); QLabel *l5 = dialog->findChild("labelCoinControlBytes"); QLabel *l7 = dialog->findChild("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild("labelCoinControlChange"); // enable/disable "dust" and "change" dialog->findChild("labelCoinControlLowOutputText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlLowOutput") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlChangeText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlChange") ->setEnabled(nPayAmount > Amount::zero()); // stats // Quantity l1->setText(QString::number(nQuantity)); // Amount l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Fee l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // After Fee l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // Bytes l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Dust l7->setText(fDust ? tr("yes") : tr("no")); // Change l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); if (nPayFee > Amount::zero() && (coinControl->nMinimumTotalFee < nPayFee)) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); if (nChange > Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { l8->setText(ASYMP_UTF8 + l8->text()); } } // turn label red when dust l7->setStyleSheet((fDust) ? "color:red;" : ""); // tool tips QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller " "than the current dust threshold."); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary; if (payTxFee.GetFeePerK() > Amount::zero()) { dFeeVary = std::max(GetRequiredFee(1000), payTxFee.GetFeePerK()) / (1000 * SATOSHI); } else { dFeeVary = std::max(GetRequiredFee(1000), mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK()) / (1000 * SATOSHI); } QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); l4->setToolTip(toolTip4); l7->setToolTip(toolTipDust); l8->setToolTip(toolTip4); dialog->findChild("labelCoinControlFeeText") ->setToolTip(l3->toolTip()); dialog->findChild("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip()); dialog->findChild("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); dialog->findChild("labelCoinControlLowOutputText") ->setToolTip(l7->toolTip()); dialog->findChild("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); // Insufficient funds QLabel *label = dialog->findChild("labelCoinControlInsuffFunds"); if (label) { label->setVisible(nChange < Amount::zero()); } } void CoinControlDialog::updateView() { if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) { return; } bool treeMode = ui->radioTreeMode->isChecked(); ui->treeWidget->clear(); // performance, otherwise updateLabels would be called for every checked // checkbox ui->treeWidget->setEnabled(false); ui->treeWidget->setAlternatingRowColors(!treeMode); QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); std::map> mapCoins; model->listCoins(mapCoins); for (const std::pair> &coins : mapCoins) { CCoinControlWidgetItem *itemWalletAddress = new CCoinControlWidgetItem(); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); QString sWalletAddress = coins.first; QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); if (sWalletLabel.isEmpty()) { sWalletLabel = tr("(no label)"); } if (treeMode) { // wallet address ui->treeWidget->addTopLevelItem(itemWalletAddress); itemWalletAddress->setFlags(flgTristate); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // label itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); // address itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); } Amount nSum = Amount::zero(); int nChildren = 0; for (const COutput &out : coins.second) { nSum += out.tx->tx->vout[out.i].nValue; nChildren++; CCoinControlWidgetItem *itemOutput; if (treeMode) { itemOutput = new CCoinControlWidgetItem(itemWalletAddress); } else { itemOutput = new CCoinControlWidgetItem(ui->treeWidget); } itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // address CTxDestination outputAddress; QString sAddress = ""; if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) { sAddress = QString::fromStdString(EncodeDestination(outputAddress)); // if listMode or change => show bitcoin address. In tree mode, // address is not shown again for direct wallet address outputs if (!treeMode || (!(sAddress == sWalletAddress))) { itemOutput->setText(COLUMN_ADDRESS, sAddress); } } // label if (!(sAddress == sWalletAddress)) { // change tooltip from where the change comes from itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)") .arg(sWalletLabel) .arg(sWalletAddress)); itemOutput->setText(COLUMN_LABEL, tr("(change)")); } else if (!treeMode) { QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); if (sLabel.isEmpty()) { sLabel = tr("(no label)"); } itemOutput->setText(COLUMN_LABEL, sLabel); } // amount itemOutput->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->tx->vout[out.i].nValue)); // padding so that sorting works correctly itemOutput->setData( COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(out.tx->tx->vout[out.i].nValue / SATOSHI))); // date itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime())); itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.tx->GetTxTime())); // confirmations itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth)); itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.nDepth)); // transaction id const TxId txid = out.tx->GetId(); itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(txid.GetHex())); // vout index itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); // disable locked coins if (model->isLockedCoin(txid, out.i)) { COutPoint outpt(txid, out.i); // just to be sure coinControl->UnSelect(outpt); itemOutput->setDisabled(true); itemOutput->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); } // set checkbox if (coinControl->IsSelected(COutPoint(txid, out.i))) { itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } // amount if (treeMode) { itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(nSum / SATOSHI))); } } // expand all partially selected if (treeMode) { for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) ui->treeWidget->topLevelItem(i)->setExpanded(true); } } // sort view sortView(sortColumn, sortOrder); ui->treeWidget->setEnabled(true); } diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 5b9ec22c9..67d2647f9 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,1042 +1,1009 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "guiutil.h" #include "bitcoinaddressvalidator.h" #include "bitcoinunits.h" #include "dstencode.h" #include "qvalidatedlineedit.h" #include "walletmodel.h" #include "cashaddr.h" #include "config.h" #include "dstencode.h" #include "fs.h" #include "init.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "protocol.h" #include "script/script.h" #include "script/standard.h" #include "util.h" #include "utilstrencodings.h" #ifdef WIN32 #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include "shellapi.h" #include "shlobj.h" #include "shlwapi.h" #endif #include #if BOOST_FILESYSTEM_VERSION >= 3 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::mightBeRichText #include - -#if QT_VERSION < 0x050000 -#include -#else #include -#endif #if QT_VERSION >= 0x50200 #include #endif #if BOOST_FILESYSTEM_VERSION >= 3 static fs::detail::utf8_codecvt_facet utf8; #endif #if defined(Q_OS_MAC) // These Mac includes must be done in the global namespace #include #include extern double NSAppKitVersionNumber; #if !defined(NSAppKitVersionNumber10_8) #define NSAppKitVersionNumber10_8 1187 #endif #if !defined(NSAppKitVersionNumber10_9) #define NSAppKitVersionNumber10_9 1265 #endif #endif namespace GUIUtil { QString dateTimeStr(const QDateTime &date) { return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm"); } QString dateTimeStr(qint64 nTime) { return dateTimeStr(QDateTime::fromTime_t((qint32)nTime)); } QFont fixedPitchFont() { #if QT_VERSION >= 0x50200 return QFontDatabase::systemFont(QFontDatabase::FixedFont); #else QFont font("Monospace"); -#if QT_VERSION >= 0x040800 font.setStyleHint(QFont::Monospace); -#else - font.setStyleHint(QFont::TypeWriter); -#endif return font; #endif } static std::string MakeAddrInvalid(std::string addr, const Config &config) { if (addr.size() < 2) { return ""; } // Checksum is at the end of the address. Swapping chars to make it invalid. std::swap(addr[addr.size() - 1], addr[addr.size() - 2]); if (!IsValidDestinationString(addr, config.GetChainParams())) { return addr; } return ""; } std::string DummyAddress(const Config &config) { // Just some dummy data to generate an convincing random-looking (but // consistent) address static const std::vector dummydata = { 0xeb, 0x15, 0x23, 0x1d, 0xfc, 0xeb, 0x60, 0x92, 0x58, 0x86, 0xb6, 0x7d, 0x06, 0x52, 0x99, 0x92, 0x59, 0x15, 0xae, 0xb1}; const CTxDestination dstKey = CKeyID(uint160(dummydata)); return MakeAddrInvalid(EncodeDestination(dstKey, config), config); } // Addresses are stored in the database with the encoding that the client was // configured with at the time of creation. // // This converts to clients current configuration. QString convertToConfiguredAddressFormat(const Config &config, const QString &addr) { if (!IsValidDestinationString(addr.toStdString(), config.GetChainParams())) { // We have something sketchy as input. Do not try to convert. return addr; } CTxDestination dst = DecodeDestination(addr.toStdString(), config.GetChainParams()); return QString::fromStdString(EncodeDestination(dst, config)); } void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) { parent->setFocusProxy(widget); widget->setFont(fixedPitchFont()); -#if QT_VERSION >= 0x040700 // We don't want translators to use own addresses in translations // and this is the only place, where this address is supplied. widget->setPlaceholderText( QObject::tr("Enter a Bitcoin address (e.g. %1)") .arg(QString::fromStdString(DummyAddress(GetConfig())))); -#endif widget->setValidator( new BitcoinAddressEntryValidator(Params().CashAddrPrefix(), parent)); widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } void setupAmountWidget(QLineEdit *widget, QWidget *parent) { QDoubleValidator *amountValidator = new QDoubleValidator(parent); amountValidator->setDecimals(8); amountValidator->setBottom(0.0); widget->setValidator(amountValidator); widget->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } QString bitcoinURIScheme(const CChainParams ¶ms, bool useCashAddr) { if (!useCashAddr) { return "bitcoincash"; } return QString::fromStdString(params.CashAddrPrefix()); } QString bitcoinURIScheme(const Config &config) { return bitcoinURIScheme(config.GetChainParams(), config.UseCashAddrEncoding()); } static bool IsCashAddrEncoded(const QUrl &uri) { const std::string addr = (uri.scheme() + ":" + uri.path()).toStdString(); auto decoded = cashaddr::Decode(addr, ""); return !decoded.first.empty(); } bool parseBitcoinURI(const QString &scheme, const QUrl &uri, SendCoinsRecipient *out) { // return if URI has wrong scheme. if (!uri.isValid() || uri.scheme() != scheme) { return false; } SendCoinsRecipient rv; if (IsCashAddrEncoded(uri)) { rv.address = uri.scheme() + ":" + uri.path(); } else { // strip out uri scheme for base58 encoded addresses rv.address = uri.path(); } // Trim any following forward slash which may have been added by the OS if (rv.address.endsWith("/")) { rv.address.truncate(rv.address.length() - 1); } rv.amount = Amount::zero(); -#if QT_VERSION < 0x050000 - QList> items = uri.queryItems(); -#else QUrlQuery uriQuery(uri); QList> items = uriQuery.queryItems(); -#endif for (QList>::iterator i = items.begin(); i != items.end(); i++) { bool fShouldReturnFalse = false; if (i->first.startsWith("req-")) { i->first.remove(0, 4); fShouldReturnFalse = true; } if (i->first == "label") { rv.label = i->second; fShouldReturnFalse = false; } if (i->first == "message") { rv.message = i->second; fShouldReturnFalse = false; } else if (i->first == "amount") { if (!i->second.isEmpty()) { if (!BitcoinUnits::parse(BitcoinUnits::BCH, i->second, &rv.amount)) { return false; } } fShouldReturnFalse = false; } if (fShouldReturnFalse) { return false; } } if (out) { *out = rv; } return true; } bool parseBitcoinURI(const QString &scheme, QString uri, SendCoinsRecipient *out) { // // Cannot handle this later, because bitcoincash:// // will cause Qt to see the part after // as host, // which will lower-case it (and thus invalidate the address). if (uri.startsWith(scheme + "://", Qt::CaseInsensitive)) { uri.replace(0, scheme.length() + 3, scheme + ":"); } QUrl uriInstance(uri); return parseBitcoinURI(scheme, uriInstance, out); } QString formatBitcoinURI(const Config &config, const SendCoinsRecipient &info) { QString ret = info.address; if (!config.UseCashAddrEncoding()) { // prefix address with uri scheme for base58 encoded addresses. ret = (bitcoinURIScheme(config) + ":%1").arg(ret); } int paramCount = 0; if (info.amount != Amount::zero()) { ret += QString("?amount=%1") .arg(BitcoinUnits::format(BitcoinUnits::BCH, info.amount, false, BitcoinUnits::separatorNever)); paramCount++; } if (!info.label.isEmpty()) { QString lbl(QUrl::toPercentEncoding(info.label)); ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); paramCount++; } if (!info.message.isEmpty()) { QString msg(QUrl::toPercentEncoding(info.message)); ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); paramCount++; } return ret; } bool isDust(const QString &address, const Amount amount, const CChainParams &chainParams) { CTxDestination dest = DecodeDestination(address.toStdString(), chainParams); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); return txOut.IsDust(dustRelayFee); } QString HtmlEscape(const QString &str, bool fMultiLine) { -#if QT_VERSION < 0x050000 - QString escaped = Qt::escape(str); -#else QString escaped = str.toHtmlEscaped(); -#endif if (fMultiLine) { escaped = escaped.replace("\n", "
\n"); } return escaped; } QString HtmlEscape(const std::string &str, bool fMultiLine) { return HtmlEscape(QString::fromStdString(str), fMultiLine); } void copyEntryData(QAbstractItemView *view, int column, int role) { if (!view || !view->selectionModel()) return; QModelIndexList selection = view->selectionModel()->selectedRows(column); if (!selection.isEmpty()) { // Copy first item setClipboard(selection.at(0).data(role).toString()); } } QList getEntryData(QAbstractItemView *view, int column) { if (!view || !view->selectionModel()) return QList(); return view->selectionModel()->selectedRows(column); } QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; // Default to user documents location if (dir.isEmpty()) { -#if QT_VERSION < 0x050000 - myDir = QDesktopServices::storageLocation( - QDesktopServices::DocumentsLocation); -#else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); -#endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName( parent, caption, myDir, filter, &selectedFilter)); /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } /* Add suffix if needed */ QFileInfo info(result); if (!result.isEmpty()) { if (info.suffix().isEmpty() && !selectedSuffix.isEmpty()) { /* No suffix specified, add selected suffix */ if (!result.endsWith(".")) result.append("."); result.append(selectedSuffix); } } /* Return selected suffix if asked to */ if (selectedSuffixOut) { *selectedSuffixOut = selectedSuffix; } return result; } QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; if (dir.isEmpty()) // Default to user documents location { -#if QT_VERSION < 0x050000 - myDir = QDesktopServices::storageLocation( - QDesktopServices::DocumentsLocation); -#else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); -#endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName( parent, caption, myDir, filter, &selectedFilter)); if (selectedSuffixOut) { /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } *selectedSuffixOut = selectedSuffix; } return result; } Qt::ConnectionType blockingGUIThreadConnection() { if (QThread::currentThread() != qApp->thread()) { return Qt::BlockingQueuedConnection; } else { return Qt::DirectConnection; } } bool checkPoint(const QPoint &p, const QWidget *w) { QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p)); if (!atW) return false; return atW->topLevelWidget() == w; } bool isObscured(QWidget *w) { return !(checkPoint(QPoint(0, 0), w) && checkPoint(QPoint(w->width() - 1, 0), w) && checkPoint(QPoint(0, w->height() - 1), w) && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } void openDebugLogfile() { fs::path pathDebug = GetDataDir() / "debug.log"; /* Open debug.log with the associated application */ if (fs::exists(pathDebug)) QDesktopServices::openUrl( QUrl::fromLocalFile(boostPathToQString(pathDebug))); } void SubstituteFonts(const QString &language) { #if defined(Q_OS_MAC) // Background: // OSX's default font changed in 10.9 and Qt is unable to find it with its // usual fallback methods when building against the 10.7 sdk or lower. // The 10.8 SDK added a function to let it find the correct fallback font. // If this fallback is not properly loaded, some characters may fail to // render correctly. // // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now // default. // // Solution: If building with the 10.7 SDK or lower and the user's platform // is 10.9 or higher at runtime, substitute the correct font. This needs to // happen before the QApplication is created. #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8) { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) /* On a 10.9 - 10.9.x system */ QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); else { /* 10.10 or later system */ if (language == "zh_CN" || language == "zh_TW" || language == "zh_HK") // traditional or simplified Chinese QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC"); else if (language == "ja") // Japanesee QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC"); else QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande"); } } #endif #endif } ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold, QObject *parent) : QObject(parent), size_threshold(_size_threshold) {} bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) { if (evt->type() == QEvent::ToolTipChange) { QWidget *widget = static_cast(obj); QString tooltip = widget->toolTip(); if (tooltip.size() > size_threshold && !tooltip.startsWith(" to make sure Qt detects this as rich text // Escape the current message as HTML and replace \n by
tooltip = "" + HtmlEscape(tooltip, true) + ""; widget->setToolTip(tooltip); return true; } } return QObject::eventFilter(obj, evt); } void TableViewLastColumnResizingFixer::connectViewHeadersSignals() { connect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); connect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // We need to disconnect these while handling the resize events, otherwise we // can enter infinite loops. void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() { disconnect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); disconnect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // Setup the resize mode, handles compatibility for Qt5 and below as the method // signatures changed. // Refactored here for readability. void TableViewLastColumnResizingFixer::setViewHeaderResizeMode( int logicalIndex, QHeaderView::ResizeMode resizeMode) { -#if QT_VERSION < 0x050000 - tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); -#else tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); -#endif } void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) { tableView->setColumnWidth(nColumnIndex, width); tableView->horizontalHeader()->resizeSection(nColumnIndex, width); } int TableViewLastColumnResizingFixer::getColumnsWidth() { int nColumnsWidthSum = 0; for (int i = 0; i < columnCount; i++) { nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i); } return nColumnsWidthSum; } int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) { int nResult = lastColumnMinimumWidth; int nTableWidth = tableView->horizontalHeader()->width(); if (nTableWidth > 0) { int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column); nResult = std::max(nResult, nTableWidth - nOtherColsWidth); } return nResult; } // Make sure we don't make the columns wider than the table's viewport width. void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() { disconnectViewHeadersSignals(); resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex)); connectViewHeadersSignals(); int nTableWidth = tableView->horizontalHeader()->width(); int nColsWidth = getColumnsWidth(); if (nColsWidth > nTableWidth) { resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); } } // Make column use all the space available, useful during window resizing. void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) { disconnectViewHeadersSignals(); resizeColumn(column, getAvailableWidthForColumn(column)); connectViewHeadersSignals(); } // When a section is resized this is a slot-proxy for ajustAmountColumnWidth(). void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize) { adjustTableColumnsWidth(); int remainingWidth = getAvailableWidthForColumn(logicalIndex); if (newSize > remainingWidth) { resizeColumn(logicalIndex, remainingWidth); } } // When the table's geometry is ready, we manually perform the stretch of the // "Message" column, // as the "Stretch" resize mode does not allow for interactive resizing. void TableViewLastColumnResizingFixer::on_geometriesChanged() { if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) { disconnectViewHeadersSignals(); resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); connectViewHeadersSignals(); } } /** * Initializes all internal variables and prepares the * the resize modes of the last 2 columns of the table and */ TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer( QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) : QObject(parent), tableView(table), lastColumnMinimumWidth(lastColMinimumWidth), allColumnsMinimumWidth(allColsMinimumWidth) { columnCount = tableView->horizontalHeader()->count(); lastColumnIndex = columnCount - 1; secondToLastColumnIndex = columnCount - 2; tableView->horizontalHeader()->setMinimumSectionSize( allColumnsMinimumWidth); setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); } #ifdef WIN32 static fs::path StartupShortcutPath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; // Remove this special case when CBaseChainParams::TESTNET = "testnet4" if (chain == CBaseChainParams::TESTNET) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk"; return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain); } bool GetStartOnSystemStartup() { // check for Bitcoin*.lnk return fs::exists(StartupShortcutPath()); } bool SetStartOnSystemStartup(bool fAutoStart) { // If the shortcut exists already, remove it for updating fs::remove(StartupShortcutPath()); if (fAutoStart) { CoInitialize(nullptr); // Get a pointer to the IShellLink interface. IShellLink *psl = nullptr; HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&psl)); if (SUCCEEDED(hres)) { // Get the current executable path TCHAR pszExePath[MAX_PATH]; GetModuleFileName(nullptr, pszExePath, sizeof(pszExePath)); // Start client minimized QString strArgs = "-min"; // Set -testnet /-regtest options strArgs += QString::fromStdString(strprintf( " -testnet=%d -regtest=%d", gArgs.GetBoolArg("-testnet", false), gArgs.GetBoolArg("-regtest", false))); #ifdef UNICODE boost::scoped_array args(new TCHAR[strArgs.length() + 1]); // Convert the QString to TCHAR* strArgs.toWCharArray(args.get()); // Add missing '\0'-termination to string args[strArgs.length()] = '\0'; #endif // Set the path to the shortcut target psl->SetPath(pszExePath); PathRemoveFileSpec(pszExePath); psl->SetWorkingDirectory(pszExePath); psl->SetShowCmd(SW_SHOWMINNOACTIVE); #ifndef UNICODE psl->SetArguments(strArgs.toStdString().c_str()); #else psl->SetArguments(args.get()); #endif // Query IShellLink for the IPersistFile interface for // saving the shortcut in persistent storage. IPersistFile *ppf = nullptr; hres = psl->QueryInterface(IID_IPersistFile, reinterpret_cast(&ppf)); if (SUCCEEDED(hres)) { WCHAR pwsz[MAX_PATH]; // Ensure that the string is ANSI. MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); // Save the link by calling IPersistFile::Save. hres = ppf->Save(pwsz, TRUE); ppf->Release(); psl->Release(); CoUninitialize(); return true; } psl->Release(); } CoUninitialize(); return false; } return true; } #elif defined(Q_OS_LINUX) // Follow the Desktop Application Autostart Spec: // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html static fs::path GetAutostartDir() { char *pszConfigHome = getenv("XDG_CONFIG_HOME"); if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; char *pszHome = getenv("HOME"); if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; return fs::path(); } static fs::path GetAutostartFilePath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain); } bool GetStartOnSystemStartup() { fs::ifstream optionFile(GetAutostartFilePath()); if (!optionFile.good()) return false; // Scan through file for "Hidden=true": std::string line; while (!optionFile.eof()) { getline(optionFile, line); if (line.find("Hidden") != std::string::npos && line.find("true") != std::string::npos) return false; } optionFile.close(); return true; } bool SetStartOnSystemStartup(bool fAutoStart) { if (!fAutoStart) fs::remove(GetAutostartFilePath()); else { char pszExePath[MAX_PATH + 1]; memset(pszExePath, 0, sizeof(pszExePath)); if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1) == -1) return false; fs::create_directories(GetAutostartDir()); fs::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc); if (!optionFile.good()) return false; std::string chain = ChainNameFromCommandLine(); // Write a bitcoin.desktop file to the autostart directory: optionFile << "[Desktop Entry]\n"; optionFile << "Type=Application\n"; if (chain == CBaseChainParams::MAIN) optionFile << "Name=Bitcoin\n"; else optionFile << strprintf("Name=Bitcoin (%s)\n", chain); optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", gArgs.GetBoolArg("-testnet", false), gArgs.GetBoolArg("-regtest", false)); optionFile << "Terminal=false\n"; optionFile << "Hidden=false\n"; optionFile.close(); } return true; } #elif defined(Q_OS_MAC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // based on: // https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m // NB: caller must release returned ref if it's not NULL LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) { LSSharedFileListItemRef foundItem = nullptr; // loop through the list of startup items and try to find the bitcoin app CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr); for (int i = 0; !foundItem && i < CFArrayGetCount(listSnapshot); ++i) { LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i); UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef currentItemURL = nullptr; #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED >= 10100 if (&LSSharedFileListItemCopyResolvedURL) currentItemURL = LSSharedFileListItemCopyResolvedURL( item, resolutionFlags, nullptr); #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ MAC_OS_X_VERSION_MIN_REQUIRED < 10100 else LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); #endif #else LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); #endif if (currentItemURL && CFEqual(currentItemURL, findUrl)) { // found CFRetain(foundItem = item); } if (currentItemURL) { CFRelease(currentItemURL); } } CFRelease(listSnapshot); return foundItem; } bool GetStartOnSystemStartup() { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return foundItem; } bool SetStartOnSystemStartup(bool fAutoStart) { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); if (fAutoStart && !foundItem) { // add bitcoin app to startup item list LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, nullptr, nullptr, bitcoinAppUrl, nullptr, nullptr); } else if (!fAutoStart && foundItem) { // remove item LSSharedFileListItemRemove(loginItems, foundItem); } // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return true; } #pragma GCC diagnostic pop #else bool GetStartOnSystemStartup() { return false; } bool SetStartOnSystemStartup(bool fAutoStart) { return false; } #endif void saveWindowGeometry(const QString &strSetting, QWidget *parent) { QSettings settings; settings.setValue(strSetting + "Pos", parent->pos()); settings.setValue(strSetting + "Size", parent->size()); } void restoreWindowGeometry(const QString &strSetting, const QSize &defaultSize, QWidget *parent) { QSettings settings; QPoint pos = settings.value(strSetting + "Pos").toPoint(); QSize size = settings.value(strSetting + "Size", defaultSize).toSize(); if (!pos.x() && !pos.y()) { QRect screen = QApplication::desktop()->screenGeometry(); pos.setX((screen.width() - size.width()) / 2); pos.setY((screen.height() - size.height()) / 2); } parent->resize(size); parent->move(pos); } void setClipboard(const QString &str) { QApplication::clipboard()->setText(str, QClipboard::Clipboard); QApplication::clipboard()->setText(str, QClipboard::Selection); } #if BOOST_FILESYSTEM_VERSION >= 3 fs::path qstringToBoostPath(const QString &path) { return fs::path(path.toStdString(), utf8); } QString boostPathToQString(const fs::path &path) { return QString::fromStdString(path.string(utf8)); } #else #warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older fs::path qstringToBoostPath(const QString &path) { return fs::path(path.toStdString()); } QString boostPathToQString(const fs::path &path) { return QString::fromStdString(path.string()); } #endif QString formatDurationStr(int secs) { QStringList strList; int days = secs / 86400; int hours = (secs % 86400) / 3600; int mins = (secs % 3600) / 60; int seconds = secs % 60; if (days) strList.append(QString(QObject::tr("%1 d")).arg(days)); if (hours) strList.append(QString(QObject::tr("%1 h")).arg(hours)); if (mins) strList.append(QString(QObject::tr("%1 m")).arg(mins)); if (seconds || (!days && !hours && !mins)) strList.append(QString(QObject::tr("%1 s")).arg(seconds)); return strList.join(" "); } QString formatServicesStr(quint64 mask) { QStringList strList; // Just scan the last 8 bits for now. for (int i = 0; i < 8; i++) { uint64_t check = 1 << i; if (mask & check) { switch (check) { case NODE_NETWORK: strList.append("NETWORK"); break; case NODE_GETUTXO: strList.append("GETUTXO"); break; case NODE_BLOOM: strList.append("BLOOM"); break; case NODE_XTHIN: strList.append("XTHIN"); break; case NODE_BITCOIN_CASH: strList.append("CASH"); break; default: strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); } } } if (strList.size()) return strList.join(" & "); else return QObject::tr("None"); } QString formatPingTime(double dPingTime) { return (dPingTime == std::numeric_limits::max() / 1e6 || dPingTime == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")) .arg(QString::number((int)(dPingTime * 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) { return QString(QObject::tr("%1 s")) .arg(QString::number((int)nTimeOffset, 10)); } QString formatNiceTimeOffset(qint64 secs) { // Represent time from last generated block in human readable text QString timeBehindText; const int HOUR_IN_SECONDS = 60 * 60; const int DAY_IN_SECONDS = 24 * 60 * 60; const int WEEK_IN_SECONDS = 7 * 24 * 60 * 60; // Average length of year in Gregorian calendar const int YEAR_IN_SECONDS = 31556952; if (secs < 60) { timeBehindText = QObject::tr("%n second(s)", "", secs); } else if (secs < 2 * HOUR_IN_SECONDS) { timeBehindText = QObject::tr("%n minute(s)", "", secs / 60); } else if (secs < 2 * DAY_IN_SECONDS) { timeBehindText = QObject::tr("%n hour(s)", "", secs / HOUR_IN_SECONDS); } else if (secs < 2 * WEEK_IN_SECONDS) { timeBehindText = QObject::tr("%n day(s)", "", secs / DAY_IN_SECONDS); } else if (secs < YEAR_IN_SECONDS) { timeBehindText = QObject::tr("%n week(s)", "", secs / WEEK_IN_SECONDS); } else { qint64 years = secs / YEAR_IN_SECONDS; qint64 remainder = secs % YEAR_IN_SECONDS; timeBehindText = QObject::tr("%1 and %2") .arg(QObject::tr("%n year(s)", "", years)) .arg(QObject::tr("%n week(s)", "", remainder / WEEK_IN_SECONDS)); } return timeBehindText; } void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 16c61f274..7bd696846 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -1,281 +1,281 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_GUIUTIL_H #define BITCOIN_QT_GUIUTIL_H #include "amount.h" #include "fs.h" #include #include #include #include #include #include #include #include class QValidatedLineEdit; class SendCoinsRecipient; class CChainParams; class Config; QT_BEGIN_NAMESPACE class QAbstractItemView; class QDateTime; class QFont; class QLineEdit; class QUrl; class QWidget; QT_END_NAMESPACE /** Utility functions used by the Bitcoin Qt UI. */ namespace GUIUtil { // Create human-readable string from date QString dateTimeStr(const QDateTime &datetime); QString dateTimeStr(qint64 nTime); // Return a monospace font QFont fixedPitchFont(); // Generate an invalid, but convincing address. std::string DummyAddress(const Config &config); // Convert an address into the user chosen format QString convertToConfiguredAddressFormat(const Config &config, const QString &addr); // Set up widgets for address and amounts void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent); void setupAmountWidget(QLineEdit *widget, QWidget *parent); QString bitcoinURIScheme(const CChainParams &, bool useCashAddr); QString bitcoinURIScheme(const Config &); // Parse "bitcoincash:" URI into recipient object, return true on successful // parsing bool parseBitcoinURI(const QString &scheme, const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(const QString &scheme, QString uri, SendCoinsRecipient *out); QString formatBitcoinURI(const Config &config, const SendCoinsRecipient &info); // Returns true if given address+amount meets "dust" definition bool isDust(const QString &address, const Amount amount, const CChainParams &chainParams); // HTML escaping for rich text controls QString HtmlEscape(const QString &str, bool fMultiLine = false); QString HtmlEscape(const std::string &str, bool fMultiLine = false); /** Copy a field of the currently selected entry of a view to the clipboard. Does nothing if nothing is selected. @param[in] column Data column to extract from the model @param[in] role Data role to extract from the model @see TransactionView::copyLabel, TransactionView::copyAmount, TransactionView::copyAddress */ void copyEntryData(QAbstractItemView *view, int column, int role = Qt::EditRole); /** Return a field of the currently selected entry as a QString. Does nothing if nothing is selected. @param[in] column Data column to extract from the model @see TransactionView::copyLabel, TransactionView::copyAmount, TransactionView::copyAddress */ QList getEntryData(QAbstractItemView *view, int column); void setClipboard(const QString &str); /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. @param[in] parent Parent window (or 0) @param[in] caption Window caption (or empty, for default) @param[in] dir Starting directory (or empty, to default to documents directory) @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" @param[out] selectedSuffixOut Pointer to return the suffix (file type) that was selected (or 0). Can be useful when choosing the save file format based on suffix. */ QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut); /** Get open filename, convenience wrapper for QFileDialog::getOpenFileName. @param[in] parent Parent window (or 0) @param[in] caption Window caption (or empty, for default) @param[in] dir Starting directory (or empty, to default to documents directory) @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" @param[out] selectedSuffixOut Pointer to return the suffix (file type) that was selected (or 0). Can be useful when choosing the save file format based on suffix. */ QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut); /** Get connection type to call object slot in GUI thread with invokeMethod. The call will be blocking. @returns If called from the GUI thread, return a Qt::DirectConnection. If called from another thread, return a Qt::BlockingQueuedConnection. */ Qt::ConnectionType blockingGUIThreadConnection(); // Determine whether a widget is hidden behind other windows bool isObscured(QWidget *w); // Open debug.log void openDebugLogfile(); // Replace invalid default fonts with known good ones void SubstituteFonts(const QString &language); /** Qt event filter that intercepts ToolTipChange events, and replaces the * tooltip with a rich text representation if needed. This assures that Qt can * word-wrap long tooltip messages. Tooltips longer than the provided size * threshold (in characters) are wrapped. */ class ToolTipToRichTextFilter : public QObject { Q_OBJECT public: explicit ToolTipToRichTextFilter(int size_threshold, QObject *parent = 0); protected: bool eventFilter(QObject *obj, QEvent *evt) override; private: int size_threshold; }; /** * Makes a QTableView last column feel as if it was being resized from its left * border. * Also makes sure the column widths are never larger than the table's viewport. * In Qt, all columns are resizable from the right, but it's not intuitive * resizing the last column from the right. * Usually our second to last columns behave as if stretched, and when on strech * mode, columns aren't resizable interactively or programmatically. * * This helper object takes care of this issue. * */ class TableViewLastColumnResizingFixer : public QObject { Q_OBJECT public: TableViewLastColumnResizingFixer(QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent); void stretchColumnWidth(int column); private: QTableView *tableView; int lastColumnMinimumWidth; int allColumnsMinimumWidth; int lastColumnIndex; int columnCount; int secondToLastColumnIndex; void adjustTableColumnsWidth(); int getAvailableWidthForColumn(int column); int getColumnsWidth(); void connectViewHeadersSignals(); void disconnectViewHeadersSignals(); void setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode); void resizeColumn(int nColumnIndex, int width); private Q_SLOTS: void on_sectionResized(int logicalIndex, int oldSize, int newSize); void on_geometriesChanged(); }; bool GetStartOnSystemStartup(); bool SetStartOnSystemStartup(bool fAutoStart); /** Save window size and position */ void saveWindowGeometry(const QString &strSetting, QWidget *parent); /** Restore window size and position */ void restoreWindowGeometry(const QString &strSetting, const QSize &defaultSizeIn, QWidget *parent); /* Convert QString to OS specific boost path through UTF-8 */ fs::path qstringToBoostPath(const QString &path); /* Convert OS specific boost path to QString through UTF-8 */ QString boostPathToQString(const fs::path &path); /* Convert seconds into a QString with days, hours, mins, secs */ QString formatDurationStr(int secs); /* Format CNodeStats.nServices bitmask into a user-readable string */ QString formatServicesStr(quint64 mask); /* Format a CNodeCombinedStats.dPingTime into a user-readable string or display * N/A, if 0*/ QString formatPingTime(double dPingTime); /* Format a CNodeCombinedStats.nTimeOffset into a user-readable string. */ QString formatTimeOffset(int64_t nTimeOffset); QString formatNiceTimeOffset(qint64 secs); class ClickableLabel : public QLabel { Q_OBJECT Q_SIGNALS: /** Emitted when the label is clicked. The relative mouse coordinates of the * click are passed to the signal. */ void clicked(const QPoint &point); protected: void mouseReleaseEvent(QMouseEvent *event) override; }; class ClickableProgressBar : public QProgressBar { Q_OBJECT Q_SIGNALS: /** Emitted when the progressbar is clicked. The relative mouse coordinates * of the click are passed to the signal. */ void clicked(const QPoint &point); protected: void mouseReleaseEvent(QMouseEvent *event) override; }; -#if defined(Q_OS_MAC) && QT_VERSION >= 0x050000 +#if defined(Q_OS_MAC) // workaround for Qt OSX Bug: // https://bugreports.qt-project.org/browse/QTBUG-15631 // QProgressBar uses around 10% CPU even when app is in background class ProgressBar : public ClickableProgressBar { bool event(QEvent *e) override { return (e->type() != QEvent::StyleAnimationUpdate) ? QProgressBar::event(e) : false; } }; #else typedef ClickableProgressBar ProgressBar; #endif } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index 9e7de0f98..a0b62ae00 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,134 +1,128 @@ // Copyright (c) 2011-2013 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "macdockiconhandler.h" #include #include #include #include #undef slots #include #include #include -#if QT_VERSION < 0x050000 -extern void qt_mac_set_dock_menu(QMenu *); -#endif - static MacDockIconHandler *s_instance = nullptr; bool dockClickHandler(id self,SEL _cmd,...) { Q_UNUSED(self) Q_UNUSED(_cmd) s_instance->handleDockIconClickEvent(); // Return NO (false) to suppress the default OS X actions return false; } void setupDockClickHandler() { Class cls = objc_getClass("NSApplication"); id appInst = objc_msgSend((id)cls, sel_registerName("sharedApplication")); if (appInst != nullptr) { id delegate = objc_msgSend(appInst, sel_registerName("delegate")); Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); if (class_getInstanceMethod(delClass, shouldHandle)) class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); else class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"); } } MacDockIconHandler::MacDockIconHandler() : QObject() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; setupDockClickHandler(); this->m_dummyWidget = new QWidget(); this->m_dockMenu = new QMenu(this->m_dummyWidget); this->setMainWindow(nullptr); -#if QT_VERSION < 0x050000 - qt_mac_set_dock_menu(this->m_dockMenu); -#elif QT_VERSION >= 0x050200 +#if QT_VERSION >= 0x050200 this->m_dockMenu->setAsDockMenu(); #endif [pool release]; } void MacDockIconHandler::setMainWindow(QMainWindow *window) { this->mainWindow = window; } MacDockIconHandler::~MacDockIconHandler() { delete this->m_dummyWidget; this->setMainWindow(nullptr); } QMenu *MacDockIconHandler::dockMenu() { return this->m_dockMenu; } void MacDockIconHandler::setIcon(const QIcon &icon) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSImage *image = nil; if (icon.isNull()) image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; else { // generate NSImage from QIcon and use this as dock icon. QSize size = icon.actualSize(QSize(128, 128)); QPixmap pixmap = icon.pixmap(size); // Write image into a R/W buffer from raw pixmap, then save the image. QBuffer notificationBuffer; if (!pixmap.isNull() && notificationBuffer.open(QIODevice::ReadWrite)) { QImageWriter writer(¬ificationBuffer, "PNG"); if (writer.write(pixmap.toImage())) { NSData* macImgData = [NSData dataWithBytes:notificationBuffer.buffer().data() length:notificationBuffer.buffer().size()]; image = [[NSImage alloc] initWithData:macImgData]; } } if(!image) { // if testnet image could not be created, load std. app icon image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; } } [NSApp setApplicationIconImage:image]; [image release]; [pool release]; } MacDockIconHandler *MacDockIconHandler::instance() { if (!s_instance) s_instance = new MacDockIconHandler(); return s_instance; } void MacDockIconHandler::cleanup() { delete s_instance; } void MacDockIconHandler::handleDockIconClickEvent() { if (this->mainWindow) { this->mainWindow->activateWindow(); this->mainWindow->show(); } Q_EMIT this->dockIconClicked(); } diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index dbc6cc839..e7602dbfc 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -1,90 +1,86 @@ // Copyright (c) 2014-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "networkstyle.h" #include "guiconstants.h" #include static const struct { const char *networkId; const char *appName; const int iconColorHueShift; const int iconColorSaturationReduction; const char *titleAddText; } network_styles[] = {{"main", QAPP_APP_NAME_DEFAULT, 0, 0, ""}, {"test", QAPP_APP_NAME_TESTNET, 70, 30, QT_TRANSLATE_NOOP("SplashScreen", "[testnet]")}, {"regtest", QAPP_APP_NAME_TESTNET, 160, 30, "[regtest]"}}; static const unsigned network_styles_count = sizeof(network_styles) / sizeof(*network_styles); // titleAddText needs to be const char* for tr() NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift, const int iconColorSaturationReduction, const char *_titleAddText) : appName(_appName), titleAddText(qApp->translate("SplashScreen", _titleAddText)) { // load pixmap QPixmap pixmap(":/icons/bitcoin"); if (iconColorHueShift != 0 && iconColorSaturationReduction != 0) { // generate QImage from QPixmap QImage img = pixmap.toImage(); int h, s, l, a; // traverse though lines for (int y = 0; y < img.height(); y++) { QRgb *scL = reinterpret_cast(img.scanLine(y)); // loop through pixels for (int x = 0; x < img.width(); x++) { // preserve alpha because QColor::getHsl doesen't return the // alpha value a = qAlpha(scL[x]); QColor col(scL[x]); // get hue value col.getHsl(&h, &s, &l); // rotate color on RGB color circle // 70° should end up with the typical "testnet" green h += iconColorHueShift; // change saturation value if (s > iconColorSaturationReduction) { s -= iconColorSaturationReduction; } col.setHsl(h, s, l, a); // set the pixel scL[x] = col.rgba(); } } // convert back to QPixmap -#if QT_VERSION >= 0x040700 pixmap.convertFromImage(img); -#else - pixmap = QPixmap::fromImage(img); -#endif } appIcon = QIcon(pixmap); trayAndWindowIcon = QIcon(pixmap.scaled(QSize(256, 256))); } const NetworkStyle *NetworkStyle::instantiate(const QString &networkId) { for (unsigned x = 0; x < network_styles_count; ++x) { if (networkId == network_styles[x].networkId) { return new NetworkStyle( network_styles[x].appName, network_styles[x].iconColorHueShift, network_styles[x].iconColorSaturationReduction, network_styles[x].titleAddText); } } return 0; } diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 92070b57a..9fa1b8e2d 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -1,47 +1,45 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "openuridialog.h" #include "ui_openuridialog.h" #include "guiutil.h" #include "walletmodel.h" #include OpenURIDialog::OpenURIDialog(const Config *cfg, QWidget *parent) : QDialog(parent), ui(new Ui::OpenURIDialog), cfg(cfg) { ui->setupUi(this); -#if QT_VERSION >= 0x040700 ui->uriEdit->setPlaceholderText(GUIUtil::bitcoinURIScheme(*cfg) + ":"); -#endif } OpenURIDialog::~OpenURIDialog() { delete ui; } QString OpenURIDialog::getURI() { return ui->uriEdit->text(); } void OpenURIDialog::accept() { SendCoinsRecipient rcp; QString uriScheme = GUIUtil::bitcoinURIScheme(*cfg); if (GUIUtil::parseBitcoinURI(uriScheme, getURI(), &rcp)) { /* Only accept value URIs */ QDialog::accept(); } else { ui->uriEdit->setValid(false); } } void OpenURIDialog::on_selectFileButton_clicked() { QString filename = GUIUtil::getOpenFileName( this, tr("Select payment request file to open"), "", "", nullptr); if (filename.isEmpty()) return; QUrl fileUri = QUrl::fromLocalFile(filename); ui->uriEdit->setText(GUIUtil::bitcoinURIScheme(*cfg) + ":?r=" + QUrl::toPercentEncoding(fileUri.toString())); } diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index d27d57bea..d22e3c0dd 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -1,353 +1,333 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "optionsdialog.h" #include "ui_optionsdialog.h" #include "bitcoinunits.h" #include "guiutil.h" #include "optionsmodel.h" #include "netbase.h" #include "txdb.h" // for -dbcache defaults #include "validation.h" // for DEFAULT_SCRIPTCHECK_THREADS and MAX_SCRIPTCHECK_THREADS #include #include #include #include #include #include #include OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : QDialog(parent), ui(new Ui::OptionsDialog), model(0), mapper(0) { ui->setupUi(this); /* Main elements init */ ui->databaseCache->setMinimum(nMinDbCache); ui->databaseCache->setMaximum(nMaxDbCache); ui->threadsScriptVerif->setMinimum(-GetNumCores()); ui->threadsScriptVerif->setMaximum(MAX_SCRIPTCHECK_THREADS); /* Network elements init */ #ifndef USE_UPNP ui->mapPortUpnp->setEnabled(false); #endif ui->proxyIp->setEnabled(false); ui->proxyPort->setEnabled(false); ui->proxyPort->setValidator(new QIntValidator(1, 65535, this)); ui->proxyIpTor->setEnabled(false); ui->proxyPortTor->setEnabled(false); ui->proxyPortTor->setValidator(new QIntValidator(1, 65535, this)); connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->proxyIp, SLOT(setEnabled(bool))); connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->proxyPort, SLOT(setEnabled(bool))); connect(ui->connectSocks, SIGNAL(toggled(bool)), this, SLOT(updateProxyValidationState())); connect(ui->connectSocksTor, SIGNAL(toggled(bool)), ui->proxyIpTor, SLOT(setEnabled(bool))); connect(ui->connectSocksTor, SIGNAL(toggled(bool)), ui->proxyPortTor, SLOT(setEnabled(bool))); connect(ui->connectSocksTor, SIGNAL(toggled(bool)), this, SLOT(updateProxyValidationState())); /* Window elements init */ #ifdef Q_OS_MAC /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); #endif /* remove Wallet tab in case of -disablewallet */ if (!enableWallet) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWallet)); } /* Display elements init */ QDir translations(":translations"); ui->bitcoinAtStartup->setToolTip( ui->bitcoinAtStartup->toolTip().arg(tr(PACKAGE_NAME))); ui->bitcoinAtStartup->setText( ui->bitcoinAtStartup->text().arg(tr(PACKAGE_NAME))); ui->lang->setToolTip(ui->lang->toolTip().arg(tr(PACKAGE_NAME))); ui->lang->addItem(QString("(") + tr("default") + QString(")"), QVariant("")); for (const QString &langStr : translations.entryList()) { QLocale locale(langStr); /** check if the locale name consists of 2 parts (language_country) */ if (langStr.contains("_")) { -#if QT_VERSION >= 0x040800 /** display language strings as "native language - native country * (locale name)", e.g. "Deutsch - Deutschland (de)" */ ui->lang->addItem(locale.nativeLanguageName() + QString(" - ") + locale.nativeCountryName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); -#else - /** display language strings as "language - country (locale name)", - * e.g. "German - Germany (de)" */ - ui->lang->addItem(QLocale::languageToString(locale.language()) + - QString(" - ") + - QLocale::countryToString(locale.country()) + - QString(" (") + langStr + QString(")"), - QVariant(langStr)); -#endif } else { -#if QT_VERSION >= 0x040800 /** display language strings as "native language (locale name)", * e.g. "Deutsch (de)" */ ui->lang->addItem(locale.nativeLanguageName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); -#else - /** display language strings as "language (locale name)", e.g. - * "German (de)" */ - ui->lang->addItem(QLocale::languageToString(locale.language()) + - QString(" (") + langStr + QString(")"), - QVariant(langStr)); -#endif } } -#if QT_VERSION >= 0x040700 ui->thirdPartyTxUrls->setPlaceholderText("https://example.com/tx/%s"); -#endif ui->unit->setModel(new BitcoinUnits(this)); /* Widget-to-option mapper */ mapper = new QDataWidgetMapper(this); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); mapper->setOrientation(Qt::Vertical); /* setup/change UI elements when proxy IPs are invalid/valid */ ui->proxyIp->setCheckValidator(new ProxyAddressValidator(parent)); ui->proxyIpTor->setCheckValidator(new ProxyAddressValidator(parent)); connect(ui->proxyIp, SIGNAL(validationDidChange(QValidatedLineEdit *)), this, SLOT(updateProxyValidationState())); connect(ui->proxyIpTor, SIGNAL(validationDidChange(QValidatedLineEdit *)), this, SLOT(updateProxyValidationState())); connect(ui->proxyPort, SIGNAL(textChanged(const QString &)), this, SLOT(updateProxyValidationState())); connect(ui->proxyPortTor, SIGNAL(textChanged(const QString &)), this, SLOT(updateProxyValidationState())); } OptionsDialog::~OptionsDialog() { delete ui; } void OptionsDialog::setModel(OptionsModel *_model) { this->model = _model; if (_model) { /* check if client restart is needed and show persistent message */ if (_model->isRestartRequired()) showRestartWarning(true); QString strLabel = _model->getOverriddenByCommandLine(); if (strLabel.isEmpty()) strLabel = tr("none"); ui->overriddenByCommandLineLabel->setText(strLabel); mapper->setModel(_model); setMapper(); mapper->toFirst(); updateDefaultProxyNets(); } /* warn when one of the following settings changes by user action (placed * here so init via mapper doesn't trigger them) */ /* Main */ connect(ui->databaseCache, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); connect(ui->threadsScriptVerif, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); /* Wallet */ connect(ui->spendZeroConfChange, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning())); /* Network */ connect(ui->allowIncoming, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning())); connect(ui->connectSocks, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning())); connect(ui->connectSocksTor, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning())); /* Display */ connect(ui->lang, SIGNAL(valueChanged()), this, SLOT(showRestartWarning())); connect(ui->thirdPartyTxUrls, SIGNAL(textChanged(const QString &)), this, SLOT(showRestartWarning())); } void OptionsDialog::setMapper() { /* Main */ mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup); mapper->addMapping(ui->threadsScriptVerif, OptionsModel::ThreadsScriptVerif); mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache); /* Wallet */ mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); /* Network */ mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP); mapper->addMapping(ui->allowIncoming, OptionsModel::Listen); mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse); mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP); mapper->addMapping(ui->proxyPort, OptionsModel::ProxyPort); mapper->addMapping(ui->connectSocksTor, OptionsModel::ProxyUseTor); mapper->addMapping(ui->proxyIpTor, OptionsModel::ProxyIPTor); mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor); /* Window */ #ifndef Q_OS_MAC mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose); #endif /* Display */ mapper->addMapping(ui->lang, OptionsModel::Language); mapper->addMapping(ui->unit, OptionsModel::DisplayUnit); mapper->addMapping(ui->thirdPartyTxUrls, OptionsModel::ThirdPartyTxUrls); } void OptionsDialog::setOkButtonState(bool fState) { ui->okButton->setEnabled(fState); } void OptionsDialog::on_resetButton_clicked() { if (model) { // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question( this, tr("Confirm options reset"), tr("Client restart required to activate changes.") + "

" + tr("Client will be shut down. Do you want to proceed?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if (btnRetVal == QMessageBox::Cancel) return; /* reset all options and close GUI */ model->Reset(); QApplication::quit(); } } void OptionsDialog::on_okButton_clicked() { mapper->submit(); accept(); updateDefaultProxyNets(); } void OptionsDialog::on_cancelButton_clicked() { reject(); } void OptionsDialog::on_hideTrayIcon_stateChanged(int fState) { if (fState) { ui->minimizeToTray->setChecked(false); ui->minimizeToTray->setEnabled(false); } else { ui->minimizeToTray->setEnabled(true); } } void OptionsDialog::showRestartWarning(bool fPersistent) { ui->statusLabel->setStyleSheet("QLabel { color: red; }"); if (fPersistent) { ui->statusLabel->setText( tr("Client restart required to activate changes.")); } else { ui->statusLabel->setText( tr("This change would require a client restart.")); // clear non-persistent status label after 10 seconds // TODO: should perhaps be a class attribute, if we extend the use of // statusLabel QTimer::singleShot(10000, this, SLOT(clearStatusLabel())); } } void OptionsDialog::clearStatusLabel() { ui->statusLabel->clear(); if (model && model->isRestartRequired()) { showRestartWarning(true); } } void OptionsDialog::updateProxyValidationState() { QValidatedLineEdit *pUiProxyIp = ui->proxyIp; QValidatedLineEdit *otherProxyWidget = (pUiProxyIp == ui->proxyIpTor) ? ui->proxyIp : ui->proxyIpTor; if (pUiProxyIp->isValid() && (!ui->proxyPort->isEnabled() || ui->proxyPort->text().toInt() > 0) && (!ui->proxyPortTor->isEnabled() || ui->proxyPortTor->text().toInt() > 0)) { // Only enable ok button if both proxys are valid setOkButtonState(otherProxyWidget->isValid()); clearStatusLabel(); } else { setOkButtonState(false); ui->statusLabel->setStyleSheet("QLabel { color: red; }"); ui->statusLabel->setText(tr("The supplied proxy address is invalid.")); } } void OptionsDialog::updateDefaultProxyNets() { proxyType proxy; std::string strProxy; QString strDefaultProxyGUI; GetProxy(NET_IPV4, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv4->setChecked(true) : ui->proxyReachIPv4->setChecked(false); GetProxy(NET_IPV6, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv6->setChecked(true) : ui->proxyReachIPv6->setChecked(false); GetProxy(NET_TOR, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachTor->setChecked(true) : ui->proxyReachTor->setChecked(false); } ProxyAddressValidator::ProxyAddressValidator(QObject *parent) : QValidator(parent) {} QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); // Validate the proxy CService serv(LookupNumeric(input.toStdString().c_str(), 9050)); proxyType addrProxy = proxyType(serv, true); if (addrProxy.IsValid()) return QValidator::Acceptable; return QValidator::Invalid; } diff --git a/src/qt/paymentrequestplus.cpp b/src/qt/paymentrequestplus.cpp index 02d055082..e88e793a7 100644 --- a/src/qt/paymentrequestplus.cpp +++ b/src/qt/paymentrequestplus.cpp @@ -1,237 +1,235 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // // Wraps dumb protocol buffer paymentRequest with some extra methods // #include "paymentrequestplus.h" #include "util.h" #include #include #include #include #include class SSLVerifyError : public std::runtime_error { public: SSLVerifyError(std::string err) : std::runtime_error(err) {} }; bool PaymentRequestPlus::parse(const QByteArray &data) { bool parseOK = paymentRequest.ParseFromArray(data.data(), data.size()); if (!parseOK) { qWarning() << "PaymentRequestPlus::parse: Error parsing payment request"; return false; } if (paymentRequest.payment_details_version() > 1) { qWarning() << "PaymentRequestPlus::parse: Received up-version payment " "details, version=" << paymentRequest.payment_details_version(); return false; } parseOK = details.ParseFromString(paymentRequest.serialized_payment_details()); if (!parseOK) { qWarning() << "PaymentRequestPlus::parse: Error parsing payment details"; paymentRequest.Clear(); return false; } return true; } bool PaymentRequestPlus::SerializeToString(std::string *output) const { return paymentRequest.SerializeToString(output); } bool PaymentRequestPlus::IsInitialized() const { return paymentRequest.IsInitialized(); } bool PaymentRequestPlus::getMerchant(X509_STORE *certStore, QString &merchant) const { merchant.clear(); if (!IsInitialized()) return false; // One day we'll support more PKI types, but just x509 for now: const EVP_MD *digestAlgorithm = nullptr; if (paymentRequest.pki_type() == "x509+sha256") { digestAlgorithm = EVP_sha256(); } else if (paymentRequest.pki_type() == "x509+sha1") { digestAlgorithm = EVP_sha1(); } else if (paymentRequest.pki_type() == "none") { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: " "pki_type == none"; return false; } else { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: " "unknown pki_type " << QString::fromStdString(paymentRequest.pki_type()); return false; } payments::X509Certificates certChain; if (!certChain.ParseFromString(paymentRequest.pki_data())) { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: error " "parsing pki_data"; return false; } std::vector certs; const QDateTime currentTime = QDateTime::currentDateTime(); for (int i = 0; i < certChain.certificate_size(); i++) { QByteArray certData(certChain.certificate(i).data(), certChain.certificate(i).size()); QSslCertificate qCert(certData, QSsl::Der); if (currentTime < qCert.effectiveDate() || currentTime > qCert.expiryDate()) { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: " "certificate expired or not yet active: " << qCert; return false; } -#if QT_VERSION >= 0x050000 if (qCert.isBlacklisted()) { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: " "certificate blacklisted: " << qCert; return false; } -#endif const uint8_t *data = (const uint8_t *)certChain.certificate(i).data(); X509 *cert = d2i_X509(nullptr, &data, certChain.certificate(i).size()); if (cert) certs.push_back(cert); } if (certs.empty()) { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: empty " "certificate chain"; return false; } // The first cert is the signing cert, the rest are untrusted certs that // chain to a valid root authority. OpenSSL needs them separately. STACK_OF(X509) *chain = sk_X509_new_null(); for (int i = certs.size() - 1; i > 0; i--) { sk_X509_push(chain, certs[i]); } X509 *signing_cert = certs[0]; // Now create a "store context", which is a single use object for checking, // load the signing cert into it and verify. X509_STORE_CTX *store_ctx = X509_STORE_CTX_new(); if (!store_ctx) { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: error " "creating X509_STORE_CTX"; return false; } char *website = nullptr; bool fResult = true; try { if (!X509_STORE_CTX_init(store_ctx, certStore, signing_cert, chain)) { int error = X509_STORE_CTX_get_error(store_ctx); throw SSLVerifyError(X509_verify_cert_error_string(error)); } // Now do the verification! int result = X509_verify_cert(store_ctx); if (result != 1) { int error = X509_STORE_CTX_get_error(store_ctx); // For testing payment requests, we allow self signed root certs! // This option is just shown in the UI options, if -help-debug is // enabled. if (!(error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && gArgs.GetBoolArg("-allowselfsignedrootcertificates", DEFAULT_SELFSIGNED_ROOTCERTS))) { throw SSLVerifyError(X509_verify_cert_error_string(error)); } else { qDebug() << "PaymentRequestPlus::getMerchant: Allowing self " "signed root certificate, because " "-allowselfsignedrootcertificates is true."; } } X509_NAME *certname = X509_get_subject_name(signing_cert); // Valid cert; check signature: // Copy payments::PaymentRequest rcopy(paymentRequest); rcopy.set_signature(std::string("")); // Everything but the signature std::string data_to_verify; rcopy.SerializeToString(&data_to_verify); #if HAVE_DECL_EVP_MD_CTX_NEW EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (!ctx) throw SSLVerifyError("Error allocating OpenSSL context."); #else EVP_MD_CTX _ctx; EVP_MD_CTX *ctx; ctx = &_ctx; #endif EVP_PKEY *pubkey = X509_get_pubkey(signing_cert); EVP_MD_CTX_init(ctx); if (!EVP_VerifyInit_ex(ctx, digestAlgorithm, nullptr) || !EVP_VerifyUpdate(ctx, data_to_verify.data(), data_to_verify.size()) || !EVP_VerifyFinal( ctx, (const uint8_t *)paymentRequest.signature().data(), (unsigned int)paymentRequest.signature().size(), pubkey)) { throw SSLVerifyError("Bad signature, invalid payment request."); } #if HAVE_DECL_EVP_MD_CTX_NEW EVP_MD_CTX_free(ctx); #endif // OpenSSL API for getting human printable strings from certs is // baroque. int textlen = X509_NAME_get_text_by_NID(certname, NID_commonName, nullptr, 0); website = new char[textlen + 1]; if (X509_NAME_get_text_by_NID(certname, NID_commonName, website, textlen + 1) == textlen && textlen > 0) { merchant = website; } else { throw SSLVerifyError("Bad certificate, missing common name."); } // TODO: detect EV certificates and set merchant = business name instead // of unfriendly NID_commonName ? } catch (const SSLVerifyError &err) { fResult = false; qWarning() << "PaymentRequestPlus::getMerchant: SSL error: " << err.what(); } if (website) { delete[] website; } X509_STORE_CTX_free(store_ctx); for (size_t i = 0; i < certs.size(); i++) { X509_free(certs[i]); } return fResult; } QList> PaymentRequestPlus::getPayTo() const { QList> result; for (int i = 0; i < details.outputs_size(); i++) { const uint8_t *scriptStr = (const uint8_t *)details.outputs(i).script().data(); CScript s(scriptStr, scriptStr + details.outputs(i).script().size()); result.append( std::make_pair(s, int64_t(details.outputs(i).amount()) * SATOSHI)); } return result; } diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index aeb1a82c4..713426b26 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -1,900 +1,881 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "paymentserver.h" #include "bitcoinunits.h" #include "guiutil.h" #include "optionsmodel.h" #include "chainparams.h" #include "config.h" #include "dstencode.h" #include "policy/policy.h" #include "ui_interface.h" #include "util.h" #include "wallet/wallet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - -#if QT_VERSION < 0x050000 -#include -#else #include -#endif const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds // BIP70 payment protocol messages const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; // BIP71 payment protocol media types const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment"; const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack"; const char *BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoincash-paymentrequest"; struct X509StoreDeleter { void operator()(X509_STORE *b) { X509_STORE_free(b); } }; struct X509Deleter { void operator()(X509 *b) { X509_free(b); } }; namespace // Anon namespace { std::unique_ptr certStore; } // // Create a name that is unique for: // testnet / non-testnet // data directory // static QString ipcServerName() { QString name("BitcoinQt"); // Append a simple hash of the datadir // Note that GetDataDir(true) returns a different path for -testnet versus // main net QString ddir(GUIUtil::boostPathToQString(GetDataDir(true))); name.append(QString::number(qHash(ddir))); return name; } // // We store payment URIs and requests received before the main GUI window is up // and ready to ask the user to send payment. // static QList savedPaymentRequests; static void ReportInvalidCertificate(const QSslCertificate &cert) { -#if QT_VERSION < 0x050000 - qDebug() << QString("%1: Payment server found an invalid certificate: ") - .arg(__func__) - << cert.serialNumber() - << cert.subjectInfo(QSslCertificate::CommonName) - << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); -#else qDebug() << QString("%1: Payment server found an invalid certificate: ") .arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); -#endif } // // Load OpenSSL's list of root certificate authorities // void PaymentServer::LoadRootCAs(X509_STORE *_store) { // Unit tests mostly use this, to pass in fake root CAs: if (_store) { certStore.reset(_store); return; } // Normal execution, use either -rootcertificates or system certs: certStore.reset(X509_STORE_new()); // Note: use "-system-" default here so that users can pass // -rootcertificates="" and get 'I don't like X.509 certificates, don't // trust anybody' behavior: QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-")); // Empty store if (certFile.isEmpty()) { qDebug() << QString("PaymentServer::%1: Payment request authentication " "via X.509 certificates disabled.") .arg(__func__); return; } QList certList; if (certFile != "-system-") { qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root " "certificate.") .arg(__func__) .arg(certFile); certList = QSslCertificate::fromPath(certFile); // Use those certificates when fetching payment requests, too: QSslSocket::setDefaultCaCertificates(certList); } else certList = QSslSocket::systemCaCertificates(); int nRootCerts = 0; const QDateTime currentTime = QDateTime::currentDateTime(); for (const QSslCertificate &cert : certList) { // Don't log nullptr certificates if (cert.isNull()) { continue; } // Not yet active/valid, or expired certificate if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { ReportInvalidCertificate(cert); continue; } -#if QT_VERSION >= 0x050000 // Blacklisted certificate if (cert.isBlacklisted()) { ReportInvalidCertificate(cert); continue; } -#endif QByteArray certData = cert.toDer(); const uint8_t *data = (const uint8_t *)certData.data(); std::unique_ptr x509( d2i_X509(0, &data, certData.size())); if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) { // Note: X509_STORE increases the reference count to the X509 // object, we still have to release our reference to it. ++nRootCerts; } else { ReportInvalidCertificate(cert); continue; } } qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates"; // Project for another day: // Fetch certificate revocation lists, and add them to certStore. // Issues to consider: // performance (start a thread to fetch in background?) // privacy (fetch through tor/proxy so IP address isn't revealed) // would it be easier to just use a compiled-in blacklist? // or use Qt's blacklist? // "certificate stapling" with server-side caching is more efficient } static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms, bool useCashAddr) { const QString scheme = GUIUtil::bitcoinURIScheme(params, useCashAddr); if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) { return {}; } SendCoinsRecipient r; if (!GUIUtil::parseBitcoinURI(scheme, arg, &r)) { return {}; } return r.address.toStdString(); } static bool ipcCanParseCashAddrURI(const QString &arg, const std::string &network) { auto tempChainParams = CreateChainParams(network); std::string addr = ipcParseURI(arg, *tempChainParams, true); return IsValidDestinationString(addr, *tempChainParams); } static bool ipcCanParseLegacyURI(const QString &arg, const std::string &network) { auto tempChainParams = CreateChainParams(network); std::string addr = ipcParseURI(arg, *tempChainParams, false); return IsValidDestinationString(addr, *tempChainParams); } // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, and the items in // savedPaymentRequest will be handled when uiReady() is called. // // Warning: ipcSendCommandLine() is called early in init, so don't use "Q_EMIT // message()", but "QMessageBox::"! // void PaymentServer::ipcParseCommandLine(int argc, char *argv[]) { std::array networks = { {&CBaseChainParams::MAIN, &CBaseChainParams::TESTNET, &CBaseChainParams::REGTEST}}; const std::string *chosenNetwork = nullptr; for (int i = 1; i < argc; i++) { QString arg(argv[i]); if (arg.startsWith("-")) { continue; } const std::string *itemNetwork = nullptr; // Try to parse as a URI for (auto net : networks) { if (ipcCanParseCashAddrURI(arg, *net)) { itemNetwork = net; break; } if (ipcCanParseLegacyURI(arg, *net)) { itemNetwork = net; break; } } if (!itemNetwork && QFile::exists(arg)) { // Filename PaymentRequestPlus request; if (readPaymentRequestFromFile(arg, request)) { for (auto net : networks) { if (*net == request.getDetails().network()) { itemNetwork = net; } } } } if (itemNetwork == nullptr) { // Printing to debug.log is about the best we can do here, the GUI // hasn't started yet so we can't pop up a message box. qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " "file or URI does not exist or is invalid: " << arg; continue; } if (chosenNetwork && chosenNetwork != itemNetwork) { qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " "from network " << QString(itemNetwork->c_str()) << " does not match already chosen network " << QString(chosenNetwork->c_str()); continue; } savedPaymentRequests.append(arg); chosenNetwork = itemNetwork; } if (chosenNetwork) { SelectParams(*chosenNetwork); } } // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, and the items in // savedPaymentRequest will be handled when uiReady() is called. // bool PaymentServer::ipcSendCommandLine() { bool fResult = false; for (const QString &r : savedPaymentRequests) { QLocalSocket *socket = new QLocalSocket(); socket->connectToServer(ipcServerName(), QIODevice::WriteOnly); if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT)) { delete socket; socket = nullptr; return false; } QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out << r; out.device()->seek(0); socket->write(block); socket->flush(); socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT); socket->disconnectFromServer(); delete socket; socket = nullptr; fResult = true; } return fResult; } PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) : QObject(parent), saveURIs(true), uriServer(0), netManager(0), optionsModel(0) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; // Install global event filter to catch QFileOpenEvents // on Mac: sent when you click bitcoincash: links // other OSes: helpful when dealing with payment request files if (parent) { parent->installEventFilter(this); } QString name = ipcServerName(); // Clean up old socket leftover from a crash: QLocalServer::removeServer(name); if (startLocalServer) { uriServer = new QLocalServer(this); if (!uriServer->listen(name)) { // constructor is called early in init, so don't use "Q_EMIT // message()" here QMessageBox::critical(0, tr("Payment request error"), tr("Cannot start click-to-pay handler")); } else { connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString))); } } } PaymentServer::~PaymentServer() { google::protobuf::ShutdownProtobufLibrary(); } // // OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types. // Also used by paymentservertests.cpp and when opening a payment request file // via "Open URI..." menu entry. // bool PaymentServer::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *fileEvent = static_cast(event); if (!fileEvent->file().isEmpty()) { handleURIOrFile(fileEvent->file()); } else if (!fileEvent->url().isEmpty()) { handleURIOrFile(fileEvent->url().toString()); } return true; } return QObject::eventFilter(object, event); } void PaymentServer::initNetManager() { if (!optionsModel) { return; } if (netManager != nullptr) { delete netManager; } // netManager is used to fetch paymentrequests given in bitcoincash: URIs netManager = new QNetworkAccessManager(this); QNetworkProxy proxy; // Query active SOCKS5 proxy if (optionsModel->getProxySettings(proxy)) { netManager->setProxy(proxy); qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); } else { qDebug() << "PaymentServer::initNetManager: No active proxy server found."; } connect(netManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(netRequestFinished(QNetworkReply *))); connect(netManager, SIGNAL(sslErrors(QNetworkReply *, const QList &)), this, SLOT(reportSslErrors(QNetworkReply *, const QList &))); } void PaymentServer::uiReady() { initNetManager(); saveURIs = false; for (const QString &s : savedPaymentRequests) { handleURIOrFile(s); } savedPaymentRequests.clear(); } bool PaymentServer::handleURI(const QString &scheme, const QString &s) { if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) { return false; } -#if QT_VERSION < 0x050000 - QUrl uri(s); -#else QUrlQuery uri((QUrl(s))); -#endif if (uri.hasQueryItem("r")) { // payment request URI QByteArray temp; temp.append(uri.queryItemValue("r")); QString decoded = QUrl::fromPercentEncoding(temp); QUrl fetchUrl(decoded, QUrl::StrictMode); if (fetchUrl.isValid()) { qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")"; fetchRequest(fetchUrl); } else { qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl; Q_EMIT message(tr("URI handling"), tr("Payment request fetch URL is invalid: %1") .arg(fetchUrl.toString()), CClientUIInterface::ICON_WARNING); } return true; } // normal URI SendCoinsRecipient recipient; if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { if (!IsValidDestinationString(recipient.address.toStdString(), GetConfig().GetChainParams())) { Q_EMIT message( tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address), CClientUIInterface::MSG_ERROR); } else { Q_EMIT receivedPaymentRequest(recipient); } } else { Q_EMIT message( tr("URI handling"), tr("URI cannot be parsed! This can be caused by an invalid " "Bitcoin address or malformed URI parameters."), CClientUIInterface::ICON_WARNING); } return true; } void PaymentServer::handleURIOrFile(const QString &s) { if (saveURIs) { savedPaymentRequests.append(s); return; } // bitcoincash: CashAddr URI QString schemeCash = GUIUtil::bitcoinURIScheme(Params(), true); if (handleURI(schemeCash, s)) { return; } // bitcoincash: Legacy URI QString schemeLegacy = GUIUtil::bitcoinURIScheme(Params(), false); if (handleURI(schemeLegacy, s)) { return; } // payment request file if (QFile::exists(s)) { PaymentRequestPlus request; SendCoinsRecipient recipient; if (!readPaymentRequestFromFile(s, request)) { Q_EMIT message(tr("Payment request file handling"), tr("Payment request file cannot be read! This can " "be caused by an invalid payment request file."), CClientUIInterface::ICON_WARNING); } else if (processPaymentRequest(request, recipient)) { Q_EMIT receivedPaymentRequest(recipient); } return; } } void PaymentServer::handleURIConnection() { QLocalSocket *clientConnection = uriServer->nextPendingConnection(); while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) { clientConnection->waitForReadyRead(); } connect(clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater())); QDataStream in(clientConnection); in.setVersion(QDataStream::Qt_4_0); if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) { return; } QString msg; in >> msg; handleURIOrFile(msg); } // // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine() // so don't use "Q_EMIT message()", but "QMessageBox::"! // bool PaymentServer::readPaymentRequestFromFile(const QString &filename, PaymentRequestPlus &request) { QFile f(filename); if (!f.open(QIODevice::ReadOnly)) { qWarning() << QString("PaymentServer::%1: Failed to open %2") .arg(__func__) .arg(filename); return false; } // BIP70 DoS protection if (!verifySize(f.size())) { return false; } QByteArray data = f.readAll(); return request.parse(data); } bool PaymentServer::processPaymentRequest(const PaymentRequestPlus &request, SendCoinsRecipient &recipient) { if (!optionsModel) { return false; } if (request.IsInitialized()) { // Payment request network matches client network? if (!verifyNetwork(request.getDetails())) { Q_EMIT message( tr("Payment request rejected"), tr("Payment request network doesn't match client network."), CClientUIInterface::MSG_ERROR); return false; } // Make sure any payment requests involved are still valid. // This is re-checked just before sending coins in // WalletModel::sendCoins(). if (verifyExpired(request.getDetails())) { Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."), CClientUIInterface::MSG_ERROR); return false; } } else { Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."), CClientUIInterface::MSG_ERROR); return false; } recipient.paymentRequest = request; recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo()); request.getMerchant(certStore.get(), recipient.authenticatedMerchant); QList> sendingTos = request.getPayTo(); QStringList addresses; for (const std::pair &sendingTo : sendingTos) { // Extract and check destination addresses CTxDestination dest; if (ExtractDestination(sendingTo.first, dest)) { // Append destination address addresses.append(QString::fromStdString(EncodeDestination(dest))); } else if (!recipient.authenticatedMerchant.isEmpty()) { // Unauthenticated payment requests to custom bitcoin addresses are // not supported // (there is no good way to tell the user where they are paying in a // way they'd // have a chance of understanding). Q_EMIT message(tr("Payment request rejected"), tr("Unverified payment requests to custom payment " "scripts are unsupported."), CClientUIInterface::MSG_ERROR); return false; } // Bitcoin amounts are stored as (optional) uint64 in the protobuf // messages (see paymentrequest.proto), but Amount is defined as // int64_t. Because of that we need to verify that amounts are in a // valid range and no overflow has happened. if (!verifyAmount(sendingTo.second)) { Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR); return false; } // Extract and check amounts CTxOut txOut(Amount(sendingTo.second), sendingTo.first); if (txOut.IsDust(dustRelayFee)) { Q_EMIT message( tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered " "dust).") .arg(BitcoinUnits::formatWithUnit( optionsModel->getDisplayUnit(), sendingTo.second)), CClientUIInterface::MSG_ERROR); return false; } recipient.amount += sendingTo.second; // Also verify that the final amount is still in a valid range after // adding additional amounts. if (!verifyAmount(recipient.amount)) { Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR); return false; } } // Store addresses and format them to fit nicely into the GUI recipient.address = addresses.join("
"); if (!recipient.authenticatedMerchant.isEmpty()) { qDebug() << "PaymentServer::processPaymentRequest: Secure payment " "request from " << recipient.authenticatedMerchant; } else { qDebug() << "PaymentServer::processPaymentRequest: Insecure payment " "request to " << addresses.join(", "); } return true; } void PaymentServer::fetchRequest(const QUrl &url) { QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST); netRequest.setUrl(url); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST); netManager->get(netRequest); } void PaymentServer::fetchPaymentACK(CWallet *wallet, SendCoinsRecipient recipient, QByteArray transaction) { const payments::PaymentDetails &details = recipient.paymentRequest.getDetails(); if (!details.has_payment_url()) { return; } QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK); netRequest.setUrl(QString::fromStdString(details.payment_url())); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK); payments::Payment payment; payment.set_merchant_data(details.merchant_data()); payment.add_transactions(transaction.data(), transaction.size()); // Create a new refund address, or re-use: QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant); std::string strAccount = account.toStdString(); std::set refundAddresses = wallet->GetAccountAddresses(strAccount); if (!refundAddresses.empty()) { CScript s = GetScriptForDestination(*refundAddresses.begin()); payments::Output *refund_to = payment.add_refund_to(); refund_to->set_script(&s[0], s.size()); } else { CPubKey newKey; if (wallet->GetKeyFromPool(newKey)) { CKeyID keyID = newKey.GetID(); wallet->SetAddressBook(keyID, strAccount, "refund"); CScript s = GetScriptForDestination(keyID); payments::Output *refund_to = payment.add_refund_to(); refund_to->set_script(&s[0], s.size()); } else { // This should never happen, because sending coins should have just // unlocked the wallet and refilled the keypool. qWarning() << "PaymentServer::fetchPaymentACK: Error getting " "refund key, refund_to not set"; } } int length = payment.ByteSize(); netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length); QByteArray serData(length, '\0'); if (payment.SerializeToArray(serData.data(), length)) { netManager->post(netRequest, serData); } else { // This should never happen, either. qWarning() << "PaymentServer::fetchPaymentACK: Error serializing " "payment message"; } } void PaymentServer::netRequestFinished(QNetworkReply *reply) { reply->deleteLater(); // BIP70 DoS protection if (!verifySize(reply->size())) { Q_EMIT message( tr("Payment request rejected"), tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).") .arg(reply->request().url().toString()) .arg(reply->size()) .arg(BIP70_MAX_PAYMENTREQUEST_SIZE), CClientUIInterface::MSG_ERROR); return; } if (reply->error() != QNetworkReply::NoError) { QString msg = tr("Error communicating with %1: %2") .arg(reply->request().url().toString()) .arg(reply->errorString()); qWarning() << "PaymentServer::netRequestFinished: " << msg; Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); return; } QByteArray data = reply->readAll(); QString requestType = reply->request().attribute(QNetworkRequest::User).toString(); if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) { PaymentRequestPlus request; SendCoinsRecipient recipient; if (!request.parse(data)) { qWarning() << "PaymentServer::netRequestFinished: Error parsing " "payment request"; Q_EMIT message(tr("Payment request error"), tr("Payment request cannot be parsed!"), CClientUIInterface::MSG_ERROR); } else if (processPaymentRequest(request, recipient)) { Q_EMIT receivedPaymentRequest(recipient); } return; } else if (requestType == BIP70_MESSAGE_PAYMENTACK) { payments::PaymentACK paymentACK; if (!paymentACK.ParseFromArray(data.data(), data.size())) { QString msg = tr("Bad response from server %1") .arg(reply->request().url().toString()); qWarning() << "PaymentServer::netRequestFinished: " << msg; Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); } else { Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo())); } } } void PaymentServer::reportSslErrors(QNetworkReply *reply, const QList &errs) { Q_UNUSED(reply); QString errString; for (const QSslError &err : errs) { qWarning() << "PaymentServer::reportSslErrors: " << err; errString += err.errorString() + "\n"; } Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR); } void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { this->optionsModel = _optionsModel; } void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) { // currently we don't further process or store the paymentACK message Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL); } bool PaymentServer::verifyNetwork( const payments::PaymentDetails &requestDetails) { bool fVerified = requestDetails.network() == Params().NetworkIDString(); if (!fVerified) { qWarning() << QString("PaymentServer::%1: Payment request network " "\"%2\" doesn't match client network \"%3\".") .arg(__func__) .arg(QString::fromStdString(requestDetails.network())) .arg(QString::fromStdString( Params().NetworkIDString())); } return fVerified; } bool PaymentServer::verifyExpired( const payments::PaymentDetails &requestDetails) { bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime()); if (fVerified) { const QString requestExpires = QString::fromStdString(DateTimeStrFormat( "%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires())); qWarning() << QString( "PaymentServer::%1: Payment request expired \"%2\".") .arg(__func__) .arg(requestExpires); } return fVerified; } bool PaymentServer::verifySize(qint64 requestSize) { bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE); if (!fVerified) { qWarning() << QString("PaymentServer::%1: Payment request too large " "(%2 bytes, allowed %3 bytes).") .arg(__func__) .arg(requestSize) .arg(BIP70_MAX_PAYMENTREQUEST_SIZE); } return fVerified; } bool PaymentServer::verifyAmount(const Amount requestAmount) { bool fVerified = MoneyRange(Amount(requestAmount)); if (!fVerified) { qWarning() << QString("PaymentServer::%1: Payment request amount out " "of allowed range (%2, allowed 0 - %3).") .arg(__func__) .arg(requestAmount / SATOSHI) .arg(MAX_MONEY / SATOSHI); } return fVerified; } X509_STORE *PaymentServer::getCertStore() { return certStore.get(); } diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 2adf9bd6a..736968395 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -1,218 +1,216 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "peertablemodel.h" #include "clientmodel.h" #include "guiconstants.h" #include "guiutil.h" #include "sync.h" #include "validation.h" // for cs_main #include #include #include bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombinedStats &right) const { const CNodeStats *pLeft = &(left.nodeStats); const CNodeStats *pRight = &(right.nodeStats); if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); switch (column) { case PeerTableModel::NetNodeId: return pLeft->nodeid < pRight->nodeid; case PeerTableModel::Address: return pLeft->addrName.compare(pRight->addrName) < 0; case PeerTableModel::Subversion: return pLeft->cleanSubVer.compare(pRight->cleanSubVer) < 0; case PeerTableModel::Ping: return pLeft->dMinPing < pRight->dMinPing; } return false; } // private implementation class PeerTablePriv { public: /** Local cache of peer information */ QList cachedNodeStats; /** Column to sort nodes by */ int sortColumn; /** Order (ascending or descending) to sort nodes by */ Qt::SortOrder sortOrder; /** Index of rows by node ID */ std::map mapNodeRows; /** Pull a full list of peers from vNodes into our cache */ void refreshPeers() { { cachedNodeStats.clear(); std::vector vstats; if (g_connman) g_connman->GetNodeStats(vstats); -#if QT_VERSION >= 0x040700 cachedNodeStats.reserve(vstats.size()); -#endif for (const CNodeStats &nodestats : vstats) { CNodeCombinedStats stats; stats.nodeStateStats.nMisbehavior = 0; stats.nodeStateStats.nSyncHeight = -1; stats.nodeStateStats.nCommonHeight = -1; stats.fNodeStateStatsAvailable = false; stats.nodeStats = nodestats; cachedNodeStats.append(stats); } } // Try to retrieve the CNodeStateStats for each node. { TRY_LOCK(cs_main, lockMain); if (lockMain) { for (CNodeCombinedStats &stats : cachedNodeStats) { stats.fNodeStateStatsAvailable = GetNodeStateStats( stats.nodeStats.nodeid, stats.nodeStateStats); } } } if (sortColumn >= 0) // sort cacheNodeStats (use stable sort to prevent rows jumping // around unnecessarily) qStableSort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder)); // build index map mapNodeRows.clear(); int row = 0; for (const CNodeCombinedStats &stats : cachedNodeStats) { mapNodeRows.insert( std::pair(stats.nodeStats.nodeid, row++)); } } int size() const { return cachedNodeStats.size(); } CNodeCombinedStats *index(int idx) { if (idx >= 0 && idx < cachedNodeStats.size()) return &cachedNodeStats[idx]; return 0; } }; PeerTableModel::PeerTableModel(ClientModel *parent) : QAbstractTableModel(parent), clientModel(parent), timer(0) { columns << tr("NodeId") << tr("Node/Service") << tr("User Agent") << tr("Ping"); priv.reset(new PeerTablePriv()); // default to unsorted priv->sortColumn = -1; // set up timer for auto refresh timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(refresh())); timer->setInterval(MODEL_UPDATE_DELAY); // load initial data refresh(); } PeerTableModel::~PeerTableModel() { // Intentionally left empty } void PeerTableModel::startAutoRefresh() { timer->start(); } void PeerTableModel::stopAutoRefresh() { timer->stop(); } int PeerTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return priv->size(); } int PeerTableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return columns.length(); } QVariant PeerTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); CNodeCombinedStats *rec = static_cast(index.internalPointer()); if (role == Qt::DisplayRole) { switch (index.column()) { case NetNodeId: return (qint64)rec->nodeStats.nodeid; case Address: return QString::fromStdString(rec->nodeStats.addrName); case Subversion: return QString::fromStdString(rec->nodeStats.cleanSubVer); case Ping: return GUIUtil::formatPingTime(rec->nodeStats.dMinPing); } } else if (role == Qt::TextAlignmentRole) { if (index.column() == Ping) return (QVariant)(Qt::AlignRight | Qt::AlignVCenter); } return QVariant(); } QVariant PeerTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && section < columns.size()) { return columns[section]; } } return QVariant(); } Qt::ItemFlags PeerTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return 0; Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; return retval; } QModelIndex PeerTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); CNodeCombinedStats *data = priv->index(row); if (data) return createIndex(row, column, data); return QModelIndex(); } const CNodeCombinedStats *PeerTableModel::getNodeStats(int idx) { return priv->index(idx); } void PeerTableModel::refresh() { Q_EMIT layoutAboutToBeChanged(); priv->refreshPeers(); Q_EMIT layoutChanged(); } int PeerTableModel::getRowByNodeId(NodeId nodeid) { std::map::iterator it = priv->mapNodeRows.find(nodeid); if (it == priv->mapNodeRows.end()) return -1; return it->second; } void PeerTableModel::sort(int column, Qt::SortOrder order) { priv->sortColumn = column; priv->sortOrder = order; refresh(); } diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 90859e8fb..a3e62d3c4 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -1,202 +1,199 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "receiverequestdialog.h" #include "ui_receiverequestdialog.h" #include "bitcoinunits.h" #include "config.h" #include "guiconstants.h" #include "guiutil.h" #include "optionsmodel.h" #include "walletmodel.h" #include #include #include #include #include #include -#if QT_VERSION < 0x050000 -#include -#endif #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" /* for USE_QRCODE */ #endif #ifdef USE_QRCODE #include #endif QRImageWidget::QRImageWidget(QWidget *parent) : QLabel(parent), contextMenu(0) { contextMenu = new QMenu(this); QAction *saveImageAction = new QAction(tr("&Save Image..."), this); connect(saveImageAction, SIGNAL(triggered()), this, SLOT(saveImage())); contextMenu->addAction(saveImageAction); QAction *copyImageAction = new QAction(tr("&Copy Image"), this); connect(copyImageAction, SIGNAL(triggered()), this, SLOT(copyImage())); contextMenu->addAction(copyImageAction); } QImage QRImageWidget::exportImage() { if (!pixmap()) return QImage(); return pixmap()->toImage(); } void QRImageWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && pixmap()) { event->accept(); QMimeData *mimeData = new QMimeData; mimeData->setImageData(exportImage()); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->exec(); } else { QLabel::mousePressEvent(event); } } void QRImageWidget::saveImage() { if (!pixmap()) return; QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); if (!fn.isEmpty()) { exportImage().save(fn); } } void QRImageWidget::copyImage() { if (!pixmap()) return; QApplication::clipboard()->setImage(exportImage()); } void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) { if (!pixmap()) return; contextMenu->exec(event->globalPos()); } ReceiveRequestDialog::ReceiveRequestDialog(const Config *config, QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), model(0), config(config) { ui->setupUi(this); #ifndef USE_QRCODE ui->btnSaveAs->setVisible(false); ui->lblQRCode->setVisible(false); #endif connect(ui->btnSaveAs, SIGNAL(clicked()), ui->lblQRCode, SLOT(saveImage())); } ReceiveRequestDialog::~ReceiveRequestDialog() { delete ui; } void ReceiveRequestDialog::setModel(OptionsModel *_model) { this->model = _model; if (_model) connect(_model, SIGNAL(displayUnitChanged(int)), this, SLOT(update())); // update the display unit if necessary update(); } void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info) { this->info = _info; // Display addresses with currently configured encoding. this->info.address = GUIUtil::convertToConfiguredAddressFormat(*config, this->info.address); update(); } void ReceiveRequestDialog::update() { if (!model) return; QString target = info.label; if (target.isEmpty()) target = info.address; setWindowTitle(tr("Request payment to %1").arg(target)); QString uri = GUIUtil::formatBitcoinURI(*config, info); ui->btnSaveAs->setEnabled(false); QString html; html += ""; html += "" + tr("Payment information") + "
"; html += "" + tr("URI") + ": "; html += "" + GUIUtil::HtmlEscape(uri) + "
"; html += "" + tr("Address") + ": " + GUIUtil::HtmlEscape(info.address) + "
"; if (info.amount != Amount::zero()) html += "" + tr("Amount") + ": " + BitcoinUnits::formatHtmlWithUnit( model->getDisplayUnit(), info.amount) + "
"; if (!info.label.isEmpty()) html += "" + tr("Label") + ": " + GUIUtil::HtmlEscape(info.label) + "
"; if (!info.message.isEmpty()) html += "" + tr("Message") + ": " + GUIUtil::HtmlEscape(info.message) + "
"; ui->outUri->setText(html); #ifdef USE_QRCODE int fontSize = config->UseCashAddrEncoding() ? 10 : 12; ui->lblQRCode->setText(""); if (!uri.isEmpty()) { // limit URI length if (uri.length() > MAX_URI_LENGTH) { ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce " "the text for label / message.")); } else { QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (!code) { ui->lblQRCode->setText(tr("Error encoding URI into QR Code.")); return; } QImage qrImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); qrImage.fill(0xffffff); uint8_t *p = code->data; for (int y = 0; y < code->width; y++) { for (int x = 0; x < code->width; x++) { qrImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); p++; } } QRcode_free(code); QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE + 20, QImage::Format_RGB32); qrAddrImage.fill(0xffffff); QPainter painter(&qrAddrImage); painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); QFont font = GUIUtil::fixedPitchFont(); font.setPixelSize(fontSize); painter.setFont(font); QRect paddedRect = qrAddrImage.rect(); paddedRect.setHeight(QR_IMAGE_SIZE + 12); painter.drawText(paddedRect, Qt::AlignBottom | Qt::AlignCenter, info.address); painter.end(); ui->lblQRCode->setPixmap(QPixmap::fromImage(qrAddrImage)); ui->btnSaveAs->setEnabled(true); } } #endif } void ReceiveRequestDialog::on_btnCopyURI_clicked() { GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(*config, info)); } void ReceiveRequestDialog::on_btnCopyAddress_clicked() { GUIUtil::setClipboard(info.address); } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 37c363e55..4b90a39a5 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1,1283 +1,1279 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "rpcconsole.h" #include "ui_debugwindow.h" #include "bantablemodel.h" #include "clientmodel.h" #include "guiutil.h" #include "platformstyle.h" #include "chainparams.h" #include "config.h" #include "netbase.h" #include "rpc/client.h" #include "rpc/server.h" #include "util.h" #include #ifdef ENABLE_WALLET #include #endif #include #include #include #include #include #include #include #include #include #include -#if QT_VERSION < 0x050000 -#include -#endif - // TODO: add a scrollback limit, as there is currently none // TODO: make it possible to filter out categories (esp debug messages when // implemented) // TODO: receive errors and debug messages through ClientModel const int CONSOLE_HISTORY = 50; const int INITIAL_TRAFFIC_GRAPH_MINS = 30; const QSize FONT_RANGE(4, 40); const char fontSizeSettingsKey[] = "consoleFontSize"; const struct { const char *url; const char *source; } ICON_MAPPING[] = {{"cmd-request", ":/icons/tx_input"}, {"cmd-reply", ":/icons/tx_output"}, {"cmd-error", ":/icons/tx_output"}, {"misc", ":/icons/tx_inout"}, {nullptr, nullptr}}; namespace { // don't add private key handling cmd's to the history const QStringList historyFilter = QStringList() << "importprivkey" << "importmulti" << "signmessagewithprivkey" << "signrawtransaction" << "walletpassphrase" << "walletpassphrasechange" << "encryptwallet"; } // namespace /* Object for executing console RPC commands in a separate thread. */ class RPCExecutor : public QObject { Q_OBJECT public Q_SLOTS: void request(const QString &command); Q_SIGNALS: void reply(int category, const QString &command); }; /** Class for handling RPC timers * (used for e.g. re-locking the wallet after a timeout) */ class QtRPCTimerBase : public QObject, public RPCTimerBase { Q_OBJECT public: QtRPCTimerBase(std::function &_func, int64_t millis) : func(_func) { timer.setSingleShot(true); connect(&timer, SIGNAL(timeout()), this, SLOT(timeout())); timer.start(millis); } ~QtRPCTimerBase() {} private Q_SLOTS: void timeout() { func(); } private: QTimer timer; std::function func; }; class QtRPCTimerInterface : public RPCTimerInterface { public: ~QtRPCTimerInterface() {} const char *Name() override { return "Qt"; } RPCTimerBase *NewTimer(std::function &func, int64_t millis) override { return new QtRPCTimerBase(func, millis); } }; #include "rpcconsole.moc" /** * Split shell command line into a list of arguments and optionally execute the * command(s). * Aims to emulate \c bash and friends. * * - Command nesting is possible with parenthesis; for example: * validateaddress(getnewaddress()) * - Arguments are delimited with whitespace or comma * - Extra whitespace at the beginning and end and between arguments will be * ignored * - Text can be "double" or 'single' quoted * - The backslash \c \ is used as escape character * - Outside quotes, any character can be escaped * - Within double quotes, only escape \c " and backslashes before a \c " or * another backslash * - Within single quotes, no escaping is possible and no special * interpretation takes place * * @param[out] result stringified Result from the executed command(chain) * @param[in] strCommand Command line to split * @param[in] fExecute set true if you want the command to be executed * @param[out] pstrFilteredOut Command line, filtered to remove any sensitive * data */ bool RPCConsole::RPCParseCommandLine(std::string &strResult, const std::string &strCommand, const bool fExecute, std::string *const pstrFilteredOut) { std::vector> stack; stack.push_back(std::vector()); enum CmdParseState { STATE_EATING_SPACES, STATE_EATING_SPACES_IN_ARG, STATE_EATING_SPACES_IN_BRACKETS, STATE_ARGUMENT, STATE_SINGLEQUOTED, STATE_DOUBLEQUOTED, STATE_ESCAPE_OUTER, STATE_ESCAPE_DOUBLEQUOTED, STATE_COMMAND_EXECUTED, STATE_COMMAND_EXECUTED_INNER } state = STATE_EATING_SPACES; std::string curarg; UniValue lastResult; unsigned nDepthInsideSensitive = 0; size_t filter_begin_pos = 0, chpos; std::vector> filter_ranges; auto add_to_current_stack = [&](const std::string &strArg) { if (stack.back().empty() && (!nDepthInsideSensitive) && historyFilter.contains(QString::fromStdString(strArg), Qt::CaseInsensitive)) { nDepthInsideSensitive = 1; filter_begin_pos = chpos; } // Make sure stack is not empty before adding something if (stack.empty()) { stack.push_back(std::vector()); } stack.back().push_back(strArg); }; auto close_out_params = [&]() { if (nDepthInsideSensitive) { if (!--nDepthInsideSensitive) { assert(filter_begin_pos); filter_ranges.push_back( std::make_pair(filter_begin_pos, chpos)); filter_begin_pos = 0; } } stack.pop_back(); }; std::string strCommandTerminated = strCommand; if (strCommandTerminated.back() != '\n') strCommandTerminated += "\n"; for (chpos = 0; chpos < strCommandTerminated.size(); ++chpos) { char ch = strCommandTerminated[chpos]; switch (state) { case STATE_COMMAND_EXECUTED_INNER: case STATE_COMMAND_EXECUTED: { bool breakParsing = true; switch (ch) { case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break; default: if (state == STATE_COMMAND_EXECUTED_INNER) { if (ch != ']') { // append char to the current argument (which is // also used for the query command) curarg += ch; break; } if (curarg.size() && fExecute) { // if we have a value query, query arrays with // index and objects with a string key UniValue subelement; if (lastResult.isArray()) { for (char argch : curarg) if (!std::isdigit(argch)) throw std::runtime_error( "Invalid result query"); subelement = lastResult[atoi(curarg.c_str())]; } else if (lastResult.isObject()) subelement = find_value(lastResult, curarg); else { // no array or object: abort throw std::runtime_error( "Invalid result query"); } lastResult = subelement; } state = STATE_COMMAND_EXECUTED; break; } // don't break parsing when the char is required for the // next argument breakParsing = false; // pop the stack and return the result to the current // command arguments close_out_params(); // don't stringify the json in case of a string to avoid // doublequotes if (lastResult.isStr()) curarg = lastResult.get_str(); else curarg = lastResult.write(2); // if we have a non empty result, use it as stack // argument otherwise as general result if (curarg.size()) { if (stack.size()) add_to_current_stack(curarg); else strResult = curarg; } curarg.clear(); // assume eating space state state = STATE_EATING_SPACES; } if (breakParsing) { break; } } // FALLTHROUGH case STATE_ARGUMENT: // In or after argument case STATE_EATING_SPACES_IN_ARG: case STATE_EATING_SPACES_IN_BRACKETS: case STATE_EATING_SPACES: // Handle runs of whitespace switch (ch) { case '"': state = STATE_DOUBLEQUOTED; break; case '\'': state = STATE_SINGLEQUOTED; break; case '\\': state = STATE_ESCAPE_OUTER; break; case '(': case ')': case '\n': if (state == STATE_EATING_SPACES_IN_ARG) throw std::runtime_error("Invalid Syntax"); if (state == STATE_ARGUMENT) { if (ch == '(' && stack.size() && stack.back().size() > 0) { if (nDepthInsideSensitive) { ++nDepthInsideSensitive; } stack.push_back(std::vector()); } // don't allow commands after executed commands on // baselevel if (!stack.size()) throw std::runtime_error("Invalid Syntax"); add_to_current_stack(curarg); curarg.clear(); state = STATE_EATING_SPACES_IN_BRACKETS; } if ((ch == ')' || ch == '\n') && stack.size() > 0) { if (fExecute) { // Convert argument list to JSON objects in // method-dependent way, and pass it along with // the method name to the dispatcher. JSONRPCRequest req; req.params = RPCConvertValues( stack.back()[0], std::vector( stack.back().begin() + 1, stack.back().end())); req.strMethod = stack.back()[0]; GlobalConfig config; lastResult = tableRPC.execute(config, req); } state = STATE_COMMAND_EXECUTED; curarg.clear(); } break; case ' ': case ',': case '\t': if (state == STATE_EATING_SPACES_IN_ARG && curarg.empty() && ch == ',') throw std::runtime_error("Invalid Syntax"); else if (state == STATE_ARGUMENT) // Space ends argument { add_to_current_stack(curarg); curarg.clear(); } if ((state == STATE_EATING_SPACES_IN_BRACKETS || state == STATE_ARGUMENT) && ch == ',') { state = STATE_EATING_SPACES_IN_ARG; break; } state = STATE_EATING_SPACES; break; default: curarg += ch; state = STATE_ARGUMENT; } break; case STATE_SINGLEQUOTED: // Single-quoted string switch (ch) { case '\'': state = STATE_ARGUMENT; break; default: curarg += ch; } break; case STATE_DOUBLEQUOTED: // Double-quoted string switch (ch) { case '"': state = STATE_ARGUMENT; break; case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break; default: curarg += ch; } break; case STATE_ESCAPE_OUTER: // '\' outside quotes curarg += ch; state = STATE_ARGUMENT; break; case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text if (ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and // '\' itself curarg += ch; state = STATE_DOUBLEQUOTED; break; } } if (pstrFilteredOut) { if (STATE_COMMAND_EXECUTED == state) { assert(!stack.empty()); close_out_params(); } *pstrFilteredOut = strCommand; for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) { pstrFilteredOut->replace(i->first, i->second - i->first, "(…)"); } } switch (state) // final state { case STATE_COMMAND_EXECUTED: if (lastResult.isStr()) strResult = lastResult.get_str(); else strResult = lastResult.write(2); // FALLTHROUGH case STATE_ARGUMENT: case STATE_EATING_SPACES: return true; default: // ERROR to end in one of the other states return false; } } void RPCExecutor::request(const QString &command) { try { std::string result; std::string executableCommand = command.toStdString() + "\n"; if (!RPCConsole::RPCExecuteCommandLine(result, executableCommand)) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); return; } Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } catch (UniValue &objError) { try // Nice formatting for standard-format error { int code = find_value(objError, "code").get_int(); std::string message = find_value(objError, "message").get_str(); Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); } catch (const std::runtime_error &) { // raised when converting to invalid type, i.e. missing code or // message. Show raw JSON object. Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write())); } } catch (const std::exception &e) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); } } RPCConsole::RPCConsole(const PlatformStyle *_platformStyle, QWidget *parent) : QWidget(parent), ui(new Ui::RPCConsole), clientModel(0), historyPtr(0), platformStyle(_platformStyle), peersTableContextMenu(0), banTableContextMenu(0), consoleFontSize(0) { ui->setupUi(this); GUIUtil::restoreWindowGeometry("nRPCConsoleWindow", this->size(), this); ui->openDebugLogfileButton->setToolTip( ui->openDebugLogfileButton->toolTip().arg(tr(PACKAGE_NAME))); if (platformStyle->getImagesOnButtons()) { ui->openDebugLogfileButton->setIcon( platformStyle->SingleColorIcon(":/icons/export")); } ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); ui->fontBiggerButton->setIcon( platformStyle->SingleColorIcon(":/icons/fontbigger")); ui->fontSmallerButton->setIcon( platformStyle->SingleColorIcon(":/icons/fontsmaller")); // Install event filter for up and down arrow ui->lineEdit->installEventFilter(this); ui->messagesWidget->installEventFilter(this); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(ui->fontBiggerButton, SIGNAL(clicked()), this, SLOT(fontBigger())); connect(ui->fontSmallerButton, SIGNAL(clicked()), this, SLOT(fontSmaller())); connect(ui->btnClearTrafficGraph, SIGNAL(clicked()), ui->trafficGraph, SLOT(clear())); // set library version labels #ifdef ENABLE_WALLET ui->berkeleyDBVersion->setText(DbEnv::version(0, 0, 0)); #else ui->label_berkeleyDBVersion->hide(); ui->berkeleyDBVersion->hide(); #endif // Register RPC timer interface rpcTimerInterface = new QtRPCTimerInterface(); // avoid accidentally overwriting an existing, non QTThread // based timer interface RPCSetTimerInterfaceIfUnset(rpcTimerInterface); setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); ui->detailWidget->hide(); ui->peerHeading->setText(tr("Select a peer to view detailed information.")); QSettings settings; consoleFontSize = settings.value(fontSizeSettingsKey, QFontInfo(QFont()).pointSize()) .toInt(); clear(); } RPCConsole::~RPCConsole() { GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this); RPCUnsetTimerInterface(rpcTimerInterface); delete rpcTimerInterface; delete ui; } bool RPCConsole::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) // Special key handling { QKeyEvent *keyevt = static_cast(event); int key = keyevt->key(); Qt::KeyboardModifiers mod = keyevt->modifiers(); switch (key) { case Qt::Key_Up: if (obj == ui->lineEdit) { browseHistory(-1); return true; } break; case Qt::Key_Down: if (obj == ui->lineEdit) { browseHistory(1); return true; } break; case Qt::Key_PageUp: /* pass paging keys to messages widget */ case Qt::Key_PageDown: if (obj == ui->lineEdit) { QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt)); return true; } break; case Qt::Key_Return: case Qt::Key_Enter: // forward these events to lineEdit if (obj == autoCompleter->popup()) { QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); return true; } break; default: // Typing in messages widget brings focus to line edit, and // redirects key there. Exclude most combinations and keys that // emit no text, except paste shortcuts. if (obj == ui->messagesWidget && ((!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) || ((mod & Qt::ControlModifier) && key == Qt::Key_V) || ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert))) { ui->lineEdit->setFocus(); QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); return true; } } } return QWidget::eventFilter(obj, event); } void RPCConsole::setClientModel(ClientModel *model) { clientModel = model; ui->trafficGraph->setClientModel(model); if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) { // Keep up to date with client setNumConnections(model->getNumConnections()); connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); setNumBlocks(model->getNumBlocks(), model->getLastBlockDate(), model->getVerificationProgress(nullptr), false); connect(model, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this, SLOT(setNumBlocks(int, QDateTime, double, bool))); updateNetworkState(); connect(model, SIGNAL(networkActiveChanged(bool)), this, SLOT(setNetworkActive(bool))); updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent()); connect(model, SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(updateTrafficStats(quint64, quint64))); connect(model, SIGNAL(mempoolSizeChanged(long, size_t)), this, SLOT(setMempoolSize(long, size_t))); // set up peer table ui->peerWidget->setModel(model->getPeerTableModel()); ui->peerWidget->verticalHeader()->hide(); ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH); ui->peerWidget->horizontalHeader()->setStretchLastSection(true); // create peer table context menu actions QAction *disconnectAction = new QAction(tr("&Disconnect"), this); QAction *banAction1h = new QAction(tr("Ban for") + " " + tr("1 &hour"), this); QAction *banAction24h = new QAction(tr("Ban for") + " " + tr("1 &day"), this); QAction *banAction7d = new QAction(tr("Ban for") + " " + tr("1 &week"), this); QAction *banAction365d = new QAction(tr("Ban for") + " " + tr("1 &year"), this); // create peer table context menu peersTableContextMenu = new QMenu(this); peersTableContextMenu->addAction(disconnectAction); peersTableContextMenu->addAction(banAction1h); peersTableContextMenu->addAction(banAction24h); peersTableContextMenu->addAction(banAction7d); peersTableContextMenu->addAction(banAction365d); // Add a signal mapping to allow dynamic context menu arguments. We need // to use int (instead of int64_t), because signal mapper only supports // int or objects, which is okay because max bantime (1 year) is < // int_max. QSignalMapper *signalMapper = new QSignalMapper(this); signalMapper->setMapping(banAction1h, 60 * 60); signalMapper->setMapping(banAction24h, 60 * 60 * 24); signalMapper->setMapping(banAction7d, 60 * 60 * 24 * 7); signalMapper->setMapping(banAction365d, 60 * 60 * 24 * 365); connect(banAction1h, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(banAction24h, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(banAction7d, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(banAction365d, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(banSelectedNode(int))); // peer table context menu signals connect(ui->peerWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showPeersTableContextMenu(const QPoint &))); connect(disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectSelectedNode())); // peer table signal handling - update peer details when selecting new // node connect(ui->peerWidget->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(peerSelected(const QItemSelection &, const QItemSelection &))); // peer table signal handling - update peer details when new nodes are // added to the model connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this, SLOT(peerLayoutChanged())); // peer table signal handling - cache selected node ids connect(model->getPeerTableModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(peerLayoutAboutToChange())); // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); ui->banlistWidget->verticalHeader()->hide(); ui->banlistWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection); ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH); ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH); ui->banlistWidget->horizontalHeader()->setStretchLastSection(true); // create ban table context menu action QAction *unbanAction = new QAction(tr("&Unban"), this); // create ban table context menu banTableContextMenu = new QMenu(this); banTableContextMenu->addAction(unbanAction); // ban table context menu signals connect(ui->banlistWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showBanTableContextMenu(const QPoint &))); connect(unbanAction, SIGNAL(triggered()), this, SLOT(unbanSelectedNode())); // ban table signal handling - clear peer details when clicking a peer // in the ban table connect(ui->banlistWidget, SIGNAL(clicked(const QModelIndex &)), this, SLOT(clearSelectedNode())); // ban table signal handling - ensure ban table is shown or hidden (if // empty) connect(model->getBanTableModel(), SIGNAL(layoutChanged()), this, SLOT(showOrHideBanTableIfRequired())); showOrHideBanTableIfRequired(); // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); ui->clientUserAgent->setText(model->formatSubVersion()); ui->dataDir->setText(model->dataDir()); ui->startupTime->setText(model->formatClientStartupTime()); ui->networkName->setText( QString::fromStdString(Params().NetworkIDString())); // Setup autocomplete and attach it QStringList wordList; std::vector commandList = tableRPC.listCommands(); for (size_t i = 0; i < commandList.size(); ++i) { wordList << commandList[i].c_str(); } autoCompleter = new QCompleter(wordList, this); ui->lineEdit->setCompleter(autoCompleter); autoCompleter->popup()->installEventFilter(this); // Start thread to execute RPC commands. startExecutor(); } if (!model) { // Client model is being set to 0, this means shutdown() is about to be // called. Make sure we clean up the executor thread Q_EMIT stopExecutor(); thread.wait(); } } static QString categoryClass(int category) { switch (category) { case RPCConsole::CMD_REQUEST: return "cmd-request"; break; case RPCConsole::CMD_REPLY: return "cmd-reply"; break; case RPCConsole::CMD_ERROR: return "cmd-error"; break; default: return "misc"; } } void RPCConsole::fontBigger() { setFontSize(consoleFontSize + 1); } void RPCConsole::fontSmaller() { setFontSize(consoleFontSize - 1); } void RPCConsole::setFontSize(int newSize) { QSettings settings; // don't allow a insane font size if (newSize < FONT_RANGE.width() || newSize > FONT_RANGE.height()) return; // temp. store the console content QString str = ui->messagesWidget->toHtml(); // replace font tags size in current content str.replace(QString("font-size:%1pt").arg(consoleFontSize), QString("font-size:%1pt").arg(newSize)); // store the new font size consoleFontSize = newSize; settings.setValue(fontSizeSettingsKey, consoleFontSize); // clear console (reset icon sizes, default stylesheet) and re-add the // content float oldPosFactor = 1.0 / ui->messagesWidget->verticalScrollBar()->maximum() * ui->messagesWidget->verticalScrollBar()->value(); clear(false); ui->messagesWidget->setHtml(str); ui->messagesWidget->verticalScrollBar()->setValue( oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum()); } void RPCConsole::clear(bool clearHistory) { ui->messagesWidget->clear(); if (clearHistory) { history.clear(); historyPtr = 0; } ui->lineEdit->clear(); ui->lineEdit->setFocus(); // Add smoothly scaled icon images. // (when using width/height on an img, Qt uses nearest instead of linear // interpolation) for (int i = 0; ICON_MAPPING[i].url; ++i) { ui->messagesWidget->document()->addResource( QTextDocument::ImageResource, QUrl(ICON_MAPPING[i].url), platformStyle->SingleColorImage(ICON_MAPPING[i].source) .scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } // Set default style sheet QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont()); ui->messagesWidget->document()->setDefaultStyleSheet( QString("table { }" "td.time { color: #808080; font-size: %2; padding-top: 3px; } " "td.message { font-family: %1; font-size: %2; " "white-space:pre-wrap; } " "td.cmd-request { color: #006060; } " "td.cmd-error { color: red; } " ".secwarning { color: red; }" "b { color: #006060; } ") .arg(fixedFontInfo.family(), QString("%1pt").arg(consoleFontSize))); message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "
" + tr("Use up and down arrows to navigate history, and " "Ctrl-L to clear screen.") + "
" + tr("Type help for an overview of available commands.")) + "
" + tr("WARNING: Scammers have been active, telling users to type " "commands here, stealing their wallet contents. Do not use " "this console without fully understanding the ramification " "of a command.") + "", true); } void RPCConsole::keyPressEvent(QKeyEvent *event) { if (windowType() != Qt::Widget && event->key() == Qt::Key_Escape) { close(); } } void RPCConsole::message(int category, const QString &message, bool html) { QTime time = QTime::currentTime(); QString timeString = time.toString(); QString out; out += ""; out += ""; out += "
" + timeString + ""; if (html) out += message; else out += GUIUtil::HtmlEscape(message, false); out += "
"; ui->messagesWidget->append(out); } void RPCConsole::updateNetworkState() { QString connections = QString::number(clientModel->getNumConnections()) + " ("; connections += tr("In:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / "; connections += tr("Out:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")"; if (!clientModel->getNetworkActive()) { connections += " (" + tr("Network activity disabled") + ")"; } ui->numberOfConnections->setText(connections); } void RPCConsole::setNumConnections(int count) { if (!clientModel) return; updateNetworkState(); } void RPCConsole::setNetworkActive(bool networkActive) { updateNetworkState(); } void RPCConsole::setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool headers) { if (!headers) { ui->numberOfBlocks->setText(QString::number(count)); ui->lastBlockTime->setText(blockDate.toString()); } } void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage) { ui->mempoolNumberTxs->setText(QString::number(numberOfTxs)); if (dynUsage < 1000000) ui->mempoolSize->setText(QString::number(dynUsage / 1000.0, 'f', 2) + " KB"); else ui->mempoolSize->setText(QString::number(dynUsage / 1000000.0, 'f', 2) + " MB"); } void RPCConsole::on_lineEdit_returnPressed() { QString cmd = ui->lineEdit->text(); if (!cmd.isEmpty()) { std::string strFilteredCmd; try { std::string dummy; if (!RPCParseCommandLine(dummy, cmd.toStdString(), false, &strFilteredCmd)) { // Failed to parse command, so we cannot even filter it for the // history throw std::runtime_error("Invalid command line"); } } catch (const std::exception &e) { QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what())); return; } ui->lineEdit->clear(); cmdBeforeBrowsing = QString(); message(CMD_REQUEST, cmd); Q_EMIT cmdRequest(cmd); cmd = QString::fromStdString(strFilteredCmd); // Remove command, if already in history history.removeOne(cmd); // Append command to history history.append(cmd); // Enforce maximum history size while (history.size() > CONSOLE_HISTORY) history.removeFirst(); // Set pointer to end of history historyPtr = history.size(); // Scroll console view to end scrollToEnd(); } } void RPCConsole::browseHistory(int offset) { // store current text when start browsing through the history if (historyPtr == history.size()) { cmdBeforeBrowsing = ui->lineEdit->text(); } historyPtr += offset; if (historyPtr < 0) historyPtr = 0; if (historyPtr > history.size()) historyPtr = history.size(); QString cmd; if (historyPtr < history.size()) cmd = history.at(historyPtr); else if (!cmdBeforeBrowsing.isNull()) { cmd = cmdBeforeBrowsing; } ui->lineEdit->setText(cmd); } void RPCConsole::startExecutor() { RPCExecutor *executor = new RPCExecutor(); executor->moveToThread(&thread); // Replies from executor object must go to this object connect(executor, SIGNAL(reply(int, QString)), this, SLOT(message(int, QString))); // Requests from this object must go to executor connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString))); // On stopExecutor signal // - quit the Qt event loop in the execution thread connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit())); // - queue executor for deletion (in execution thread) connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()), Qt::DirectConnection); // Default implementation of QThread::run() simply spins up an event loop in // the thread, which is what we want. thread.start(); } void RPCConsole::on_tabWidget_currentChanged(int index) { if (ui->tabWidget->widget(index) == ui->tab_console) ui->lineEdit->setFocus(); else if (ui->tabWidget->widget(index) != ui->tab_peers) clearSelectedNode(); } void RPCConsole::on_openDebugLogfileButton_clicked() { GUIUtil::openDebugLogfile(); } void RPCConsole::scrollToEnd() { QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar(); scrollbar->setValue(scrollbar->maximum()); } void RPCConsole::on_sldGraphRange_valueChanged(int value) { const int multiplier = 5; // each position on the slider represents 5 min int mins = value * multiplier; setTrafficGraphRange(mins); } QString RPCConsole::FormatBytes(quint64 bytes) { if (bytes < 1024) return QString(tr("%1 B")).arg(bytes); if (bytes < 1024 * 1024) return QString(tr("%1 KB")).arg(bytes / 1024); if (bytes < 1024 * 1024 * 1024) return QString(tr("%1 MB")).arg(bytes / 1024 / 1024); return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024); } void RPCConsole::setTrafficGraphRange(int mins) { ui->trafficGraph->setGraphRangeMins(mins); ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60)); } void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) { ui->lblBytesIn->setText(FormatBytes(totalBytesIn)); ui->lblBytesOut->setText(FormatBytes(totalBytesOut)); } void RPCConsole::peerSelected(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected); if (!clientModel || !clientModel->getPeerTableModel() || selected.indexes().isEmpty()) return; const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats( selected.indexes().first().row()); if (stats) updateNodeDetail(stats); } void RPCConsole::peerLayoutAboutToChange() { QModelIndexList selected = ui->peerWidget->selectionModel()->selectedIndexes(); cachedNodeids.clear(); for (int i = 0; i < selected.size(); i++) { const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats( selected.at(i).row()); cachedNodeids.append(stats->nodeStats.nodeid); } } void RPCConsole::peerLayoutChanged() { if (!clientModel || !clientModel->getPeerTableModel()) return; const CNodeCombinedStats *stats = nullptr; bool fUnselect = false; bool fReselect = false; if (cachedNodeids.empty()) // no node selected yet return; // find the currently selected row int selectedRow = -1; QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes(); if (!selectedModelIndex.isEmpty()) { selectedRow = selectedModelIndex.first().row(); } // check if our detail node has a row in the table (it may not necessarily // be at selectedRow since its position can change after a layout change) int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.first()); if (detailNodeRow < 0) { // detail node disappeared from table (node disconnected) fUnselect = true; } else { if (detailNodeRow != selectedRow) { // detail node moved position fUnselect = true; fReselect = true; } // get fresh stats on the detail node. stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); } if (fUnselect && selectedRow >= 0) { clearSelectedNode(); } if (fReselect) { for (int i = 0; i < cachedNodeids.size(); i++) { ui->peerWidget->selectRow( clientModel->getPeerTableModel()->getRowByNodeId( cachedNodeids.at(i))); } } if (stats) updateNodeDetail(stats); } void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) { // update the detail ui with latest node information QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " "); peerAddrDetails += tr("(node id: %1)").arg(QString::number(stats->nodeStats.nodeid)); if (!stats->nodeStats.addrLocal.empty()) peerAddrDetails += "
" + tr("via %1").arg(QString::fromStdString( stats->nodeStats.addrLocal)); ui->peerHeading->setText(peerAddrDetails); ui->peerServices->setText( GUIUtil::formatServicesStr(stats->nodeStats.nServices)); ui->peerLastSend->setText( stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastSend) : tr("never")); ui->peerLastRecv->setText( stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastRecv) : tr("never")); ui->peerBytesSent->setText(FormatBytes(stats->nodeStats.nSendBytes)); ui->peerBytesRecv->setText(FormatBytes(stats->nodeStats.nRecvBytes)); ui->peerConnTime->setText(GUIUtil::formatDurationStr( GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected)); ui->peerPingTime->setText( GUIUtil::formatPingTime(stats->nodeStats.dPingTime)); ui->peerPingWait->setText( GUIUtil::formatPingTime(stats->nodeStats.dPingWait)); ui->peerMinPing->setText( GUIUtil::formatPingTime(stats->nodeStats.dMinPing)); ui->timeoffset->setText( GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); ui->peerVersion->setText( QString("%1").arg(QString::number(stats->nodeStats.nVersion))); ui->peerSubversion->setText( QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound")); ui->peerHeight->setText( QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight))); ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No")); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. if (stats->fNodeStateStatsAvailable) { // Ban score is init to 0 ui->peerBanScore->setText( QString("%1").arg(stats->nodeStateStats.nMisbehavior)); // Sync height is init to -1 if (stats->nodeStateStats.nSyncHeight > -1) ui->peerSyncHeight->setText( QString("%1").arg(stats->nodeStateStats.nSyncHeight)); else ui->peerSyncHeight->setText(tr("Unknown")); // Common height is init to -1 if (stats->nodeStateStats.nCommonHeight > -1) ui->peerCommonHeight->setText( QString("%1").arg(stats->nodeStateStats.nCommonHeight)); else ui->peerCommonHeight->setText(tr("Unknown")); } ui->detailWidget->show(); } void RPCConsole::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); } void RPCConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); if (!clientModel || !clientModel->getPeerTableModel()) return; // start PeerTableModel auto refresh clientModel->getPeerTableModel()->startAutoRefresh(); } void RPCConsole::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); if (!clientModel || !clientModel->getPeerTableModel()) return; // stop PeerTableModel auto refresh clientModel->getPeerTableModel()->stopAutoRefresh(); } void RPCConsole::showPeersTableContextMenu(const QPoint &point) { QModelIndex index = ui->peerWidget->indexAt(point); if (index.isValid()) peersTableContextMenu->exec(QCursor::pos()); } void RPCConsole::showBanTableContextMenu(const QPoint &point) { QModelIndex index = ui->banlistWidget->indexAt(point); if (index.isValid()) banTableContextMenu->exec(QCursor::pos()); } void RPCConsole::disconnectSelectedNode() { if (!g_connman) return; // Get selected peer addresses QList nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId); for (int i = 0; i < nodes.count(); i++) { // Get currently selected peer address NodeId id = nodes.at(i).data().toLongLong(); // Find the node, disconnect it and clear the selected node if (g_connman->DisconnectNode(id)) clearSelectedNode(); } } void RPCConsole::banSelectedNode(int bantime) { if (!clientModel || !g_connman) return; // Get selected peer addresses QList nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId); for (int i = 0; i < nodes.count(); i++) { // Get currently selected peer address NodeId id = nodes.at(i).data().toLongLong(); // Get currently selected peer address int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id); if (detailNodeRow < 0) return; // Find possible nodes, ban it and clear the selected node const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); if (stats) { g_connman->Ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime); } } clearSelectedNode(); clientModel->getBanTableModel()->refresh(); } void RPCConsole::unbanSelectedNode() { if (!clientModel) return; // Get selected ban addresses QList nodes = GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address); for (int i = 0; i < nodes.count(); i++) { // Get currently selected ban address QString strNode = nodes.at(i).data().toString(); CSubNet possibleSubnet; LookupSubNet(strNode.toStdString().c_str(), possibleSubnet); if (possibleSubnet.IsValid() && g_connman) { g_connman->Unban(possibleSubnet); clientModel->getBanTableModel()->refresh(); } } } void RPCConsole::clearSelectedNode() { ui->peerWidget->selectionModel()->clearSelection(); cachedNodeids.clear(); ui->detailWidget->hide(); ui->peerHeading->setText(tr("Select a peer to view detailed information.")); } void RPCConsole::showOrHideBanTableIfRequired() { if (!clientModel) return; bool visible = clientModel->getBanTableModel()->shouldShow(); ui->banlistWidget->setVisible(visible); ui->banHeading->setVisible(visible); } void RPCConsole::setTabFocus(enum TabTypes tabType) { ui->tabWidget->setCurrentIndex(tabType); } diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index b67f691ec..d61ff5482 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -1,280 +1,278 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sendcoinsentry.h" #include "ui_sendcoinsentry.h" #include "addressbookpage.h" #include "addresstablemodel.h" #include "config.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "walletmodel.h" #include #include SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *parent) : QStackedWidget(parent), ui(new Ui::SendCoinsEntry), model(0), platformStyle(_platformStyle) { ui->setupUi(this); ui->addressBookButton->setIcon( platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton->setIcon( platformStyle->SingleColorIcon(":/icons/editpaste")); ui->deleteButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); ui->deleteButton_is->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->deleteButton_s->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->messageTextLabel->setToolTip( tr("A message that was attached to the %1 URI which will be" " stored with the transaction for your reference. Note: " "This message will not be sent over the Bitcoin network.") .arg(GUIUtil::bitcoinURIScheme(GetConfig()))); setCurrentWidget(ui->SendCoins); if (platformStyle->getUseExtraSpacing()) ui->payToLayout->setSpacing(4); -#if QT_VERSION >= 0x040700 ui->addAsLabel->setPlaceholderText( tr("Enter a label for this address to add it to your address book")); -#endif // normal bitcoin address field GUIUtil::setupAddressWidget(ui->payTo, this); // just a label for displaying bitcoin address(es) ui->payTo_is->setFont(GUIUtil::fixedPitchFont()); // Connect signals connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged())); connect(ui->checkboxSubtractFeeFromAmount, SIGNAL(toggled(bool)), this, SIGNAL(subtractFeeFromAmountChanged())); connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->useAvailableBalanceButton, SIGNAL(clicked()), this, SLOT(useAvailableBalanceClicked())); } SendCoinsEntry::~SendCoinsEntry() { delete ui; } void SendCoinsEntry::on_pasteButton_clicked() { // Paste text from clipboard into recipient field ui->payTo->setText(QApplication::clipboard()->text()); } void SendCoinsEntry::on_addressBookButton_clicked() { if (!model) return; AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::SendingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) { ui->payTo->setText(dlg.getReturnValue()); ui->payAmount->setFocus(); } } void SendCoinsEntry::on_payTo_textChanged(const QString &address) { updateLabel(address); } void SendCoinsEntry::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel()) connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); clear(); } void SendCoinsEntry::clear() { // clear UI elements for normal payment ui->payTo->clear(); ui->addAsLabel->clear(); ui->payAmount->clear(); ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked); ui->messageTextLabel->clear(); ui->messageTextLabel->hide(); ui->messageLabel->hide(); // clear UI elements for unauthenticated payment request ui->payTo_is->clear(); ui->memoTextLabel_is->clear(); ui->payAmount_is->clear(); // clear UI elements for authenticated payment request ui->payTo_s->clear(); ui->memoTextLabel_s->clear(); ui->payAmount_s->clear(); // update the display unit, to not use the default ("BCH") updateDisplayUnit(); } void SendCoinsEntry::checkSubtractFeeFromAmount() { ui->checkboxSubtractFeeFromAmount->setChecked(true); } void SendCoinsEntry::deleteClicked() { Q_EMIT removeEntry(this); } void SendCoinsEntry::useAvailableBalanceClicked() { Q_EMIT useAvailableBalance(this); } bool SendCoinsEntry::validate() { if (!model) { return false; } // Check input validity bool retval = true; // Skip checks for payment request if (recipient.paymentRequest.IsInitialized()) { return retval; } if (!model->validateAddress(ui->payTo->text())) { ui->payTo->setValid(false); retval = false; } if (!ui->payAmount->validate()) { retval = false; } // Sending a zero amount is invalid if (ui->payAmount->value(0) <= Amount::zero()) { ui->payAmount->setValid(false); retval = false; } // Reject dust outputs: if (retval && GUIUtil::isDust(ui->payTo->text(), ui->payAmount->value(), model->getChainParams())) { ui->payAmount->setValid(false); retval = false; } return retval; } SendCoinsRecipient SendCoinsEntry::getValue() { // Payment request if (recipient.paymentRequest.IsInitialized()) return recipient; // Normal payment recipient.address = ui->payTo->text(); recipient.label = ui->addAsLabel->text(); recipient.amount = ui->payAmount->value(); recipient.message = ui->messageTextLabel->text(); recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked); return recipient; } QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) { QWidget::setTabOrder(prev, ui->payTo); QWidget::setTabOrder(ui->payTo, ui->addAsLabel); QWidget *w = ui->payAmount->setupTabChain(ui->addAsLabel); QWidget::setTabOrder(w, ui->checkboxSubtractFeeFromAmount); QWidget::setTabOrder(ui->checkboxSubtractFeeFromAmount, ui->addressBookButton); QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton); QWidget::setTabOrder(ui->pasteButton, ui->deleteButton); return ui->deleteButton; } void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { recipient = value; if (recipient.paymentRequest.IsInitialized()) // payment request { if (recipient.authenticatedMerchant.isEmpty()) // unauthenticated { ui->payTo_is->setText(recipient.address); ui->memoTextLabel_is->setText(recipient.message); ui->payAmount_is->setValue(recipient.amount); ui->payAmount_is->setReadOnly(true); setCurrentWidget(ui->SendCoins_UnauthenticatedPaymentRequest); } else // authenticated { ui->payTo_s->setText(recipient.authenticatedMerchant); ui->memoTextLabel_s->setText(recipient.message); ui->payAmount_s->setValue(recipient.amount); ui->payAmount_s->setReadOnly(true); setCurrentWidget(ui->SendCoins_AuthenticatedPaymentRequest); } } else // normal payment { // message ui->messageTextLabel->setText(recipient.message); ui->messageTextLabel->setVisible(!recipient.message.isEmpty()); ui->messageLabel->setVisible(!recipient.message.isEmpty()); ui->addAsLabel->clear(); ui->payTo->setText( recipient.address); // this may set a label from addressbook if (!recipient.label.isEmpty()) // if a label had been set from the // addressbook, don't overwrite with an // empty label ui->addAsLabel->setText(recipient.label); ui->payAmount->setValue(recipient.amount); } } void SendCoinsEntry::setAddress(const QString &address) { ui->payTo->setText(address); ui->payAmount->setFocus(); } void SendCoinsEntry::setAmount(const Amount amount) { ui->payAmount->setValue(amount); } bool SendCoinsEntry::isClear() { return ui->payTo->text().isEmpty() && ui->payTo_is->text().isEmpty() && ui->payTo_s->text().isEmpty(); } void SendCoinsEntry::setFocus() { ui->payTo->setFocus(); } void SendCoinsEntry::updateDisplayUnit() { if (model && model->getOptionsModel()) { // Update payAmount with the current unit ui->payAmount->setDisplayUnit( model->getOptionsModel()->getDisplayUnit()); ui->payAmount_is->setDisplayUnit( model->getOptionsModel()->getDisplayUnit()); ui->payAmount_s->setDisplayUnit( model->getOptionsModel()->getDisplayUnit()); } } bool SendCoinsEntry::updateLabel(const QString &address) { if (!model) return false; // Fill in label from address book, if address has an associated label QString associatedLabel = model->getAddressTableModel()->labelForAddress(address); if (!associatedLabel.isEmpty()) { ui->addAsLabel->setText(associatedLabel); return true; } return false; } diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 1c7e5d179..ed27504dd 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -1,280 +1,278 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "signverifymessagedialog.h" #include "ui_signverifymessagedialog.h" #include "addressbookpage.h" #include "guiutil.h" #include "platformstyle.h" #include "walletmodel.h" #include "dstencode.h" #include "init.h" #include "validation.h" // For strMessageMagic #include "wallet/wallet.h" #include #include #include SignVerifyMessageDialog::SignVerifyMessageDialog( const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::SignVerifyMessageDialog), model(0), platformStyle(_platformStyle) { ui->setupUi(this); ui->addressBookButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/editpaste")); ui->copySignatureButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/editcopy")); ui->signMessageButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/edit")); ui->clearButton_SM->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->addressBookButton_VM->setIcon( platformStyle->SingleColorIcon(":/icons/address-book")); ui->verifyMessageButton_VM->setIcon( platformStyle->SingleColorIcon(":/icons/transaction_0")); ui->clearButton_VM->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); -#if QT_VERSION >= 0x040700 ui->signatureOut_SM->setPlaceholderText( tr("Click \"Sign Message\" to generate signature")); -#endif GUIUtil::setupAddressWidget(ui->addressIn_SM, this); GUIUtil::setupAddressWidget(ui->addressIn_VM, this); ui->addressIn_SM->installEventFilter(this); ui->messageIn_SM->installEventFilter(this); ui->signatureOut_SM->installEventFilter(this); ui->addressIn_VM->installEventFilter(this); ui->messageIn_VM->installEventFilter(this); ui->signatureIn_VM->installEventFilter(this); ui->signatureOut_SM->setFont(GUIUtil::fixedPitchFont()); ui->signatureIn_VM->setFont(GUIUtil::fixedPitchFont()); } SignVerifyMessageDialog::~SignVerifyMessageDialog() { delete ui; } void SignVerifyMessageDialog::setModel(WalletModel *_model) { this->model = _model; } void SignVerifyMessageDialog::setAddress_SM(const QString &address) { ui->addressIn_SM->setText(address); ui->messageIn_SM->setFocus(); } void SignVerifyMessageDialog::setAddress_VM(const QString &address) { ui->addressIn_VM->setText(address); ui->messageIn_VM->setFocus(); } void SignVerifyMessageDialog::showTab_SM(bool fShow) { ui->tabWidget->setCurrentIndex(0); if (fShow) this->show(); } void SignVerifyMessageDialog::showTab_VM(bool fShow) { ui->tabWidget->setCurrentIndex(1); if (fShow) this->show(); } void SignVerifyMessageDialog::on_addressBookButton_SM_clicked() { if (model && model->getAddressTableModel()) { AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) { setAddress_SM(dlg.getReturnValue()); } } } void SignVerifyMessageDialog::on_pasteButton_SM_clicked() { setAddress_SM(QApplication::clipboard()->text()); } void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() { if (!model) return; /* Clear old signature to ensure users don't get confused on error with an * old signature displayed */ ui->signatureOut_SM->clear(); CTxDestination destination = DecodeDestination( ui->addressIn_SM->text().toStdString(), model->getChainParams()); if (!IsValidDestination(destination)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText( tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); return; } const CKeyID *keyID = boost::get(&destination); if (!keyID) { ui->addressIn_SM->setValid(false); ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText( tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); return; } WalletModel::UnlockContext ctx(model->requestUnlock()); if (!ctx.isValid()) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(tr("Wallet unlock was cancelled.")); return; } CKey key; if (!model->getPrivKey(*keyID, key)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText( tr("Private key for the entered address is not available.")); return; } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << ui->messageIn_SM->document()->toPlainText().toStdString(); std::vector vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(QString("") + tr("Message signing failed.") + QString("")); return; } ui->statusLabel_SM->setStyleSheet("QLabel { color: green; }"); ui->statusLabel_SM->setText(QString("") + tr("Message signed.") + QString("")); ui->signatureOut_SM->setText( QString::fromStdString(EncodeBase64(&vchSig[0], vchSig.size()))); } void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked() { GUIUtil::setClipboard(ui->signatureOut_SM->text()); } void SignVerifyMessageDialog::on_clearButton_SM_clicked() { ui->addressIn_SM->clear(); ui->messageIn_SM->clear(); ui->signatureOut_SM->clear(); ui->statusLabel_SM->clear(); ui->addressIn_SM->setFocus(); } void SignVerifyMessageDialog::on_addressBookButton_VM_clicked() { if (model && model->getAddressTableModel()) { AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::SendingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) { setAddress_VM(dlg.getReturnValue()); } } } void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() { CTxDestination destination = DecodeDestination( ui->addressIn_VM->text().toStdString(), model->getChainParams()); if (!IsValidDestination(destination)) { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); return; } if (!boost::get(&destination)) { ui->addressIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); return; } bool fInvalid = false; std::vector vchSig = DecodeBase64( ui->signatureIn_VM->text().toStdString().c_str(), &fInvalid); if (fInvalid) { ui->signatureIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again.")); return; } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << ui->messageIn_VM->document()->toPlainText().toStdString(); CPubKey pubkey; if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) { ui->signatureIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText( tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again.")); return; } if (!(CTxDestination(pubkey.GetID()) == destination)) { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText(QString("") + tr("Message verification failed.") + QString("")); return; } ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }"); ui->statusLabel_VM->setText(QString("") + tr("Message verified.") + QString("")); } void SignVerifyMessageDialog::on_clearButton_VM_clicked() { ui->addressIn_VM->clear(); ui->signatureIn_VM->clear(); ui->messageIn_VM->clear(); ui->statusLabel_VM->clear(); ui->addressIn_VM->setFocus(); } bool SignVerifyMessageDialog::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::FocusIn) { if (ui->tabWidget->currentIndex() == 0) { /* Clear status message on focus change */ ui->statusLabel_SM->clear(); /* Select generated signature */ if (object == ui->signatureOut_SM) { ui->signatureOut_SM->selectAll(); return true; } } else if (ui->tabWidget->currentIndex() == 1) { /* Clear status message on focus change */ ui->statusLabel_VM->clear(); } } return QDialog::eventFilter(object, event); } diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 6448b804d..308ef1cb9 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -1,111 +1,104 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "compat/setenv.h" #include "bitcoinaddressvalidatortests.h" #include "chainparams.h" #include "compattests.h" #include "guiutiltests.h" #include "key.h" #include "rpcnestedtests.h" #include "uritests.h" #include "util.h" #ifdef ENABLE_WALLET #include "paymentservertests.h" #include "wallettests.h" #endif #include #include #include #include #if defined(QT_STATICPLUGIN) #include -#if QT_VERSION < 0x050000 -Q_IMPORT_PLUGIN(qcncodecs) -Q_IMPORT_PLUGIN(qjpcodecs) -Q_IMPORT_PLUGIN(qtwcodecs) -Q_IMPORT_PLUGIN(qkrcodecs) -#else #if defined(QT_QPA_PLATFORM_MINIMAL) Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin); #endif #if defined(QT_QPA_PLATFORM_XCB) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_WINDOWS) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif -#endif extern void noui_connect(); // This is all you need to run all the tests int main(int argc, char *argv[]) { SetupEnvironment(); SetupNetworking(); SelectParams(CBaseChainParams::MAIN); noui_connect(); bool fInvalid = false; // Prefer the "minimal" platform for the test instead of the normal default // platform ("xcb", "windows", or "cocoa") so tests can't unintentially // interfere with any background GUIs and don't require extra resources. setenv("QT_QPA_PLATFORM", "minimal", 0); // Don't remove this, it's needed to access // QApplication:: and QCoreApplication:: in the tests QApplication app(argc, argv); app.setApplicationName("BitcoinABC-Qt-test"); // This is necessary to initialize openssl on the test framework // (at least on Darwin). SSL_library_init(); URITests test1; if (QTest::qExec(&test1) != 0) { fInvalid = true; } #ifdef ENABLE_WALLET PaymentServerTests test2; if (QTest::qExec(&test2) != 0) { fInvalid = true; } #endif RPCNestedTests test3; if (QTest::qExec(&test3) != 0) { fInvalid = true; } CompatTests test4; if (QTest::qExec(&test4) != 0) { fInvalid = true; } GUIUtilTests test5; if (QTest::qExec(&test5) != 0) { fInvalid = true; } BitcoinAddressValidatorTests test6; if (QTest::qExec(&test6) != 0) { fInvalid = true; } #ifdef ENABLE_WALLET WalletTests test7; if (QTest::qExec(&test7) != 0) { fInvalid = true; } #endif return fInvalid; } diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index cd4c6728f..338155fb7 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -1,637 +1,633 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactionview.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "csvmodelwriter.h" #include "editaddressdialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "transactiondescdialog.h" #include "transactionfilterproxy.h" #include "transactionrecord.h" #include "transactiontablemodel.h" #include "walletmodel.h" #include "ui_interface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), model(0), transactionProxyModel(0), transactionView(0), abandonAction(0), columnResizingFixer(0) { // Build filter row setContentsMargins(0, 0, 0, 0); QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0, 0, 0, 0); if (platformStyle->getUseExtraSpacing()) { hlayout->setSpacing(5); hlayout->addSpacing(26); } else { hlayout->setSpacing(0); hlayout->addSpacing(23); } watchOnlyWidget = new QComboBox(this); watchOnlyWidget->setFixedWidth(24); watchOnlyWidget->addItem("", TransactionFilterProxy::WatchOnlyFilter_All); watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_plus"), "", TransactionFilterProxy::WatchOnlyFilter_Yes); watchOnlyWidget->addItem( platformStyle->SingleColorIcon(":/icons/eye_minus"), "", TransactionFilterProxy::WatchOnlyFilter_No); hlayout->addWidget(watchOnlyWidget); dateWidget = new QComboBox(this); if (platformStyle->getUseExtraSpacing()) { dateWidget->setFixedWidth(121); } else { dateWidget->setFixedWidth(120); } dateWidget->addItem(tr("All"), All); dateWidget->addItem(tr("Today"), Today); dateWidget->addItem(tr("This week"), ThisWeek); dateWidget->addItem(tr("This month"), ThisMonth); dateWidget->addItem(tr("Last month"), LastMonth); dateWidget->addItem(tr("This year"), ThisYear); dateWidget->addItem(tr("Range..."), Range); hlayout->addWidget(dateWidget); typeWidget = new QComboBox(this); if (platformStyle->getUseExtraSpacing()) { typeWidget->setFixedWidth(121); } else { typeWidget->setFixedWidth(120); } typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES); typeWidget->addItem( tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); typeWidget->addItem( tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE( TransactionRecord::SendToSelf)); typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE( TransactionRecord::Generated)); typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); hlayout->addWidget(typeWidget); addressWidget = new QLineEdit(this); -#if QT_VERSION >= 0x040700 addressWidget->setPlaceholderText(tr("Enter address or label to search")); -#endif hlayout->addWidget(addressWidget); amountWidget = new QLineEdit(this); -#if QT_VERSION >= 0x040700 amountWidget->setPlaceholderText(tr("Min amount")); -#endif if (platformStyle->getUseExtraSpacing()) { amountWidget->setFixedWidth(97); } else { amountWidget->setFixedWidth(100); } amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this)); hlayout->addWidget(amountWidget); QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); QTableView *view = new QTableView(this); vlayout->addLayout(hlayout); vlayout->addWidget(createDateRangeWidget()); vlayout->addWidget(view); vlayout->setSpacing(0); int width = view->verticalScrollBar()->sizeHint().width(); // Cover scroll bar width with spacing if (platformStyle->getUseExtraSpacing()) { hlayout->addSpacing(width + 2); } else { hlayout->addSpacing(width); } // Always show scroll bar view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); view->setTabKeyNavigation(false); view->setContextMenuPolicy(Qt::CustomContextMenu); view->installEventFilter(this); transactionView = view; // Actions abandonAction = new QAction(tr("Abandon transaction"), this); QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this); QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this); QAction *editLabelAction = new QAction(tr("Edit label"), this); QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTxIDAction); contextMenu->addAction(copyTxHexAction); contextMenu->addAction(copyTxPlainText); contextMenu->addAction(showDetailsAction); contextMenu->addSeparator(); contextMenu->addAction(abandonAction); contextMenu->addAction(editLabelAction); mapperThirdPartyTxUrls = new QSignalMapper(this); // Connect actions connect(mapperThirdPartyTxUrls, SIGNAL(mapped(QString)), this, SLOT(openThirdPartyTxUrl(QString))); connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int))); connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int))); connect(watchOnlyWidget, SIGNAL(activated(int)), this, SLOT(chooseWatchonly(int))); connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString))); connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString))); connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex))); connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx())); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID())); connect(copyTxHexAction, SIGNAL(triggered()), this, SLOT(copyTxHex())); connect(copyTxPlainText, SIGNAL(triggered()), this, SLOT(copyTxPlainText())); connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel())); connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); } void TransactionView::setModel(WalletModel *_model) { this->model = _model; if (_model) { transactionProxyModel = new TransactionFilterProxy(this); transactionProxyModel->setSourceModel( _model->getTransactionTableModel()); transactionProxyModel->setDynamicSortFilter(true); transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setSortRole(Qt::EditRole); transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); transactionView->setModel(transactionProxyModel); transactionView->setAlternatingRowColors(true); transactionView->setSelectionBehavior(QAbstractItemView::SelectRows); transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection); transactionView->setSortingEnabled(true); transactionView->sortByColumn(TransactionTableModel::Date, Qt::DescendingOrder); transactionView->verticalHeader()->hide(); transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer( transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this); if (_model->getOptionsModel()) { // Add third party transaction URLs to context menu QStringList listUrls = _model->getOptionsModel()->getThirdPartyTxUrls().split( "|", QString::SkipEmptyParts); for (int i = 0; i < listUrls.size(); ++i) { QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host(); if (!host.isEmpty()) { // use host as menu item label QAction *thirdPartyTxUrlAction = new QAction(host, this); if (i == 0) { contextMenu->addSeparator(); } contextMenu->addAction(thirdPartyTxUrlAction); connect(thirdPartyTxUrlAction, SIGNAL(triggered()), mapperThirdPartyTxUrls, SLOT(map())); mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed()); } } } // show/hide column Watch-only updateWatchOnlyColumn(_model->haveWatchOnly()); // Watch-only signal connect(_model, SIGNAL(notifyWatchonlyChanged(bool)), this, SLOT(updateWatchOnlyColumn(bool))); } } void TransactionView::chooseDate(int idx) { if (!transactionProxyModel) { return; } QDate current = QDate::currentDate(); dateRangeWidget->setVisible(false); switch (dateWidget->itemData(idx).toInt()) { case All: transactionProxyModel->setDateRange( TransactionFilterProxy::MIN_DATE, TransactionFilterProxy::MAX_DATE); break; case Today: transactionProxyModel->setDateRange( QDateTime(current), TransactionFilterProxy::MAX_DATE); break; case ThisWeek: { // Find last Monday QDate startOfWeek = current.addDays(-(current.dayOfWeek() - 1)); transactionProxyModel->setDateRange( QDateTime(startOfWeek), TransactionFilterProxy::MAX_DATE); } break; case ThisMonth: transactionProxyModel->setDateRange( QDateTime(QDate(current.year(), current.month(), 1)), TransactionFilterProxy::MAX_DATE); break; case LastMonth: transactionProxyModel->setDateRange( QDateTime( QDate(current.year(), current.month(), 1).addMonths(-1)), QDateTime(QDate(current.year(), current.month(), 1))); break; case ThisYear: transactionProxyModel->setDateRange( QDateTime(QDate(current.year(), 1, 1)), TransactionFilterProxy::MAX_DATE); break; case Range: dateRangeWidget->setVisible(true); dateRangeChanged(); break; } } void TransactionView::chooseType(int idx) { if (!transactionProxyModel) { return; } transactionProxyModel->setTypeFilter(typeWidget->itemData(idx).toInt()); } void TransactionView::chooseWatchonly(int idx) { if (!transactionProxyModel) { return; } transactionProxyModel->setWatchOnlyFilter( (TransactionFilterProxy::WatchOnlyFilter)watchOnlyWidget->itemData(idx) .toInt()); } void TransactionView::changedPrefix(const QString &prefix) { if (!transactionProxyModel) { return; } transactionProxyModel->setAddressPrefix(prefix); } void TransactionView::changedAmount(const QString &amount) { if (!transactionProxyModel) { return; } Amount amount_parsed = Amount::zero(); if (BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed)) { transactionProxyModel->setMinAmount(amount_parsed); } else { transactionProxyModel->setMinAmount(Amount::zero()); } } void TransactionView::exportClicked() { // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName( this, tr("Export Transaction History"), QString(), tr("Comma separated file (*.csv)"), nullptr); if (filename.isNull()) { return; } CSVModelWriter writer(filename); // name, column, role writer.setModel(transactionProxyModel); writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole); if (model && model->haveWatchOnly()) { writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly); } writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole); writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole); writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole); writer.addColumn(BitcoinUnits::getAmountColumnTitle( model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole); writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole); if (!writer.write()) { Q_EMIT message(tr("Exporting Failed"), tr("There was an error trying to save the transaction " "history to %1.") .arg(filename), CClientUIInterface::MSG_ERROR); } else { Q_EMIT message( tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.") .arg(filename), CClientUIInterface::MSG_INFORMATION); } } void TransactionView::contextualMenu(const QPoint &point) { QModelIndex index = transactionView->indexAt(point); QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); if (selection.empty()) { return; } // check if transaction can be abandoned, disable context menu action in // case it doesn't TxId txid; txid.SetHex(selection.at(0) .data(TransactionTableModel::TxHashRole) .toString() .toStdString()); abandonAction->setEnabled(model->transactionCanBeAbandoned(txid)); if (index.isValid()) { contextMenu->exec(QCursor::pos()); } } void TransactionView::abandonTx() { if (!transactionView || !transactionView->selectionModel()) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); // get the hash from the TxHashRole (QVariant / QString) QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); TxId txid; txid.SetHex(hashQStr.toStdString()); // Abandon the wallet transaction over the walletModel model->abandonTransaction(txid); // Update the table model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false); } void TransactionView::copyAddress() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole); } void TransactionView::copyLabel() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole); } void TransactionView::copyAmount() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole); } void TransactionView::copyTxID() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole); } void TransactionView::copyTxHex() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHexRole); } void TransactionView::copyTxPlainText() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); } void TransactionView::editLabel() { if (!transactionView->selectionModel() || !model) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(); if (!selection.isEmpty()) { AddressTableModel *addressBook = model->getAddressTableModel(); if (!addressBook) { return; } QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString(); if (address.isEmpty()) { // If this transaction has no associated address, exit return; } // Is address in address book? Address book can miss address when a // transaction is sent from outside the UI. int idx = addressBook->lookupAddress(address); if (idx != -1) { // Edit sending / receiving address QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex()); // Determine type of address, launch appropriate editor dialog type QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); EditAddressDialog dlg(type == AddressTableModel::Receive ? EditAddressDialog::EditReceivingAddress : EditAddressDialog::EditSendingAddress, this); dlg.setModel(addressBook); dlg.loadRow(idx); dlg.exec(); } else { // Add sending address EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this); dlg.setModel(addressBook); dlg.setAddress(address); dlg.exec(); } } } void TransactionView::showDetails() { if (!transactionView->selectionModel()) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(); if (!selection.isEmpty()) { TransactionDescDialog *dlg = new TransactionDescDialog(selection.at(0)); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); } } void TransactionView::openThirdPartyTxUrl(QString url) { if (!transactionView || !transactionView->selectionModel()) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); if (!selection.isEmpty()) { QDesktopServices::openUrl(QUrl::fromUserInput( url.replace("%s", selection.at(0) .data(TransactionTableModel::TxHashRole) .toString()))); } } QWidget *TransactionView::createDateRangeWidget() { dateRangeWidget = new QFrame(); dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); dateRangeWidget->setContentsMargins(1, 1, 1, 1); QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget); layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing(23); layout->addWidget(new QLabel(tr("Range:"))); dateFrom = new QDateTimeEdit(this); dateFrom->setDisplayFormat("dd/MM/yy"); dateFrom->setCalendarPopup(true); dateFrom->setMinimumWidth(100); dateFrom->setDate(QDate::currentDate().addDays(-7)); layout->addWidget(dateFrom); layout->addWidget(new QLabel(tr("to"))); dateTo = new QDateTimeEdit(this); dateTo->setDisplayFormat("dd/MM/yy"); dateTo->setCalendarPopup(true); dateTo->setMinimumWidth(100); dateTo->setDate(QDate::currentDate()); layout->addWidget(dateTo); layout->addStretch(); // Hide by default dateRangeWidget->setVisible(false); // Notify on change connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); return dateRangeWidget; } void TransactionView::dateRangeChanged() { if (!transactionProxyModel) { return; } transactionProxyModel->setDateRange(QDateTime(dateFrom->date()), QDateTime(dateTo->date()).addDays(1)); } void TransactionView::focusTransaction(const QModelIndex &idx) { if (!transactionProxyModel) { return; } QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx); transactionView->scrollTo(targetIdx); transactionView->setCurrentIndex(targetIdx); transactionView->setFocus(); } // We override the virtual resizeEvent of the QWidget to adjust tables column // sizes as the tables width is proportional to the dialogs width. void TransactionView::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress); } // Need to override default Ctrl+C action for amount as default behaviour is // just to copy DisplayRole text bool TransactionView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier)) { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); return true; } } return QWidget::eventFilter(obj, event); } // show/hide column Watch-only void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly) { watchOnlyWidget->setVisible(fHaveWatchOnly); transactionView->setColumnHidden(TransactionTableModel::Watchonly, !fHaveWatchOnly); } diff --git a/src/qt/winshutdownmonitor.cpp b/src/qt/winshutdownmonitor.cpp index 723026e35..7962e6351 100644 --- a/src/qt/winshutdownmonitor.cpp +++ b/src/qt/winshutdownmonitor.cpp @@ -1,74 +1,74 @@ // Copyright (c) 2014-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "winshutdownmonitor.h" -#if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 -#include "init.h" -#include "util.h" +#if defined(Q_OS_WIN) +#include +#include #include #include #include // If we don't want a message to be processed by Qt, return true and set result // to the value that the window procedure should return. Otherwise return false. bool WinShutdownMonitor::nativeEventFilter(const QByteArray &eventType, void *pMessage, long *pnResult) { Q_UNUSED(eventType); MSG *pMsg = static_cast(pMessage); // Seed OpenSSL PRNG with Windows event data (e.g. mouse movements and // other user interactions) if (RAND_event(pMsg->message, pMsg->wParam, pMsg->lParam) == 0) { // Warn only once as this is performance-critical static bool warned = false; if (!warned) { LogPrintf("%s: OpenSSL RAND_event() failed to seed OpenSSL PRNG " "with enough data.\n", __func__); warned = true; } } switch (pMsg->message) { case WM_QUERYENDSESSION: { // Initiate a client shutdown after receiving a WM_QUERYENDSESSION // and block // Windows session end until we have finished client shutdown. StartShutdown(); *pnResult = FALSE; return true; } case WM_ENDSESSION: { *pnResult = FALSE; return true; } } return false; } void WinShutdownMonitor::registerShutdownBlockReason(const QString &strReason, const HWND &mainWinId) { typedef BOOL(WINAPI * PSHUTDOWNBRCREATE)(HWND, LPCWSTR); PSHUTDOWNBRCREATE shutdownBRCreate = (PSHUTDOWNBRCREATE)GetProcAddress( GetModuleHandleA("User32.dll"), "ShutdownBlockReasonCreate"); if (shutdownBRCreate == nullptr) { qWarning() << "registerShutdownBlockReason: GetProcAddress for " "ShutdownBlockReasonCreate failed"; return; } if (shutdownBRCreate(mainWinId, strReason.toStdWString().c_str())) qWarning() << "registerShutdownBlockReason: Successfully registered: " + strReason; else qWarning() << "registerShutdownBlockReason: Failed to register: " + strReason; } #endif diff --git a/src/qt/winshutdownmonitor.h b/src/qt/winshutdownmonitor.h index 53cf1c764..9f1e94bc2 100644 --- a/src/qt/winshutdownmonitor.h +++ b/src/qt/winshutdownmonitor.h @@ -1,32 +1,30 @@ // Copyright (c) 2014 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_WINSHUTDOWNMONITOR_H #define BITCOIN_QT_WINSHUTDOWNMONITOR_H #ifdef WIN32 #include #include -#if QT_VERSION >= 0x050000 #include // for HWND #include class WinShutdownMonitor : public QAbstractNativeEventFilter { public: /** Implements QAbstractNativeEventFilter interface for processing Windows * messages */ bool nativeEventFilter(const QByteArray &eventType, void *pMessage, long *pnResult); /** Register the reason for blocking shutdown on Windows to allow clean * client exit */ static void registerShutdownBlockReason(const QString &strReason, const HWND &mainWinId); }; #endif -#endif #endif // BITCOIN_QT_WINSHUTDOWNMONITOR_H