diff -Nru copyq-3.9.0/CHANGES copyq-3.9.1/CHANGES --- copyq-3.9.0/CHANGES 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/CHANGES 2019-08-18 09:07:57.000000000 +0000 @@ -1,3 +1,42 @@ +v3.9.1 + +- Commands are moved to a separate configuration file "copyq-commands.ini". + +- Horizontal tabs in the configuration dialog were replaced with a list of + sections so it's possible to view all of the sections even in a smaller + window. + +- New option `hide_main_window_in_task_bar` to hide window in task bar can be + set using `copyq config hide_main_window_in_task_bar true`. + +- New `logs()` script function prints application logs. + +- New `clipboardFormatsToSave()` script function allows to override clipboard + formats to save. + +- Some hidden options can be modified using `config()` script function. + +- Font sizes in items and editor are limited to prevent application freeze. + +- Application icons are cached so as to avoid creating icons for the snip + animation again. + +- Fix restoring tabs with some non-ASCII characters + +- Fix opening window on different screen with different DPI + +- Fix 100% CPU utilization on wide screens (5120x1440) + +- Fix icon size and GUI margins in Tabs configuration tab + +- X11: Fix stuck clipboard access + +- X11: Faster selection synchronization + +- OSX: Prevent showing font download dialog + +- OSX: Fix clipboard owner window title + v3.9.0 - Large images in clipboard are no longer automatically converted to other diff -Nru copyq-3.9.0/debian/changelog copyq-3.9.1/debian/changelog --- copyq-3.9.0/debian/changelog 2019-07-09 19:19:46.000000000 +0000 +++ copyq-3.9.1/debian/changelog 2019-08-20 15:36:00.000000000 +0000 @@ -1,3 +1,9 @@ +copyq (3.9.1-1) unstable; urgency=medium + + * New upstream release 3.9.1. + + -- Boyuan Yang Tue, 20 Aug 2019 11:36:00 -0400 + copyq (3.9.0-1) unstable; urgency=medium * New upstream release 3.9.0. diff -Nru copyq-3.9.0/docs/faq.rst copyq-3.9.1/docs/faq.rst --- copyq-3.9.0/docs/faq.rst 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/docs/faq.rst 2019-08-18 09:07:57.000000000 +0000 @@ -250,6 +250,37 @@ Icon=\xf56f Name=Store File Manager Metadata +How to trigger a command based on primary selection only? +--------------------------------------------------------- + +You can check ``application/x-copyq-clipboard-mode`` format in automatic commands. + +E.g. if you set input format of a command it would be only executed on X11 selection change: + +.. code-block:: ini + + [Command] + Automatic=true + Command=" + copyq: + popup(input())" + Input=application/x-copyq-clipboard-mode + Name=Executed only on X11 selection change + +Otherwise you can check it in command: + +.. code-block:: ini + + [Command] + Automatic=true + Command=" + copyq: + if (str(data(mimeClipboardMode)) == 'selection') + popup('selection changed') + else + popup('clipboard changed')" + Name=Show clipboard/selection change + Why can I no longer paste from the application on macOS? -------------------------------------------------------- diff -Nru copyq-3.9.0/docs/scripting-api.rst copyq-3.9.1/docs/scripting-api.rst --- copyq-3.9.0/docs/scripting-api.rst 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/docs/scripting-api.rst 2019-08-18 09:07:57.000000000 +0000 @@ -576,6 +576,10 @@ Prints value to application log. +.. js:function:: String logs() + + Returns application logs. + .. js:function:: abort() Aborts script evaluation. @@ -1057,6 +1061,30 @@ Default implementation calls ``provideClipboard()``. +.. js:function:: String[] clipboardFormatsToSave() + + Returns list of clipboard format to save automatically. + + Override the funtion, for example, to save only plain text: + + .. code-block:: js + + global.clipboardFormatsToSave = function() { + return ["text/plain"] + } + + Or to save additional formats: + + .. code-block:: js + + var originalFunction = global.clipboardFormatsToSave; + global.clipboardFormatsToSave = function() { + return originalFunction().concat([ + "text/uri-list", + "text/xml" + ]) + } + .. js:function:: saveData() Save current data (depends on `mimeOutputTab`). diff -Nru copyq-3.9.0/plugins/itemtext/CMakeLists.txt copyq-3.9.1/plugins/itemtext/CMakeLists.txt --- copyq-3.9.0/plugins/itemtext/CMakeLists.txt 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/plugins/itemtext/CMakeLists.txt 2019-08-18 09:07:57.000000000 +0000 @@ -1,5 +1,6 @@ set(copyq_plugin_itemtext_SOURCES ../../src/common/mimetypes.cpp + ../../src/common/sanitize_text_document.cpp ../../src/common/textdata.cpp ) diff -Nru copyq-3.9.0/plugins/itemtext/itemtext.cpp copyq-3.9.1/plugins/itemtext/itemtext.cpp --- copyq-3.9.0/plugins/itemtext/itemtext.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/plugins/itemtext/itemtext.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -21,6 +21,7 @@ #include "ui_itemtextsettings.h" #include "common/mimetypes.h" +#include "common/sanitize_text_document.h" #include "common/textdata.h" #include @@ -163,6 +164,9 @@ } } + if (m_isRichText) + sanitizeTextDocument(&m_textDocument); + setDocument(&m_textDocument); connect( this, &QTextEdit::selectionChanged, diff -Nru copyq-3.9.0/README.md copyq-3.9.1/README.md --- copyq-3.9.0/README.md 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/README.md 2019-08-18 09:07:57.000000000 +0000 @@ -162,7 +162,15 @@ Insert some texts to the history: - copyq add "first item" "second item" "third item" + copyq add -- 'first item' 'second item' 'third item' + +Omitting double-dash (`--`) in the command above would mean that slash +(`\`) in arguments will be treated as special character so that `\n` is new +line character, `\t` is tab, `\\` is slash, `\x` is `x` etc. + +Create single item containing two lines: + + copyq add 'first line\nsecond line' Print content of the first three items: diff -Nru copyq-3.9.0/RELEASE.md copyq-3.9.1/RELEASE.md --- copyq-3.9.0/RELEASE.md 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/RELEASE.md 2019-08-18 09:07:57.000000000 +0000 @@ -58,6 +58,9 @@ Upload packages and binaries to: - [github](https://github.com/hluk/CopyQ/releases) - [sourceforge](https://sourceforge.net/projects/copyq/files/) +- [fosshub](https://www.fosshub.com/CopyQ.html) + + ./utils/fosshub.py 3.3.1 $TOKEN Update Homebrew package for OS X. diff -Nru copyq-3.9.0/shared/com.github.hluk.copyq.appdata.xml copyq-3.9.1/shared/com.github.hluk.copyq.appdata.xml --- copyq-3.9.0/shared/com.github.hluk.copyq.appdata.xml 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/shared/com.github.hluk.copyq.appdata.xml 2019-08-18 09:07:57.000000000 +0000 @@ -26,6 +26,7 @@ https://hosted.weblate.org/engage/copyq/ + @@ -66,4 +67,6 @@ hluk_at_email.cz Lukas Holecek + + diff -Nru copyq-3.9.0/src/app/app.cpp copyq-3.9.1/src/app/app.cpp --- copyq-3.9.0/src/app/app.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/app/app.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -19,6 +19,9 @@ #include "app.h" +#include "common/command.h" +#include "common/commandstore.h" +#include "common/config.h" #include "common/log.h" #include "common/settings.h" #include "common/textdata.h" @@ -122,6 +125,49 @@ QLocale::setDefault(QLocale(locale)); } +/// Move commands to separate config file. +void migrateCommands(const QString &commandConfigPath) +{ + Settings oldSettings; + const auto oldCommands = loadCommands(oldSettings.settingsData()); + + const QString commandConfigPathNew = commandConfigPath + ".new"; + { + Settings newSettings(commandConfigPathNew); + saveCommands(oldCommands, newSettings.settingsData()); + } + + { + QSettings newSettings(commandConfigPathNew, QSettings::IniFormat); + const auto newCommands = loadCommands(&newSettings); + if ( newCommands != oldCommands ) { + log( QString("Failed to save commands in new file %1") + .arg(commandConfigPathNew), LogError ); + return; + } + } + + if ( !QFile::rename(commandConfigPathNew, commandConfigPath) ) { + log( QString("Failed to save commands in new file %1") + .arg(commandConfigPath), LogError ); + return; + } + + oldSettings.remove("Commands"); + oldSettings.remove("Command"); +} + +void restoreConfiguration() +{ + Settings().restore(); + + const QString commandConfigPath = getConfigurationFilePath("-commands.ini"); + if ( QFile::exists(commandConfigPath) ) + Settings(commandConfigPath).restore(); + else + migrateCommands(commandConfigPath); +} + } // namespace App::App(QCoreApplication *application, @@ -177,7 +223,7 @@ platformNativeInterface()->loadSettings(); if (canModifySettings) - Settings::restore(); + restoreConfiguration(); installTranslator(); } diff -Nru copyq-3.9.0/src/app/clipboardclient.cpp copyq-3.9.1/src/app/clipboardclient.cpp --- copyq-3.9.0/src/app/clipboardclient.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/app/clipboardclient.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -56,20 +56,11 @@ QCoreApplication *createClientApplication(int &argc, char **argv, const QStringList &arguments) { // Clipboard access requires QApplication. - if ( !arguments.isEmpty() && ( - arguments[0] == "monitorClipboard" - || arguments[0] == "provideClipboard" - || arguments[0] == "provideSelection" -#ifdef HAS_MOUSE_SELECTIONS - || arguments[0] == "synchronizeToSelection" - || arguments[0] == "synchronizeFromSelection" -#endif - ) ) - { + if ( arguments.size() > 1 && arguments[0] == "--clipboard-access" ) { QGuiApplication::setDesktopSettingsAware(false); const auto app = platformNativeInterface() ->createClipboardProviderApplication(argc, argv); - setCurrentThreadName(arguments[0]); + setCurrentThreadName(arguments[1]); return app; } diff -Nru copyq-3.9.0/src/app/clipboardmonitor.cpp copyq-3.9.1/src/app/clipboardmonitor.cpp --- copyq-3.9.0/src/app/clipboardmonitor.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/app/clipboardmonitor.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -115,20 +115,6 @@ .arg(mode == ClipboardMode::Clipboard ? "Clipboard" : "Selection", getTextData(data, mimeOwner)) ); - if (mode != ClipboardMode::Clipboard) { - const QString modeName = mode == ClipboardMode::Selection - ? "selection" - : "find buffer"; - data.insert(mimeClipboardMode, modeName); - } - - // add window title of clipboard owner - if ( !data.contains(mimeOwner) && !data.contains(mimeWindowTitle) ) { - const QByteArray windowTitle = m_clipboard->clipboardOwner(); - if ( !windowTitle.isEmpty() ) - data.insert(mimeWindowTitle, windowTitle); - } - #ifdef HAS_MOUSE_SELECTIONS if ( (mode == ClipboardMode::Clipboard ? m_clipboardToSelection : m_selectionToClipboard) && !data.contains(mimeOwner) ) @@ -147,6 +133,20 @@ return; #endif + if (mode != ClipboardMode::Clipboard) { + const QString modeName = mode == ClipboardMode::Selection + ? "selection" + : "find buffer"; + data.insert(mimeClipboardMode, modeName); + } + + // add window title of clipboard owner + if ( !data.contains(mimeOwner) && !data.contains(mimeWindowTitle) ) { + const QByteArray windowTitle = m_clipboard->clipboardOwner(); + if ( !windowTitle.isEmpty() ) + data.insert(mimeWindowTitle, windowTitle); + } + // run automatic commands if ( anySessionOwnsClipboardData(data) ) { emit clipboardChanged(data, ClipboardOwnership::Own); diff -Nru copyq-3.9.0/src/app/clipboardserver.cpp copyq-3.9.1/src/app/clipboardserver.cpp --- copyq-3.9.0/src/app/clipboardserver.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/app/clipboardserver.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -30,7 +30,6 @@ #include "common/sleeptimer.h" #include "gui/clipboardbrowser.h" #include "gui/commanddialog.h" -#include "gui/configtabshortcuts.h" #include "gui/iconfactory.h" #include "gui/mainwindow.h" #include "item/itemfactory.h" @@ -181,7 +180,7 @@ COPYQ_LOG("Starting monitor"); m_monitor = new Action(); - m_monitor->setCommand("copyq monitorClipboard"); + m_monitor->setCommand("copyq --clipboard-access monitorClipboard"); connect( m_monitor.data(), &QObject::destroyed, this, &ClipboardServer::onMonitorFinished ); m_wnd->runInternalAction(m_monitor); diff -Nru copyq-3.9.0/src/common/action.cpp copyq-3.9.1/src/common/action.cpp --- copyq-3.9.0/src/common/action.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/action.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -294,11 +294,6 @@ } } -bool Action::waitForStarted(int msecs) -{ - return !m_processes.empty() && m_processes.back()->waitForStarted(msecs); -} - bool Action::waitForFinished(int msecs) { if ( !isRunning() ) diff -Nru copyq-3.9.0/src/common/action.h copyq-3.9.1/src/common/action.h --- copyq-3.9.0/src/common/action.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/action.h 2019-08-18 09:07:57.000000000 +0000 @@ -76,8 +76,6 @@ /** Execute command. */ void start(); - bool waitForStarted(int msecs); - bool waitForFinished(int msecs = -1); bool isRunning() const; diff -Nru copyq-3.9.0/src/common/appconfig.h copyq-3.9.1/src/common/appconfig.h --- copyq-3.9.0/src/common/appconfig.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/appconfig.h 2019-08-18 09:07:57.000000000 +0000 @@ -181,6 +181,11 @@ static QString name() { return "hide_main_window"; } }; +struct hide_main_window_in_task_bar : Config { + static QString name() { return "hide_main_window_in_task_bar"; } + static Value defaultValue() { return false; } +}; + struct tab_tree : Config { static QString name() { return "tab_tree"; } }; diff -Nru copyq-3.9.0/src/common/commandstore.cpp copyq-3.9.1/src/common/commandstore.cpp --- copyq-3.9.0/src/common/commandstore.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/commandstore.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -21,6 +21,7 @@ #include "common/command.h" #include "common/common.h" +#include "common/config.h" #include "common/mimetypes.h" #include "common/settings.h" #include "common/temporarysettings.h" @@ -149,13 +150,13 @@ Commands loadAllCommands() { - QSettings settings; - return loadCommands(&settings); + const QString commandConfigPath = getConfigurationFilePath("-commands.ini"); + return importCommandsFromFile(commandConfigPath); } void saveCommands(const Commands &commands) { - Settings settings; + Settings settings(getConfigurationFilePath("-commands.ini")); saveCommands(commands, settings.settingsData()); } @@ -171,11 +172,20 @@ settings->endGroup(); } - int size = settings->beginReadArray("Commands"); - - for(int i=0; isetArrayIndex(i); - loadCommand(*settings, &commands); + const int size = settings->beginReadArray("Commands"); + // Allow undefined "size" for the array in settings. + if (size == 0) { + for (int i = 0; ; ++i) { + settings->setArrayIndex(i); + if ( settings->childKeys().isEmpty() ) + break; + loadCommand(*settings, &commands); + } + } else { + for (int i = 0; i < size; ++i) { + settings->setArrayIndex(i); + loadCommand(*settings, &commands); + } } settings->endArray(); diff -Nru copyq-3.9.0/src/common/common.cpp copyq-3.9.1/src/common/common.cpp --- copyq-3.9.0/src/common/common.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/common.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -43,6 +43,11 @@ #include #include +#ifdef COPYQ_WS_X11 +# include "platform/x11/x11platform.h" +# include +#endif + #include #include @@ -50,9 +55,69 @@ const int maxElidedTextLineLength = 512; +#ifdef COPYQ_WS_X11 +// WORKAROUND: This fixes stuck clipboard access by creating dummy X11 events +// when accessing clipboard takes too long. +class WakeUpThread final { +public: + WakeUpThread() + { + m_timerWakeUp.setInterval(100); + QObject::connect( &m_timerWakeUp, &QTimer::timeout, []() { + sendDummyX11Event(); + }); + + m_timerWakeUp.moveToThread(&m_wakeUpThread); + QObject::connect( &m_wakeUpThread, &QThread::started, + &m_timerWakeUp, [this]() { m_timerWakeUp.start(); } ); + QObject::connect( &m_wakeUpThread, &QThread::finished, + &m_timerWakeUp, &QTimer::stop ); + m_wakeUpThread.start(); + } + + ~WakeUpThread() + { + m_wakeUpThread.quit(); + m_wakeUpThread.wait(); + } + +private: + QTimer m_timerWakeUp; + QThread m_wakeUpThread; +}; +#endif + +class MimeData final : public QMimeData { +protected: + QVariant retrieveData(const QString &mimeType, QVariant::Type preferredType) const override { + COPYQ_LOG_VERBOSE( QString("Providing \"%1\"").arg(mimeType) ); + return QMimeData::retrieveData(mimeType, preferredType); + } +}; + // Avoids accessing old clipboard/drag'n'drop data. class ClipboardDataGuard final { public: + class ElapsedGuard { + public: + explicit ElapsedGuard(const QString &format) + : m_format(format) + { + COPYQ_LOG_VERBOSE( QString("Accessing \"%1\"").arg(format) ); + m_elapsed.start(); + } + + ~ElapsedGuard() + { + const auto t = m_elapsed.elapsed(); + if (t > 500) + log( QString("ELAPSED %1 ms acessing \"%2\"").arg(t).arg(m_format), LogWarning ); + } + private: + QString m_format; + QElapsedTimer m_elapsed; + }; + explicit ClipboardDataGuard(const QMimeData &data) : m_dataGuard(&data) { @@ -61,21 +126,25 @@ bool hasFormat(const QString &mime) { + ElapsedGuard _("has:" + mime); return refresh() && m_dataGuard->hasFormat(mime); } QByteArray data(const QString &mime) { + ElapsedGuard _(mime); return refresh() ? m_dataGuard->data(mime) : QByteArray(); } QList urls() { + ElapsedGuard _("urls"); return refresh() ? m_dataGuard->urls() : QList(); } QImage getImageData() { + ElapsedGuard _("imageData"); if (!refresh()) return QImage(); @@ -89,6 +158,7 @@ QByteArray getUtf8Data(const QString &format) { + ElapsedGuard _("UTF8:" + format); if (!refresh()) return QByteArray(); @@ -129,6 +199,10 @@ QPointer m_dataGuard; QElapsedTimer m_timerExpire; + +#ifdef COPYQ_WS_X11 + WakeUpThread m_wakeUpThread; +#endif }; QString getImageFormatFromMime(const QString &mime) @@ -335,7 +409,7 @@ QStringList copyFormats = data.keys(); copyFormats.removeOne(mimeClipboardMode); - std::unique_ptr newClipboardData(new QMimeData); + std::unique_ptr newClipboardData(new MimeData); for ( const auto &format : copyFormats ) newClipboardData->setData( format, data[format].toByteArray() ); diff -Nru copyq-3.9.0/src/common/config.cpp copyq-3.9.1/src/common/config.cpp --- copyq-3.9.0/src/common/config.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/config.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -105,6 +105,23 @@ return tag; } +/// Make top of the window always visible on screen so it's possible to move and resize the window. +QPoint sanitizeWindowPosition(QPoint pos) +{ + const QRect availableGeometry = QApplication::desktop()->availableGeometry(pos); + const int x = qBound(availableGeometry.left(), pos.x(), availableGeometry.right() - 10); + const int y = qBound(availableGeometry.top(), pos.y(), availableGeometry.bottom() - 10); + return QPoint(x, y); +} + +void ensureWindowOnScreen(QWidget *w) +{ + const QPoint pos = w->pos(); + const QPoint newPos = sanitizeWindowPosition(pos); + if (pos != newPos) + w->move(pos); +} + } // namespace QString getConfigurationFilePath(const QString &suffix) @@ -154,34 +171,31 @@ const auto position = availableGeometry.center() - w->rect().center(); w->move(position); - // Adjust window size to current screen and move to center again. - const auto idealSize = w->size(); - w->adjustSize(); - const auto size = w->size().expandedTo(idealSize); - w->resize(size); - w->move(position); - - geometry = w->saveGeometry(); - GEOMETRY_LOG( w, QString("New geometry for \"%1%2\"").arg(optionName, tag) ); } } if (w->saveGeometry() != geometry) { - // WORKAROUND: Fixes QWidget::restoreGeometry() for different monitor scaling. if ( openOnCurrentScreen ) { const int screenNumber = ::screenNumber(*w, GeometryAction::Restore); QScreen *screen = QGuiApplication::screens().value(screenNumber); if (screen) { - if ( w->windowHandle() ) - w->windowHandle()->setScreen(screen); - else - w->move(screen->geometry().topLeft()); + // WORKAROUND: Fixes QWidget::restoreGeometry() for different monitor scaling. + auto windowHandle = w->windowHandle(); + if ( windowHandle && windowHandle->screen() != screen ) + windowHandle->setScreen(screen); + + const QRect availableGeometry = screen->availableGeometry(); + const auto position = availableGeometry.center() - w->rect().center(); + w->move(position); } } const auto oldGeometry = w->geometry(); - w->restoreGeometry(geometry); + if ( !geometry.isEmpty() ) + w->restoreGeometry(geometry); + + ensureWindowOnScreen(w); const auto newGeometry = w->geometry(); GEOMETRY_LOG( w, QString("Restore geometry \"%1%2\": %3 -> %4").arg( @@ -233,13 +247,11 @@ void moveWindowOnScreen(QWidget *w, QPoint pos) { - const QRect availableGeometry = QApplication::desktop()->availableGeometry(pos); - const int x = qMax(availableGeometry.left(), qMin(pos.x(), availableGeometry.right() - w->width())); - const int y = qMax(availableGeometry.top(), qMin(pos.y(), availableGeometry.bottom() - w->height())); - GEOMETRY_LOG( w, QString("Move window on screen %1x%2") - .arg(availableGeometry.right()) - .arg(availableGeometry.bottom()) ); - w->move(x, y); + const QPoint newPos = sanitizeWindowPosition(pos); + GEOMETRY_LOG( w, QString("Move window [%1, %2]") + .arg(newPos.x()) + .arg(newPos.y()) ); + w->move(newPos); moveToCurrentWorkspace(w); } diff -Nru copyq-3.9.0/src/common/log.cpp copyq-3.9.1/src/common/log.cpp --- copyq-3.9.0/src/common/log.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/log.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -235,6 +235,24 @@ } } +bool writeLogFile(const QByteArray &message) +{ + SystemMutexLocker lock(getSessionMutex()); + + QFile f( ::logFileName() ); + if ( !f.open(QIODevice::Append) ) + return false; + + if ( !f.write(message) ) + return false; + + f.close(); + if ( f.size() > logFileSize ) + rotateLogFiles(); + + return true; +} + QByteArray createLogMessage(const QByteArray &label, const QByteArray &text) { return label + QByteArray(text).replace("\n", "\n" + label + " ") + "\n"; @@ -284,6 +302,19 @@ return content; } +bool removeLogFiles() +{ + SystemMutexLocker lock(getSessionMutex()); + + for (int i = 0; i < logFileCount; ++i) { + QFile logFile( logFileName(i) ); + if ( logFile.exists() && !logFile.remove() ) + return false; + } + + return true; +} + void createSessionMutex() { initSessionMutex(QSystemSemaphore::Create); @@ -320,15 +351,9 @@ if ( !hasLogLevel(level) ) return; - SystemMutexLocker lock(getSessionMutex()); - const auto msgText = text.toUtf8(); const auto msg = createLogMessage(msgText, level); - - QFile f( logFileName() ); - const bool writtenToLogFile = f.open(QIODevice::Append) && f.write(msg); - if (writtenToLogFile) - f.close(); + const bool writtenToLogFile = writeLogFile(msg); // Log to file and if needed to stderr. if ( !writtenToLogFile || level <= LogWarning || hasLogLevel(LogDebug) ) { @@ -337,9 +362,6 @@ const auto simpleMsg = createSimpleLogMessage(msgText, level); ferr.write(simpleMsg); } - - if ( writtenToLogFile && f.size() > logFileSize ) - rotateLogFiles(); } void setCurrentThreadName(const QString &name) diff -Nru copyq-3.9.0/src/common/log.h copyq-3.9.1/src/common/log.h --- copyq-3.9.0/src/common/log.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/log.h 2019-08-18 09:07:57.000000000 +0000 @@ -36,6 +36,8 @@ QString readLogFile(int maxReadSize); +bool removeLogFiles(); + void createSessionMutex(); bool hasLogLevel(LogLevel level); diff -Nru copyq-3.9.0/src/common/sanitize_text_document.cpp copyq-3.9.1/src/common/sanitize_text_document.cpp --- copyq-3.9.0/src/common/sanitize_text_document.cpp 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/common/sanitize_text_document.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,92 @@ +/* + Copyright (c) 2019, Lukas Holecek + + This file is part of CopyQ. + + CopyQ is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CopyQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CopyQ. If not, see . +*/ + +#include "sanitize_text_document.h" + +#include +#include +#include + +#ifdef Q_OS_MAC +# include +#endif + +namespace { + +constexpr int maxFontPointSize = 128; +constexpr int maxFontPixelSize = maxFontPointSize * 4 / 3; + +/// Sanitize font sizes to prevent app freeze. +bool sanitizeFont(QFont *font) +{ + const int pixelSize = font->pixelSize(); + const int pointSize = font->pointSize(); + + if (-maxFontPixelSize > pixelSize || pixelSize > maxFontPixelSize) { + font->setPixelSize(maxFontPixelSize); + return true; + } + + if (-maxFontPointSize > pointSize || pointSize > maxFontPointSize) { + font->setPointSize(maxFontPointSize); + return true; + } + + return false; +} + +} // namespace + +void sanitizeTextDocument(QTextDocument *document) +{ + QTextCursor tc(document); + +#ifdef Q_OS_MAC + QFontDatabase fontDatabase; +#endif + + for (auto block = document->begin(); block != document->end(); block = block.next()) { + for (auto it = block.begin(); !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + QTextCharFormat charFormat = fragment.charFormat(); + bool sanitized = false; + +#ifdef Q_OS_MAC + // Prevent showing font download dialog on macOS. + const QString fontFamily = charFormat.fontFamily(); + if ( !fontDatabase.hasFamily(fontFamily) ) { + charFormat.setFontFamily(QString()); + sanitized = true; + } +#endif + + QFont font = charFormat.font(); + if ( sanitizeFont(&font) ) { + charFormat.setFont(font); + sanitized = true; + } + + if (sanitized) { + tc.setPosition( fragment.position() ); + tc.setPosition( fragment.position() + fragment.length(), QTextCursor::KeepAnchor ); + tc.setCharFormat(charFormat); + } + } + } +} diff -Nru copyq-3.9.0/src/common/sanitize_text_document.h copyq-3.9.1/src/common/sanitize_text_document.h --- copyq-3.9.0/src/common/sanitize_text_document.h 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/common/sanitize_text_document.h 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,27 @@ +/* + Copyright (c) 2019, Lukas Holecek + + This file is part of CopyQ. + + CopyQ is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CopyQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CopyQ. If not, see . +*/ + +#ifndef SANITIZE_TEXT_DOCUMENT_H +#define SANITIZE_TEXT_DOCUMENT_H + +class QTextDocument; + +void sanitizeTextDocument(QTextDocument *document); + +#endif // SANITIZE_TEXT_DOCUMENT_H diff -Nru copyq-3.9.0/src/common/settings.cpp copyq-3.9.1/src/common/settings.cpp --- copyq-3.9.0/src/common/settings.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/settings.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -55,25 +55,27 @@ to->sync(); } -QString lockFileName() +QString lockFileName(const QString &path) { - return getConfigurationFilePath(".bad"); + if ( path.isEmpty() ) + return getConfigurationFilePath(".bad"); + return path + ".bad"; } -bool isLastSaveUnfinished() +bool isLastSaveUnfinished(const QString &path) { - return QFile::exists(lockFileName()); + return QFile::exists(lockFileName(path)); } -void beginSave() +void beginSave(const QString &path) { - QFile lockFile(lockFileName()); + QFile lockFile(lockFileName(path)); lockFile.open(QIODevice::WriteOnly); } -void endSave() +void endSave(const QString &path) { - QFile::remove(lockFileName()); + QFile::remove(lockFileName(path)); } } // namespace @@ -93,7 +95,12 @@ QCoreApplication::applicationName() + "-bak" ) , m_changed(false) { - Q_ASSERT( isMainThread() ); +} + +Settings::Settings(const QString &path) + : m_settings(path + ".bak", QSettings::IniFormat) + , m_path(path) +{ } Settings::~Settings() @@ -102,33 +109,48 @@ if (canModifySettings && m_changed) { m_settings.sync(); - beginSave(); - QSettings to; + beginSave(m_path); + while ( !m_settings.group().isEmpty() ) m_settings.endGroup(); - copySettings(m_settings, &to); - endSave(); + + save(); + + endSave(m_path); } } void Settings::restore() { - Settings appSettings; - - if ( isLastSaveUnfinished() ) { + if ( isLastSaveUnfinished(m_path) ) { log("Restoring application settings", LogWarning); - if ( appSettings.isEmpty() ) { + if ( isEmpty() ) log("Cannot restore application settings", LogError); - } else { - QSettings settings; - copySettings(appSettings.m_settings, &settings); - } + else + save(); - endSave(); + endSave(m_path); + } else if ( m_path.isEmpty() ) { + restore( QSettings() ); + } else { + restore( QSettings(m_path, QSettings::IniFormat) ); + } +} + +void Settings::restore(const QSettings &settings) +{ + if ( needsUpdate(*this, settings) ) + copySettings(settings, &m_settings); +} + +void Settings::save() +{ + if ( m_path.isEmpty() ) { + QSettings settings; + copySettings(m_settings, &settings); } else { - const QSettings settings; - if ( needsUpdate(appSettings, settings) ) - copySettings(settings, &appSettings.m_settings); + QSettings settings(m_path, QSettings::IniFormat); + copySettings(m_settings, &settings); } } diff -Nru copyq-3.9.0/src/common/settings.h copyq-3.9.1/src/common/settings.h --- copyq-3.9.0/src/common/settings.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/settings.h 2019-08-18 09:07:57.000000000 +0000 @@ -21,6 +21,7 @@ #define SETTINGS_H #include +#include /** * Wrapper for safer QSettings() handling. @@ -55,9 +56,9 @@ Settings(); - ~Settings(); + explicit Settings(const QString &path); - static void restore(); + ~Settings(); bool isEmpty() const { return isEmpty(m_settings); } @@ -115,11 +116,19 @@ Settings(const Settings &) = delete; Settings &operator=(const Settings &) = delete; + void restore(); + private: + void restore(const QSettings &settings); + + void save(); + QSettings m_settings; /// True only if QSetting data changed and need to be synced. bool m_changed; + + QString m_path; }; diff -Nru copyq-3.9.0/src/common/version.h copyq-3.9.1/src/common/version.h --- copyq-3.9.0/src/common/version.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/common/version.h 2019-08-18 09:07:57.000000000 +0000 @@ -2,7 +2,7 @@ #define VERSION_H #ifndef COPYQ_VERSION -# define COPYQ_VERSION "v3.9.0" +# define COPYQ_VERSION "v3.9.1" #endif #endif // VERSION_H diff -Nru copyq-3.9.0/src/gui/clipboardbrowser.h copyq-3.9.1/src/gui/clipboardbrowser.h --- copyq-3.9.0/src/gui/clipboardbrowser.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/clipboardbrowser.h 2019-08-18 09:07:57.000000000 +0000 @@ -23,7 +23,6 @@ #include "common/clipboardmode.h" #include "common/command.h" #include "gui/clipboardbrowsershared.h" -#include "gui/configtabshortcuts.h" #include "gui/theme.h" #include "item/clipboardmodel.h" #include "item/itemdelegate.h" diff -Nru copyq-3.9.0/src/gui/commandcompleterdocumentation.h copyq-3.9.1/src/gui/commandcompleterdocumentation.h --- copyq-3.9.0/src/gui/commandcompleterdocumentation.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/commandcompleterdocumentation.h 2019-08-18 09:07:57.000000000 +0000 @@ -84,6 +84,7 @@ addDocumentation("dataFormats", "String[] dataFormats()", "Returns formats available for `data()`."); addDocumentation("print", "print(value)", "Prints value to standard output."); addDocumentation("serverLog", "serverLog(value)", "Prints value to application log."); + addDocumentation("logs", "String logs()", "Returns application logs."); addDocumentation("abort", "abort()", "Aborts script evaluation."); addDocumentation("fail", "fail()", "Aborts script evaluation with nonzero exit code."); addDocumentation("setCurrentTab", "setCurrentTab(tabName)", "Focus tab without showing main window."); @@ -149,6 +150,7 @@ addDocumentation("setTitle", "setTitle([title])", "Set main window title and tool tip."); addDocumentation("synchronizeToSelection", "synchronizeToSelection(text)", "Synchronize current data from clipboard to X11 selection."); addDocumentation("synchronizeFromSelection", "synchronizeFromSelection(text)", "Synchronize current data from X11 selection to clipboard."); + addDocumentation("clipboardFormatsToSave", "String[] clipboardFormatsToSave()", "Returns list of clipboard format to save automatically."); addDocumentation("saveData", "saveData()", "Save current data (depends on `mimeOutputTab`)."); addDocumentation("hasData", "bool hasData()", "Returns true only if some non-empty data can be returned by data()."); addDocumentation("showDataNotification", "showDataNotification()", "Show notification for current data."); diff -Nru copyq-3.9.0/src/gui/commanddialog.cpp copyq-3.9.1/src/gui/commanddialog.cpp --- copyq-3.9.0/src/gui/commanddialog.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/commanddialog.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -153,7 +153,9 @@ ui->pushButtonSaveCommands->setIcon(iconSaveCommands()); ui->pushButtonCopyCommands->setIcon(iconCopyCommands()); ui->pushButtonPasteCommands->setIcon(iconPasteCommands()); - ui->itemOrderListCommands->setAddRemoveButtonsVisible(true); + ui->itemOrderListCommands->setWiderIconsEnabled(true); + ui->itemOrderListCommands->setEditable(true); + ui->itemOrderListCommands->setItemsMovable(true); addCommandsWithoutSave(loadAllCommands(), -1); if ( ui->itemOrderListCommands->itemCount() != 0 ) diff -Nru copyq-3.9.0/src/gui/configtabshortcuts.cpp copyq-3.9.1/src/gui/configtabshortcuts.cpp --- copyq-3.9.0/src/gui/configtabshortcuts.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/configtabshortcuts.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,53 +0,0 @@ -/* - Copyright (c) 2019, Lukas Holecek - - This file is part of CopyQ. - - CopyQ is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - CopyQ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with CopyQ. If not, see . -*/ - -#include "gui/configtabshortcuts.h" -#include "ui_configtabshortcuts.h" - -#include "common/client_server.h" -#include "common/common.h" -#include "gui/iconfactory.h" -#include "gui/icons.h" - -ConfigTabShortcuts::ConfigTabShortcuts(QWidget *parent) - : QWidget(parent) - , ui(new Ui::ConfigTabShortcuts) -{ - ui->setupUi(this); -} - -ConfigTabShortcuts::~ConfigTabShortcuts() -{ - delete ui; -} - -void ConfigTabShortcuts::loadShortcuts(const QSettings &settings) -{ - ui->shortcutsWidgetGeneral->loadShortcuts(settings); -} - -void ConfigTabShortcuts::saveShortcuts(QSettings *settings) const -{ - ui->shortcutsWidgetGeneral->saveShortcuts(settings); -} - -void ConfigTabShortcuts::addCommands(const QVector &commands) -{ - ui->shortcutsWidgetGeneral->addCommands(commands); -} diff -Nru copyq-3.9.0/src/gui/configtabshortcuts.h copyq-3.9.1/src/gui/configtabshortcuts.h --- copyq-3.9.0/src/gui/configtabshortcuts.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/configtabshortcuts.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -/* - Copyright (c) 2019, Lukas Holecek - - This file is part of CopyQ. - - CopyQ is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - CopyQ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with CopyQ. If not, see . -*/ - -#ifndef CONFIGTABSHORTCUTS_H -#define CONFIGTABSHORTCUTS_H - -#include -#include - -namespace Ui { -class ConfigTabShortcuts; -} - -class QSettings; - -struct Command; - -class ConfigTabShortcuts final : public QWidget -{ -public: - explicit ConfigTabShortcuts(QWidget *parent = nullptr); - - ~ConfigTabShortcuts(); - - /** Load shortcuts from settings file. */ - void loadShortcuts(const QSettings &settings); - /** Save shortcuts to settings file. */ - void saveShortcuts(QSettings *settings) const; - - void addCommands(const QVector &commands); - -private: - Ui::ConfigTabShortcuts *ui; -}; - -#endif // CONFIGTABSHORTCUTS_H diff -Nru copyq-3.9.0/src/gui/configtabtabs.cpp copyq-3.9.1/src/gui/configtabtabs.cpp --- copyq-3.9.0/src/gui/configtabtabs.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/configtabtabs.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -85,7 +85,10 @@ : QWidget(parent) , m_list(new ItemOrderList(this)) { + m_list->setItemsMovable(false); + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_list); const Tabs tabs; diff -Nru copyq-3.9.0/src/gui/configurationmanager.cpp copyq-3.9.1/src/gui/configurationmanager.cpp --- copyq-3.9.0/src/gui/configurationmanager.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/configurationmanager.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -20,6 +20,12 @@ #include "configurationmanager.h" #include "ui_configurationmanager.h" +#include "ui_configtabgeneral.h" +#include "ui_configtabhistory.h" +#include "ui_configtablayout.h" +#include "ui_configtabnotifications.h" +#include "ui_configtabtray.h" + #include "common/appconfig.h" #include "common/command.h" #include "common/common.h" @@ -28,9 +34,12 @@ #include "common/mimetypes.h" #include "common/option.h" #include "common/settings.h" +#include "gui/configtabappearance.h" +#include "gui/configtabtabs.h" #include "gui/iconfactory.h" #include "gui/icons.h" #include "gui/pluginwidget.h" +#include "gui/shortcutswidget.h" #include "gui/tabicons.h" #include "gui/windowgeometryguard.h" #include "item/clipboardmodel.h" @@ -44,11 +53,49 @@ #include #include #include +#include #include #include namespace { +class TabItem final : public ItemOrderList::Item { +public: + explicit TabItem(QWidget *widget) noexcept + : m_widget(widget) + { + m_widget->hide(); + } + + QVariant data() const override { return QVariant(); } + +private: + QWidget *createWidget(QWidget *) override + { + return m_widget; + } + + QWidget *m_widget; +}; + +template +ItemOrderList::ItemPtr makeTab(std::shared_ptr &ui, QWidget *parent) +{ + Q_ASSERT(!ui); + auto widget = new QScrollArea(parent); + ui = std::make_shared(); + ui->setupUi(widget); + return std::make_shared(widget); +} + +template +ItemOrderList::ItemPtr makeTab(Widget **widget, QWidget *parent) +{ + Q_ASSERT(!*widget); + *widget = new Widget(parent); + return std::make_shared(*widget); +} + class PluginItem final : public ItemOrderList::Item { public: explicit PluginItem(const ItemLoaderPtr &loader) @@ -88,25 +135,24 @@ , m_options() { ui->setupUi(this); + initTabIcons(); connectSlots(); setWindowIcon(appIcon()); - ui->spinBoxItems->setMaximum(Config::maxItems); + m_tabHistory->spinBoxItems->setMaximum(Config::maxItems); if ( itemFactory && itemFactory->hasLoaders() ) initPluginWidgets(itemFactory); - else - ui->tabItems->hide(); initOptions(); if (itemFactory) - ui->configTabAppearance->createPreview(itemFactory); + m_tabAppearance->createPreview(itemFactory); loadSettings(); if (itemFactory) - ui->configTabShortcuts->addCommands( itemFactory->commands() ); + m_tabShortcuts->addCommands( itemFactory->commands() ); } ConfigurationManager::ConfigurationManager() @@ -114,6 +160,7 @@ , m_options() { ui->setupUi(this); + initTabIcons(); connectSlots(); initOptions(); } @@ -125,24 +172,23 @@ void ConfigurationManager::initTabIcons() { - QTabWidget *tw = ui->tabWidget; - if ( !tw->tabIcon(0).isNull() ) - return; - - tw->setTabIcon( tw->indexOf(ui->tabGeneral), getIcon("", IconWrench) ); - tw->setTabIcon( tw->indexOf(ui->tabLayout), getIcon("", IconColumns) ); - tw->setTabIcon( tw->indexOf(ui->tabHistory), getIcon("", IconListAlt) ); - tw->setTabIcon( tw->indexOf(ui->tabTabs), getIconFromResources("tab_rename") ); - tw->setTabIcon( tw->indexOf(ui->tabItems), getIcon("", IconThList) ); - tw->setTabIcon( tw->indexOf(ui->tabTray), getIcon("", IconInbox) ); - tw->setTabIcon( tw->indexOf(ui->tabNotifications), getIcon("", IconInfoCircle) ); - tw->setTabIcon( tw->indexOf(ui->tabShortcuts), getIcon("", IconKeyboard) ); - tw->setTabIcon( tw->indexOf(ui->tabAppearance), getIcon("", IconImage) ); + ItemOrderList *list = ui->itemOrderList; + list->setItemsMovable(false); + list->appendItem( tr("General"), getIcon("", IconWrench), makeTab(m_tabGeneral, this) ); + list->appendItem( tr("Layout"), getIcon("", IconColumns), makeTab(m_tabLayout, this) ); + list->appendItem( tr("History"), getIcon("", IconThList), makeTab(m_tabHistory, this) ); + list->appendItem( tr("Tray"), getIcon("", IconInbox), makeTab(m_tabTray, this) ); + list->appendItem( tr("Notifications"), getIcon("", IconInfoCircle), makeTab(m_tabNotifications, this) ); + list->appendItem( tr("Tabs"), getIconFromResources("tab_rename"), makeTab(&m_tabTabs, this) ); + list->appendItem( tr("Items"), getIcon("", IconThList), makeTab(&m_tabItems, this) ); + list->appendItem( tr("Shortcuts"), getIcon("", IconKeyboard), makeTab(&m_tabShortcuts, this) ); + list->appendItem( tr("Appearance"), getIcon("", IconImage), makeTab(&m_tabAppearance, this) ); } void ConfigurationManager::initPluginWidgets(ItemFactory *itemFactory) { - ui->itemOrderListPlugins->clearItems(); + m_tabItems->clearItems(); + m_tabItems->setItemsMovable(true); for ( const auto &loader : itemFactory->loaders() ) { ItemOrderList::ItemPtr pluginItem(new PluginItem(loader)); @@ -150,14 +196,14 @@ const auto state = itemFactory->isLoaderEnabled(loader) ? ItemOrderList::Checked : ItemOrderList::Unchecked; - ui->itemOrderListPlugins->appendItem( loader->name(), icon, pluginItem, state ); + m_tabItems->appendItem( loader->name(), icon, pluginItem, state ); } } void ConfigurationManager::initLanguages() { - ui->comboBoxLanguage->addItem("English"); - ui->comboBoxLanguage->setItemData(0, "en"); + m_tabGeneral->comboBoxLanguage->addItem("English"); + m_tabGeneral->comboBoxLanguage->setItemData(0, "en"); const QString currentLocale = QLocale().name(); bool currentLocaleFound = false; // otherwise not found or partial match ("uk" partially matches locale "uk_UA") @@ -171,20 +217,20 @@ if (!language.isEmpty()) { languages.insert(language); - const int index = ui->comboBoxLanguage->count(); - ui->comboBoxLanguage->addItem(language); - ui->comboBoxLanguage->setItemData(index, locale); + const int index = m_tabGeneral->comboBoxLanguage->count(); + m_tabGeneral->comboBoxLanguage->addItem(language); + m_tabGeneral->comboBoxLanguage->setItemData(index, locale); if (!currentLocaleFound) { currentLocaleFound = (locale == currentLocale); if (currentLocaleFound || currentLocale.startsWith(locale + "_")) - ui->comboBoxLanguage->setCurrentIndex(index); + m_tabGeneral->comboBoxLanguage->setCurrentIndex(index); } } } } - ui->comboBoxLanguage->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_tabGeneral->comboBoxLanguage->setSizeAdjustPolicy(QComboBox::AdjustToContents); } void ConfigurationManager::updateAutostart() @@ -192,9 +238,9 @@ auto platform = platformNativeInterface(); if ( platform->canAutostart() ) { - bind(ui->checkBoxAutostart); + bind(m_tabGeneral->checkBoxAutostart); } else { - ui->checkBoxAutostart->hide(); + m_tabGeneral->checkBoxAutostart->hide(); } } @@ -207,65 +253,65 @@ void ConfigurationManager::initOptions() { /* general options */ - bind(ui->checkBoxAutostart); - bind(ui->comboBoxClipboardTab->lineEdit()); - bind(ui->spinBoxItems); - bind(ui->spinBoxExpireTab); - bind(ui->lineEditEditor); - bind(ui->spinBoxNotificationPopupInterval); - bind(ui->comboBoxNotificationPosition); - bind(ui->spinBoxClipboardNotificationLines); - bind(ui->spinBoxNotificationHorizontalOffset); - bind(ui->spinBoxNotificationVerticalOffset); - bind(ui->spinBoxNotificationMaximumWidth); - bind(ui->spinBoxNotificationMaximumHeight); - bind(ui->checkBoxEditCtrlReturn); - bind(ui->checkBoxShowSimpleItems); - bind(ui->checkBoxNumberSearch); - bind(ui->checkBoxMove); - bind(ui->checkBoxClip); - bind(ui->checkBoxConfirmExit); - bind(ui->checkBoxViMode); - bind(ui->checkBoxSaveFilterHistory); - bind(ui->checkBoxAutocompleteCommands); - bind(ui->checkBoxAlwaysOnTop); - bind(ui->checkBoxCloseOnUnfocus); - bind(ui->checkBoxOpenWindowsOnCurrentScreen); - bind(ui->spinBoxTransparencyFocused); - bind(ui->spinBoxTransparencyUnfocused); - bind(ui->checkBoxHideTabs); - bind(ui->checkBoxHideToolbar); - bind(ui->checkBoxHideToolbarLabels); - bind(ui->checkBoxDisableTray); - bind(ui->checkBoxHideWindow); - bind(ui->checkBoxTabTree); - bind(ui->checkBoxShowTabItemCount); - bind(ui->checkBoxTextWrap); - - bind(ui->checkBoxActivateCloses); - bind(ui->checkBoxActivateFocuses); - bind(ui->checkBoxActivatePastes); - - bind(ui->spinBoxTrayItems); - bind(ui->checkBoxPasteMenuItem); - bind(ui->checkBoxTrayShowCommands); - bind(ui->checkBoxMenuTabIsCurrent); - bind(ui->checkBoxTrayImages); - bind(ui->comboBoxMenuTab->lineEdit()); + bind(m_tabGeneral->checkBoxAutostart); + bind(m_tabHistory->comboBoxClipboardTab->lineEdit()); + bind(m_tabHistory->spinBoxItems); + bind(m_tabHistory->spinBoxExpireTab); + bind(m_tabHistory->lineEditEditor); + bind(m_tabNotifications->spinBoxNotificationPopupInterval); + bind(m_tabNotifications->comboBoxNotificationPosition); + bind(m_tabNotifications->spinBoxClipboardNotificationLines); + bind(m_tabNotifications->spinBoxNotificationHorizontalOffset); + bind(m_tabNotifications->spinBoxNotificationVerticalOffset); + bind(m_tabNotifications->spinBoxNotificationMaximumWidth); + bind(m_tabNotifications->spinBoxNotificationMaximumHeight); + bind(m_tabHistory->checkBoxEditCtrlReturn); + bind(m_tabHistory->checkBoxShowSimpleItems); + bind(m_tabHistory->checkBoxNumberSearch); + bind(m_tabHistory->checkBoxMove); + bind(m_tabGeneral->checkBoxClip); + bind(m_tabGeneral->checkBoxConfirmExit); + bind(m_tabGeneral->checkBoxViMode); + bind(m_tabGeneral->checkBoxSaveFilterHistory); + bind(m_tabGeneral->checkBoxAutocompleteCommands); + bind(m_tabGeneral->checkBoxAlwaysOnTop); + bind(m_tabGeneral->checkBoxCloseOnUnfocus); + bind(m_tabGeneral->checkBoxOpenWindowsOnCurrentScreen); + bind(m_tabLayout->spinBoxTransparencyFocused); + bind(m_tabLayout->spinBoxTransparencyUnfocused); + bind(m_tabLayout->checkBoxHideTabs); + bind(m_tabLayout->checkBoxHideToolbar); + bind(m_tabLayout->checkBoxHideToolbarLabels); + bind(m_tabTray->checkBoxDisableTray); + bind(m_tabLayout->checkBoxHideWindow); + bind(m_tabLayout->checkBoxTabTree); + bind(m_tabLayout->checkBoxShowTabItemCount); + bind(m_tabGeneral->checkBoxTextWrap); + + bind(m_tabHistory->checkBoxActivateCloses); + bind(m_tabHistory->checkBoxActivateFocuses); + bind(m_tabHistory->checkBoxActivatePastes); + + bind(m_tabTray->spinBoxTrayItems); + bind(m_tabTray->checkBoxPasteMenuItem); + bind(m_tabTray->checkBoxTrayShowCommands); + bind(m_tabTray->checkBoxMenuTabIsCurrent); + bind(m_tabTray->checkBoxTrayImages); + bind(m_tabTray->comboBoxMenuTab->lineEdit()); /* other options */ bind(); #ifdef HAS_MOUSE_SELECTIONS /* X11 clipboard selection monitoring and synchronization */ - bind(ui->checkBoxSel); - bind(ui->checkBoxCopyClip); - bind(ui->checkBoxCopySel); - bind(ui->checkBoxRunSel); + bind(m_tabGeneral->checkBoxSel); + bind(m_tabGeneral->checkBoxCopyClip); + bind(m_tabGeneral->checkBoxCopySel); + bind(m_tabGeneral->checkBoxRunSel); #else - ui->checkBoxCopySel->hide(); - ui->checkBoxSel->hide(); - ui->checkBoxCopyClip->hide(); - ui->checkBoxRunSel->hide(); + m_tabGeneral->checkBoxCopySel->hide(); + m_tabGeneral->checkBoxSel->hide(); + m_tabGeneral->checkBoxCopyClip->hide(); + m_tabGeneral->checkBoxRunSel->hide(); #endif // values of last submitted action @@ -273,6 +319,10 @@ bind(); bind(); bind(); + + bind(); + bind(); + bind(); } template @@ -314,8 +364,8 @@ void ConfigurationManager::updateTabComboBoxes() { - initTabComboBox(ui->comboBoxClipboardTab); - initTabComboBox(ui->comboBoxMenuTab); + initTabComboBox(m_tabHistory->comboBoxClipboardTab); + initTabComboBox(m_tabTray->comboBoxMenuTab); } QStringList ConfigurationManager::options() const @@ -323,11 +373,8 @@ QStringList options; for (auto it = m_options.constBegin(); it != m_options.constEnd(); ++it) { const auto &option = it.key(); - if ( it.value().value().canConvert(QVariant::String) - && !optionToolTip(option).isEmpty() ) - { + if ( it.value().value().canConvert(QVariant::String) ) options.append(option); - } } return options; @@ -377,16 +424,16 @@ settings.endGroup(); settings.beginGroup("Shortcuts"); - ui->configTabShortcuts->loadShortcuts(settings); + m_tabShortcuts->loadShortcuts(settings); settings.endGroup(); settings.beginGroup("Theme"); - ui->configTabAppearance->loadTheme(settings); + m_tabAppearance->loadTheme(settings); settings.endGroup(); - ui->configTabAppearance->setEditor( AppConfig().option() ); + m_tabAppearance->setEditor( AppConfig().option() ); - onCheckBoxMenuTabIsCurrentStateChanged( ui->checkBoxMenuTabIsCurrent->checkState() ); + onCheckBoxMenuTabIsCurrentStateChanged( m_tabTray->checkBoxMenuTabIsCurrent->checkState() ); updateTabComboBoxes(); @@ -432,7 +479,6 @@ QDialog::setVisible(visible); if (visible) { - initTabIcons(); initLanguages(); } } @@ -441,9 +487,9 @@ { connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &ConfigurationManager::onButtonBoxClicked); - connect(ui->checkBoxMenuTabIsCurrent, &QCheckBox::stateChanged, + connect(m_tabTray->checkBoxMenuTabIsCurrent, &QCheckBox::stateChanged, this, &ConfigurationManager::onCheckBoxMenuTabIsCurrentStateChanged); - connect(ui->spinBoxTrayItems, static_cast(&QSpinBox::valueChanged), + connect(m_tabTray->spinBoxTrayItems, static_cast(&QSpinBox::valueChanged), this, &ConfigurationManager::onSpinBoxTrayItemsValueChanged); } @@ -456,33 +502,33 @@ settings.setValue( it.key(), it.value().value() ); settings.endGroup(); - ui->configTabTabs->saveTabs(settings.settingsData()); + m_tabTabs->saveTabs(settings.settingsData()); // Save configuration without command line alternatives only if option widgets are initialized // (i.e. clicked OK or Apply in configuration dialog). settings.beginGroup("Shortcuts"); - ui->configTabShortcuts->saveShortcuts(settings.settingsData()); + m_tabShortcuts->saveShortcuts(settings.settingsData()); settings.endGroup(); settings.beginGroup("Theme"); - ui->configTabAppearance->saveTheme(settings.settingsData()); + m_tabAppearance->saveTheme(settings.settingsData()); settings.endGroup(); // Save settings for each plugin. settings.beginGroup("Plugins"); QStringList pluginPriority; - pluginPriority.reserve( ui->itemOrderListPlugins->itemCount() ); + pluginPriority.reserve( m_tabItems->itemCount() ); - for (int i = 0; i < ui->itemOrderListPlugins->itemCount(); ++i) { - const QString loaderId = ui->itemOrderListPlugins->data(i).toString(); + for (int i = 0; i < m_tabItems->itemCount(); ++i) { + const QString loaderId = m_tabItems->data(i).toString(); Q_ASSERT(!loaderId.isEmpty()); pluginPriority.append(loaderId); settings.beginGroup(loaderId); - QWidget *w = ui->itemOrderListPlugins->widget(i); + QWidget *w = m_tabItems->widget(i); if (w) { PluginWidget *pluginWidget = qobject_cast(w); const auto &loader = pluginWidget->loader(); @@ -491,7 +537,7 @@ settings.setValue( it.key(), it.value() ); } - const bool isPluginEnabled = ui->itemOrderListPlugins->isItemChecked(i); + const bool isPluginEnabled = m_tabItems->isItemChecked(i); settings.setValue("enabled", isPluginEnabled); settings.endGroup(); @@ -502,13 +548,13 @@ if (!pluginPriority.isEmpty()) settings.setValue("plugin_priority", pluginPriority); - ui->configTabAppearance->setEditor( AppConfig().option() ); + m_tabAppearance->setEditor( AppConfig().option() ); setAutostartEnable(); // Language changes after restart. - const int newLocaleIndex = ui->comboBoxLanguage->currentIndex(); - const QString newLocaleName = ui->comboBoxLanguage->itemData(newLocaleIndex).toString(); + const int newLocaleIndex = m_tabGeneral->comboBoxLanguage->currentIndex(); + const QString newLocaleName = m_tabGeneral->comboBoxLanguage->itemData(newLocaleIndex).toString(); QString oldLocaleName = settings.value("Options/language").toString(); if (oldLocaleName.isEmpty()) oldLocaleName = "en"; @@ -534,10 +580,10 @@ void ConfigurationManager::onCheckBoxMenuTabIsCurrentStateChanged(int state) { - ui->comboBoxMenuTab->setEnabled(state == Qt::Unchecked); + m_tabTray->comboBoxMenuTab->setEnabled(state == Qt::Unchecked); } void ConfigurationManager::onSpinBoxTrayItemsValueChanged(int value) { - ui->checkBoxPasteMenuItem->setEnabled(value > 0); + m_tabTray->checkBoxPasteMenuItem->setEnabled(value > 0); } diff -Nru copyq-3.9.0/src/gui/configurationmanager.h copyq-3.9.1/src/gui/configurationmanager.h --- copyq-3.9.0/src/gui/configurationmanager.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/configurationmanager.h 2019-08-18 09:07:57.000000000 +0000 @@ -25,12 +25,23 @@ #include #include +#include + namespace Ui { + class ConfigTabGeneral; + class ConfigTabHistory; + class ConfigTabLayout; + class ConfigTabNotifications; + class ConfigTabTray; class ConfigurationManager; } +class ConfigTabAppearance; +class ConfigTabTabs; class ItemFactory; +class ItemOrderList; class Option; +class ShortcutsWidget; class QAbstractButton; class QCheckBox; class QComboBox; @@ -119,6 +130,17 @@ void updateTabComboBoxes(); Ui::ConfigurationManager *ui; + + ConfigTabAppearance *m_tabAppearance = nullptr; + ConfigTabTabs *m_tabTabs = nullptr; + ItemOrderList *m_tabItems = nullptr; + ShortcutsWidget *m_tabShortcuts = nullptr; + std::shared_ptr m_tabGeneral; + std::shared_ptr m_tabHistory; + std::shared_ptr m_tabLayout; + std::shared_ptr m_tabNotifications; + std::shared_ptr m_tabTray; + QHash m_options; }; diff -Nru copyq-3.9.0/src/gui/iconfactory.cpp copyq-3.9.1/src/gui/iconfactory.cpp --- copyq-3.9.0/src/gui/iconfactory.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/iconfactory.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -509,17 +509,31 @@ QPixmap doCreatePixmap(QSize size, QIcon::Mode, QIcon::State, QPainter *) override { + // If copyq-normal icon exist in theme, omit changing color. + const bool useColoredIcon = !hasNormalIcon(); + const auto sessionColor = useColoredIcon ? sessionIconColor() : QColor(); + + const auto cacheKey = QString("app:%1|%2|%3x%4") + .arg(m_iconType) + .arg(sessionColor.name()) + .arg(size.width()) + .arg(size.height()); + + { + QPixmap pixmap; + if ( QPixmapCache::find(cacheKey, &pixmap) ) + return pixmap; + } + const bool running = m_iconType == AppIconRunning; const auto suffix = running ? QLatin1String("-busy") : QLatin1String(""); auto pix = appPixmap(suffix, size); - // If copyq-normal icon exist in theme, omit changing color. - if ( !hasNormalIcon() ) { - const auto sessionColor = sessionIconColor(); - if ( sessionColor.isValid() ) - replaceColor(&pix, suffix, sessionColor); - } + if ( sessionColor.isValid() ) + replaceColor(&pix, suffix, sessionColor); + + QPixmapCache::insert(cacheKey, pix); return pix; } diff -Nru copyq-3.9.0/src/gui/itemorderlist.cpp copyq-3.9.1/src/gui/itemorderlist.cpp --- copyq-3.9.0/src/gui/itemorderlist.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/itemorderlist.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -67,9 +67,8 @@ ui->listWidgetItems->setFocus(); setCurrentItemWidget(nullptr); - // Make icon wider so icon tag can be bigger. - const auto size = iconFontSizePixels(); - ui->listWidgetItems->setIconSize( QSize(size * 3/2, size) ); + setEditable(false); + setItemsMovable(false); } ItemOrderList::~ItemOrderList() @@ -77,10 +76,21 @@ delete ui; } -void ItemOrderList::setAddRemoveButtonsVisible(bool visible) +void ItemOrderList::setEditable(bool editable) { - ui->pushButtonRemove->setVisible(visible); - ui->pushButtonAdd->setVisible(visible); + ui->pushButtonRemove->setVisible(editable); + ui->pushButtonAdd->setVisible(editable); + ui->listWidgetItems->setSelectionMode( + editable + ? QAbstractItemView::ExtendedSelection + : QAbstractItemView::SingleSelection); +} + +void ItemOrderList::setItemsMovable(bool movable) +{ + ui->pushButtonUp->setVisible(movable); + ui->pushButtonDown->setVisible(movable); + ui->listWidgetItems->setDragEnabled(movable); } void ItemOrderList::clearItems() @@ -111,13 +121,6 @@ const int row = targetRow >= 0 ? qMin(list->count(), targetRow) : list->count(); list->insertItem(row, listItem); - // Resize list to minimal size. - if ( !isVisible() ) { - const int w = list->sizeHintForColumn(0) - + list->verticalScrollBar()->sizeHint().width() + 4; - list->resize( w, list->height() ); - } - if ( list->currentItem() == nullptr ) list->setCurrentRow(row); } @@ -229,6 +232,30 @@ setAcceptDrops(m_dragAndDropRe.isValid()); } +void ItemOrderList::setWiderIconsEnabled(bool wider) +{ + const auto height = iconFontSizePixels(); + const auto width = wider ? height * 3/2 : height; + ui->listWidgetItems->setIconSize( QSize(width, height) ); +} + +void ItemOrderList::keyPressEvent(QKeyEvent *event) +{ + if ( event->matches(QKeySequence::NextChild) ) { + nextPreviousItem(1); + event->accept(); + return; + } + + if ( event->matches(QKeySequence::PreviousChild) ) { + nextPreviousItem(-1); + event->accept(); + return; + } + + QWidget::keyPressEvent(event); +} + void ItemOrderList::dragEnterEvent(QDragEnterEvent *event) { const QString text = event->mimeData()->text(); @@ -260,9 +287,28 @@ ui->pushButtonUp->setIcon( getIcon("go-up", IconArrowUp) ); } + // Resize list to minimal size. + QListWidget *list = ui->listWidgetItems; + const int w = list->sizeHintForColumn(0) + + list->verticalScrollBar()->sizeHint().width() + 4; + const auto sizes = ui->splitter->sizes(); + ui->splitter->setSizes({w, sizes[0] + sizes[1] - w}); + QWidget::showEvent(event); } +void ItemOrderList::nextPreviousItem(int d) +{ + QListWidget *list = ui->listWidgetItems; + const int rowCount = list->count(); + if (rowCount < 2) + return; + + const int row = list->currentRow(); + const int nextRow = (row + d + rowCount) % rowCount; + list->setCurrentRow(nextRow, QItemSelectionModel::ClearAndSelect); +} + void ItemOrderList::onPushButtonUpClicked() { QListWidget *list = ui->listWidgetItems; @@ -331,15 +377,14 @@ void ItemOrderList::setCurrentItemWidget(QWidget *widget) { - // Reparent current widget so it's safely deleted. - QWidget *currentWidget = ui->scrollArea->takeWidget(); - if (currentWidget != nullptr) { - currentWidget->setParent(this); - currentWidget->hide(); - } - - if (widget != nullptr) { - ui->scrollArea->setWidget(widget); + QLayoutItem *layoutItem = ui->widgetLayout->takeAt(0); + if (layoutItem) + layoutItem->widget()->hide(); + delete layoutItem; + + if (widget) { + ui->widgetLayout->addWidget(widget); + ui->widgetParent->setFocusProxy(widget); widget->show(); } } @@ -348,7 +393,7 @@ { ItemWidgetPair &pair = m_items[item]; if (!pair.widget) - pair.widget = pair.item->createWidget(ui->scrollArea); + pair.widget = pair.item->createWidget(this); return pair.widget; } diff -Nru copyq-3.9.0/src/gui/itemorderlist.h copyq-3.9.1/src/gui/itemorderlist.h --- copyq-3.9.0/src/gui/itemorderlist.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/itemorderlist.h 2019-08-18 09:07:57.000000000 +0000 @@ -63,7 +63,8 @@ explicit ItemOrderList(QWidget *parent = nullptr); ~ItemOrderList(); - void setAddRemoveButtonsVisible(bool visible); + void setEditable(bool editable); + void setItemsMovable(bool movable); void clearItems(); @@ -105,6 +106,9 @@ void setDragAndDropValidator(const QRegExp &re); + /// Make icons wider so icon tag can be bigger. + void setWiderIconsEnabled(bool wider); + signals: void addButtonClicked(); void itemSelectionChanged(); @@ -112,11 +116,14 @@ void itemCheckStateChanged(int row, bool checked); protected: + void keyPressEvent(QKeyEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void showEvent(QShowEvent *event) override; private: + void nextPreviousItem(int d); + void onPushButtonUpClicked(); void onPushButtonDownClicked(); void onPushButtonRemoveClicked(); diff -Nru copyq-3.9.0/src/gui/mainwindow.cpp copyq-3.9.1/src/gui/mainwindow.cpp --- copyq-3.9.0/src/gui/mainwindow.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/mainwindow.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -276,6 +276,17 @@ } } +void setHideInTaskBar(QWidget *window, bool hideInTaskBar) +{ + if (!window) + return; + + const Qt::WindowFlags flags = window->windowFlags(); + const bool hiddenInTaskBar = flags.testFlag(Qt::Tool); + if (hiddenInTaskBar != hideInTaskBar) + window->setWindowFlags(flags ^ Qt::Tool); +} + template Dialog *openDialog(QWidget *dialogParent) { @@ -350,8 +361,10 @@ void deleteSubMenus(QObject *parent) { - for (auto subMenu : parent->findChildren()) - delete subMenu; + for (auto subMenu : parent->findChildren()) { + if (subMenu->parent() == parent) + delete subMenu; + } } void clearActions(QMenu *menu) @@ -1867,6 +1880,7 @@ m_automaticCommands.clear(); m_menuCommands.clear(); m_scriptCommands.clear(); + m_trayMenuCommands.clear(); QVector displayCommands; @@ -2242,6 +2256,9 @@ m_options.hideMainWindow = appConfig.option(); m_options.closeOnUnfocus = appConfig.option(); + const bool hideInTaskBar = appConfig.option(); + setHideInTaskBar(this, hideInTaskBar); + // shared data for browsers m_sharedData->editor = appConfig.option(); m_sharedData->maxItems = appConfig.option(); diff -Nru copyq-3.9.0/src/gui/tabicons.cpp copyq-3.9.1/src/gui/tabicons.cpp --- copyq-3.9.0/src/gui/tabicons.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/gui/tabicons.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -47,6 +47,11 @@ return icons; } +QByteArray tabNameFromFileSuffix(QByteArray base64Suffix) +{ + return QByteArray::fromBase64(base64Suffix.replace('-', '/')); +} + } // namespace QStringList savedTabs() @@ -63,7 +68,7 @@ for (const auto &fileName : files) { if ( fileName.contains(re) ) { const QString tabName = - getTextData(QByteArray::fromBase64(re.cap(1).toUtf8())); + getTextData(tabNameFromFileSuffix(re.cap(1).toUtf8())); if ( !tabName.isEmpty() && !tabs.contains(tabName) ) tabs.append(tabName); } diff -Nru copyq-3.9.0/src/item/itemdelegate.cpp copyq-3.9.1/src/item/itemdelegate.cpp --- copyq-3.9.0/src/item/itemdelegate.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/item/itemdelegate.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -22,6 +22,7 @@ #include "common/client_server.h" #include "common/contenttype.h" #include "common/mimetypes.h" +#include "common/sanitize_text_document.h" #include "common/textdata.h" #include "gui/clipboardbrowser.h" #include "gui/iconfactory.h" @@ -213,10 +214,12 @@ connect(editor, &QObject::destroyed, editorParent, &QObject::deleteLater); - if (hasHtml) + if (hasHtml) { editor->setHtml(text); - else + sanitizeTextDocument(editor->document()); + } else { editor->setPlainText(text); + } editor->selectAll(); diff -Nru copyq-3.9.0/src/item/itemwidget.h copyq-3.9.1/src/item/itemwidget.h --- copyq-3.9.0/src/item/itemwidget.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/item/itemwidget.h 2019-08-18 09:07:57.000000000 +0000 @@ -49,7 +49,7 @@ class ItemScriptableFactoryInterface; using ItemScriptableFactoryPtr = std::shared_ptr; -#define COPYQ_PLUGIN_ITEM_LOADER_ID "com.github.hluk.copyq.itemloader/3.9.0" +#define COPYQ_PLUGIN_ITEM_LOADER_ID "com.github.hluk.copyq.itemloader/3.9.1" /** * Handles item in list. diff -Nru copyq-3.9.0/src/item/serialize.cpp copyq-3.9.1/src/item/serialize.cpp --- copyq-3.9.0/src/item/serialize.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/item/serialize.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -36,6 +35,17 @@ namespace { +template +bool readOrError(QDataStream *out, T *value, const char *error) +{ + *out >> *value; + if ( out->status() == QDataStream::Ok ) + return true; + + log( QString("Corrupted data: %1").arg(error), LogError ); + return false; +} + const std::unordered_map &idToMime() { static const std::unordered_map map({ @@ -60,13 +70,13 @@ QString decompressMime(QDataStream *out) { QString mime; - *out >> mime; - if ( out->status() != QDataStream::Ok ) + if ( !readOrError(out, &mime, "Failed to read MIME type") ) return QString(); bool ok; const int id = mime.midRef(0, 1).toInt(&ok, 16); if (!ok) { + log("Corrupted data: Failed to parse MIME type ID", LogError); out->setStatus(QDataStream::ReadCorruptData); return QString(); } @@ -78,6 +88,7 @@ if ( it != std::end(idToMime()) ) return it->second + mime.mid(1); + log("Corrupted data: Failed to decompress MIME type", LogError); out->setStatus(QDataStream::ReadCorruptData); return QString(); } @@ -97,22 +108,26 @@ bool deserializeDataV2(QDataStream *out, QVariantMap *data) { qint32 size; - *out >> size; + if ( !readOrError(out, &size, "Failed to read size (v2)") ) + return false; QByteArray tmpBytes; bool compress; - for (qint32 i = 0; i < size && out->status() == QDataStream::Ok; ++i) { + for (qint32 i = 0; i < size; ++i) { const QString mime = decompressMime(out); if ( out->status() != QDataStream::Ok ) return false; - *out >> compress >> tmpBytes; - if ( out->status() != QDataStream::Ok ) + if ( !readOrError(out, &compress, "Failed to read compression flag (v2)") ) + return false; + + if ( !readOrError(out, &tmpBytes, "Failed to read item data (v2)") ) return false; if (compress) { tmpBytes = qUncompress(tmpBytes); if ( tmpBytes.isEmpty() ) { + log("Corrupted data: Failed to decompress data (v2)", LogError); out->setStatus(QDataStream::ReadCorruptData); return false; } @@ -142,44 +157,50 @@ } } -void deserializeData(QDataStream *stream, QVariantMap *data) +bool deserializeData(QDataStream *stream, QVariantMap *data) { try { qint32 length; + if ( !readOrError(stream, &length, "Failed to read length") ) + return false; - *stream >> length; - if ( stream->status() != QDataStream::Ok ) - return; - - if (length == -2) { - deserializeDataV2(stream, data); - return; - } + if (length == -2) + return deserializeDataV2(stream, data); if (length < 0) { + log("Corrupted data: Invalid length (v1)", LogError); stream->setStatus(QDataStream::ReadCorruptData); - return; + return false; } // Deprecated format. // TODO: Data should be saved again in new format. QString mime; QByteArray tmpBytes; - for (qint32 i = 0; i < length && stream->status() == QDataStream::Ok; ++i) { - *stream >> mime >> tmpBytes; + for (qint32 i = 0; i < length; ++i) { + if ( !readOrError(stream, &mime, "Failed to read MIME type (v1)") ) + return false; + + if ( !readOrError(stream, &tmpBytes, "Failed to read item data (v1)") ) + return false; + if( !tmpBytes.isEmpty() ) { tmpBytes = qUncompress(tmpBytes); if ( tmpBytes.isEmpty() ) { + log("Corrupted data: Failed to decompress data (v1)", LogError); stream->setStatus(QDataStream::ReadCorruptData); - break; + return false; } } data->insert(mime, tmpBytes); } } catch (const std::exception &e) { - log( QObject::tr("Data deserialization failed: %1").arg(e.what()), LogError ); + log( QString("Data deserialization failed: %1").arg(e.what()), LogError ); stream->setStatus(QDataStream::ReadCorruptData); + return false; } + + return stream->status() == QDataStream::Ok; } QByteArray serializeData(const QVariantMap &data) @@ -193,8 +214,7 @@ bool deserializeData(QVariantMap *data, const QByteArray &bytes) { QDataStream out(bytes); - deserializeData(&out, data); - return out.status() == QDataStream::Ok; + return deserializeData(&out, data); } bool serializeData(const QAbstractItemModel &model, QDataStream *stream) @@ -211,12 +231,11 @@ bool deserializeData(QAbstractItemModel *model, QDataStream *stream, int maxItems) { qint32 length; - *stream >> length; - - if ( stream->status() != QDataStream::Ok ) + if ( !readOrError(stream, &length, "Failed to read length") ) return false; if (length < 0) { + log("Corrupted data: Invalid length", LogError); stream->setStatus(QDataStream::ReadCorruptData); return false; } @@ -227,10 +246,16 @@ if ( length != 0 && !model->insertRows(0, length) ) return false; - for(qint32 i = 0; i < length && stream->status() == QDataStream::Ok; ++i) { + for(qint32 i = 0; i < length; ++i) { QVariantMap data; - deserializeData(stream, &data); - model->setData( model->index(i, 0), data, contentType::data ); + if ( !deserializeData(stream, &data) ) + return false; + + if ( !model->setData(model->index(i, 0), data, contentType::data) ) { + log("Failed to set model data", LogError); + stream->setStatus(QDataStream::ReadCorruptData); + return false; + } } return stream->status() == QDataStream::Ok; diff -Nru copyq-3.9.0/src/item/serialize.h copyq-3.9.1/src/item/serialize.h --- copyq-3.9.0/src/item/serialize.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/item/serialize.h 2019-08-18 09:07:57.000000000 +0000 @@ -28,7 +28,7 @@ class QIODevice; void serializeData(QDataStream *stream, const QVariantMap &data); -void deserializeData(QDataStream *stream, QVariantMap *data); +bool deserializeData(QDataStream *stream, QVariantMap *data); QByteArray serializeData(const QVariantMap &data); bool deserializeData(QVariantMap *data, const QByteArray &bytes); diff -Nru copyq-3.9.0/src/main.cpp copyq-3.9.1/src/main.cpp --- copyq-3.9.0/src/main.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/main.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -159,6 +159,12 @@ arg == "info"; } +bool needsLogs(const QString &arg) +{ + return arg == "--logs" || + arg == "logs"; +} + #ifdef HAS_TESTS bool needsTests(const QString &arg) { @@ -224,6 +230,9 @@ if ( needsInfo(arg) ) return evaluate( "info", arguments.mid(skipArguments + 1), argc, argv, sessionName ); + if ( needsLogs(arg) ) + return evaluate( "logs", arguments.mid(skipArguments + 1), argc, argv, sessionName ); + #ifdef HAS_TESTS if ( needsTests(arg) ) { // Skip the "tests" argument and pass the rest to tests. diff -Nru copyq-3.9.0/src/platform/dummy/dummyplatform.cpp copyq-3.9.1/src/platform/dummy/dummyplatform.cpp --- copyq-3.9.0/src/platform/dummy/dummyplatform.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/dummy/dummyplatform.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -46,12 +46,12 @@ QGuiApplication *DummyPlatform::createMonitorApplication(int &argc, char **argv) { - return new ApplicationExceptionHandler(argc, argv); + return new ApplicationExceptionHandler(argc, argv); } QGuiApplication *DummyPlatform::createClipboardProviderApplication(int &argc, char **argv) { - return new ApplicationExceptionHandler(argc, argv); + return new ApplicationExceptionHandler(argc, argv); } QCoreApplication *DummyPlatform::createClientApplication(int &argc, char **argv) @@ -59,6 +59,11 @@ return new ApplicationExceptionHandler(argc, argv); } +QGuiApplication *DummyPlatform::createTestApplication(int &argc, char **argv) +{ + return new ApplicationExceptionHandler(argc, argv); +} + PlatformClipboardPtr DummyPlatform::clipboard() { return PlatformClipboardPtr(new DummyClipboard()); diff -Nru copyq-3.9.0/src/platform/dummy/dummyplatform.h copyq-3.9.1/src/platform/dummy/dummyplatform.h --- copyq-3.9.0/src/platform/dummy/dummyplatform.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/dummy/dummyplatform.h 2019-08-18 09:07:57.000000000 +0000 @@ -50,6 +50,8 @@ QCoreApplication *createClientApplication(int &argc, char **argv) override; + QGuiApplication *createTestApplication(int &argc, char **argv) override; + void loadSettings() override {} PlatformClipboardPtr clipboard() override; diff -Nru copyq-3.9.0/src/platform/mac/macclipboard.h copyq-3.9.1/src/platform/mac/macclipboard.h --- copyq-3.9.0/src/platform/mac/macclipboard.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/mac/macclipboard.h 2019-08-18 09:07:57.000000000 +0000 @@ -28,6 +28,8 @@ void setData(ClipboardMode mode, const QVariantMap &dataMap) override; + QByteArray clipboardOwner() override; + private: void clipboardTimeout(); diff -Nru copyq-3.9.0/src/platform/mac/macclipboard.mm copyq-3.9.1/src/platform/mac/macclipboard.mm --- copyq-3.9.0/src/platform/mac/macclipboard.mm 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/mac/macclipboard.mm 2019-08-18 09:07:57.000000000 +0000 @@ -22,6 +22,8 @@ #include "common/common.h" #include "common/mimetypes.h" #include "common/textdata.h" +#include "platform/platformnativeinterface.h" +#include "platform/platformwindow.h" #include #include @@ -61,6 +63,13 @@ return DummyClipboard::setData(mode, dataMapForMac); } +QByteArray MacClipboard::clipboardOwner() +{ + PlatformWindowPtr currentWindow = platformNativeInterface()->getCurrentWindow(); + if (currentWindow) + return currentWindow->getTitle().toUtf8(); + return QByteArray(); +} void MacClipboard::clipboardTimeout() { NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; diff -Nru copyq-3.9.0/src/platform/mac/macplatform.h copyq-3.9.1/src/platform/mac/macplatform.h --- copyq-3.9.0/src/platform/mac/macplatform.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/mac/macplatform.h 2019-08-18 09:07:57.000000000 +0000 @@ -48,6 +48,8 @@ QCoreApplication *createClientApplication(int &argc, char **argv) override; + QGuiApplication *createTestApplication(int &argc, char **argv) override; + void loadSettings() override {} PlatformClipboardPtr clipboard() override; diff -Nru copyq-3.9.0/src/platform/mac/macplatform.mm copyq-3.9.1/src/platform/mac/macplatform.mm --- copyq-3.9.0/src/platform/mac/macplatform.mm 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/mac/macplatform.mm 2019-08-18 09:07:57.000000000 +0000 @@ -201,6 +201,11 @@ return new Activity(argc, argv, MacActivity::User, "CopyQ Client"); } +QGuiApplication *MacPlatform::createTestApplication(int &argc, char **argv) +{ + return new Activity(argc, argv, MacActivity::Background, "CopyQ Tests"); +} + PlatformClipboardPtr MacPlatform::clipboard() { return PlatformClipboardPtr(new MacClipboard()); diff -Nru copyq-3.9.0/src/platform/platformnativeinterface.h copyq-3.9.1/src/platform/platformnativeinterface.h --- copyq-3.9.0/src/platform/platformnativeinterface.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/platformnativeinterface.h 2019-08-18 09:07:57.000000000 +0000 @@ -100,6 +100,10 @@ * Create QCoreApplication object for client. */ virtual QCoreApplication *createClientApplication(int &argc, char **argv) = 0; + /** + * Create QGuiApplication object for tests. + */ + virtual QGuiApplication *createTestApplication(int &argc, char **argv) = 0; /** * Modify settings (QSettings) before it's first used. diff -Nru copyq-3.9.0/src/platform/win/winplatform.cpp copyq-3.9.1/src/platform/win/winplatform.cpp --- copyq-3.9.0/src/platform/win/winplatform.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/win/winplatform.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -302,6 +302,11 @@ return createApplication(argc, argv); } +QGuiApplication *WinPlatform::createTestApplication(int &argc, char **argv) +{ + return createApplication(argc, argv); +} + void WinPlatform::loadSettings() { migrateConfigToAppDir(); diff -Nru copyq-3.9.0/src/platform/win/winplatform.h copyq-3.9.1/src/platform/win/winplatform.h --- copyq-3.9.0/src/platform/win/winplatform.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/win/winplatform.h 2019-08-18 09:07:57.000000000 +0000 @@ -50,6 +50,8 @@ QCoreApplication *createClientApplication(int &argc, char **argv) override; + QGuiApplication *createTestApplication(int &argc, char **argv) override; + void loadSettings() override; PlatformClipboardPtr clipboard() override; diff -Nru copyq-3.9.0/src/platform/x11/x11platformclipboard.cpp copyq-3.9.1/src/platform/x11/x11platformclipboard.cpp --- copyq-3.9.0/src/platform/x11/x11platformclipboard.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/x11/x11platformclipboard.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -88,13 +88,6 @@ } ); initSingleShotTimer( &m_selectionData.timerEmitChange, 0, this, [this](){ - if ( isSelectionIncomplete() ) { - COPYQ_LOG("Selection is incomplete"); - if ( !m_timerCheckAgain.isActive() ) - m_timerCheckAgain.start(minCheckAgainIntervalMs); - return; - } - useNewClipboardData(&m_selectionData); } ); @@ -129,8 +122,6 @@ if (!clipboardData.enabled) return; - clipboardData.changed = true; - // Store the current window title right after the clipboard/selection changes. // This makes sure that the title points to the correct clipboard/selection // owner most of the times. @@ -142,40 +133,57 @@ clipboardData.newOwner = currentWindowTitle; } - // Omit checking selection too fast. - if ( mode == QClipboard::Selection && m_timerCheckAgain.isActive() ) { - COPYQ_LOG("Postponing fast selection change"); - m_selectionData.timerEmitChange.stop(); - return; + if (mode == QClipboard::Selection) { + // Omit checking selection too fast. + if ( mode == QClipboard::Selection && m_timerCheckAgain.isActive() ) { + COPYQ_LOG("Postponing fast selection change"); + return; + } + + if ( isSelectionIncomplete() ) { + COPYQ_LOG("Selection is incomplete"); + if ( !m_timerCheckAgain.isActive() + || minCheckAgainIntervalMs < m_timerCheckAgain.remainingTime() ) + { + m_timerCheckAgain.start(minCheckAgainIntervalMs); + } + return; + } } + updateClipboardData(&clipboardData); + checkAgainLater(true, 0); } void X11PlatformClipboard::check() { - m_clipboardData.timerEmitChange.stop(); - m_selectionData.timerEmitChange.stop(); m_timerCheckAgain.stop(); - const auto changed = - // Prioritize checking clipboard before selection. - updateClipboardData(&m_clipboardData) - || updateClipboardData(&m_selectionData); + updateClipboardData(&m_clipboardData); + updateClipboardData(&m_selectionData); if ( m_timerCheckAgain.isActive() ) return; + const bool changed = m_clipboardData.timerEmitChange.isActive() + || m_selectionData.timerEmitChange.isActive(); + // Check clipboard and selection again if some signals where // not delivered or older data was received after new one. const int interval = m_timerCheckAgain.interval() * 2 + minCheckAgainIntervalMs; checkAgainLater(changed, interval); } -bool X11PlatformClipboard::updateClipboardData(X11PlatformClipboard::ClipboardData *clipboardData) +void X11PlatformClipboard::updateClipboardData(X11PlatformClipboard::ClipboardData *clipboardData) { if (!clipboardData->enabled) - return false; + return; + + if ( isSelectionIncomplete() ) { + m_timerCheckAgain.start(minCheckAgainIntervalMs); + return; + } const auto data = ::clipboardData(clipboardData->mode); @@ -191,32 +199,28 @@ .arg(clipboardData->retry) .arg(maxRetryCount), LogWarning ); - return false; + return; } clipboardData->retry = 0; const auto newDataTimestamp = data->data(QLatin1String("TIMESTAMP")); - if ( newDataTimestamp.isEmpty() || clipboardData->newDataTimestamp != newDataTimestamp ) { - clipboardData->newDataTimestamp = newDataTimestamp; - clipboardData->newData = cloneData(*data, clipboardData->formats); - } - - if (!clipboardData->changed) { - if (clipboardData->data == clipboardData->newData) - return false; + // In case there is a timestamp, omit update if it did not change. + if ( !newDataTimestamp.isEmpty() && clipboardData->newDataTimestamp == newDataTimestamp ) + return; - clipboardData->changed = true; - } + clipboardData->newDataTimestamp = newDataTimestamp; + clipboardData->newData = cloneData(*data, clipboardData->formats); + // In case there is no timestamp, update only if the data changed. + if ( newDataTimestamp.isEmpty() && clipboardData->data == clipboardData->newData ) + return; clipboardData->timerEmitChange.start(); - return true; } void X11PlatformClipboard::useNewClipboardData(X11PlatformClipboard::ClipboardData *clipboardData) { clipboardData->data = clipboardData->newData; clipboardData->owner = clipboardData->newOwner; - clipboardData->changed = false; clipboardData->timerEmitChange.stop(); emit changed(clipboardData->mode); } @@ -232,10 +236,10 @@ m_timerCheckAgain.setInterval(0); COPYQ_LOG( QString("Clipboard %1, selection %2.%3") - .arg(m_clipboardData.data == m_clipboardData.newData ? "unchanged" : "*CHANGED*") - .arg(m_selectionData.data == m_selectionData.newData ? "unchanged" : "*CHANGED*") + .arg(m_clipboardData.timerEmitChange.isActive() ? "*CHANGED*" : "unchanged") + .arg(m_selectionData.timerEmitChange.isActive() ? "*CHANGED*" : "unchanged") .arg(m_timerCheckAgain.isActive() - ? QString(" Test clipboard in %1ms.").arg(m_timerCheckAgain.interval()) + ? QString(" Test again in %1ms.").arg(m_timerCheckAgain.interval()) : QString()) ); } diff -Nru copyq-3.9.0/src/platform/x11/x11platformclipboard.h copyq-3.9.1/src/platform/x11/x11platformclipboard.h --- copyq-3.9.0/src/platform/x11/x11platformclipboard.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/x11/x11platformclipboard.h 2019-08-18 09:07:57.000000000 +0000 @@ -55,12 +55,11 @@ QByteArray newDataTimestamp; ClipboardMode mode; bool enabled = true; - bool changed = false; int retry = 0; }; void check(); - bool updateClipboardData(ClipboardData *clipboardData); + void updateClipboardData(ClipboardData *clipboardData); void useNewClipboardData(ClipboardData *clipboardData); void checkAgainLater(bool clipboardChanged, int interval); diff -Nru copyq-3.9.0/src/platform/x11/x11platform.cpp copyq-3.9.1/src/platform/x11/x11platform.cpp --- copyq-3.9.0/src/platform/x11/x11platform.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/x11/x11platform.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -262,6 +262,11 @@ return new ApplicationExceptionHandler(argc, argv); } +QGuiApplication *X11Platform::createTestApplication(int &argc, char **argv) +{ + return new ApplicationExceptionHandler(argc, argv); +} + PlatformClipboardPtr X11Platform::clipboard() { return PlatformClipboardPtr(new X11PlatformClipboard()); @@ -314,3 +319,16 @@ { return QString(); } + +void sendDummyX11Event() +{ + if (!QX11Info::isPlatformX11()) + return; + + auto display = QX11Info::display(); + auto black = BlackPixel(display, 0); + Window window = XCreateSimpleWindow( + display, RootWindow(display, 0), -100000, -100000, 1, 1, 0, black, black); + XDestroyWindow(display, window); + XFlush(display); +} diff -Nru copyq-3.9.0/src/platform/x11/x11platform.h copyq-3.9.1/src/platform/x11/x11platform.h --- copyq-3.9.0/src/platform/x11/x11platform.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/platform/x11/x11platform.h 2019-08-18 09:07:57.000000000 +0000 @@ -64,6 +64,8 @@ QCoreApplication *createClientApplication(int &argc, char **argv) override; + QGuiApplication *createTestApplication(int &argc, char **argv) override; + void loadSettings() override {} PlatformClipboardPtr clipboard() override; @@ -81,4 +83,6 @@ QString themePrefix() override { return QString(); } }; +void sendDummyX11Event(); + #endif // X11PLATFORM_H diff -Nru copyq-3.9.0/src/scriptable/scriptable.cpp copyq-3.9.1/src/scriptable/scriptable.cpp --- copyq-3.9.0/src/scriptable/scriptable.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/scriptable/scriptable.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -492,28 +493,6 @@ return newData; } -QStringList monitorFormatsToSave(const Commands &&automaticCommands) -{ - ItemFactory factory; - factory.loadPlugins(); - - QSettings settings; - factory.loadItemFactorySettings(&settings); - - QStringList formats = factory.formatsToSave(); - COPYQ_LOG( "Clipboard formats to save: " + formats.join(", ") ); - - for (const auto &command : automaticCommands) { - if ( !command.input.isEmpty() && !formats.contains(command.input) ) { - COPYQ_LOG( QString("Clipboard format to save for command \"%1\": %2") - .arg(command.name, command.input) ); - formats.append(command.input); - } - } - - return formats; -} - QScriptValue checksumForArgument(Scriptable *scriptable, QCryptographicHash::Algorithm method) { const auto data = scriptable->makeByteArray(scriptable->argument(0)); @@ -960,7 +939,7 @@ { m_skipArguments = 1; const QString &mime = arg(0, mimeText); - return newByteArray( m_proxy->getClipboardData(mime) ); + return newByteArray( getClipboardData(mime) ); } QScriptValue Scriptable::selection() @@ -968,7 +947,7 @@ m_skipArguments = 1; #ifdef HAS_MOUSE_SELECTIONS const QString &mime = arg(0, mimeText); - return newByteArray( m_proxy->getClipboardData(mime, ClipboardMode::Selection) ); + return newByteArray( getClipboardData(mime, ClipboardMode::Selection) ); #else return QScriptValue(); #endif @@ -978,7 +957,7 @@ { m_skipArguments = 1; const QString &mime = arg(0); - return m_proxy->hasClipboardFormat(mime, ClipboardMode::Clipboard); + return hasClipboardFormat(mime, ClipboardMode::Clipboard); } QScriptValue Scriptable::hasSelectionFormat() @@ -986,7 +965,7 @@ m_skipArguments = 1; #ifdef HAS_MOUSE_SELECTIONS const QString &mime = arg(0); - return m_proxy->hasClipboardFormat(mime, ClipboardMode::Selection); + return hasClipboardFormat(mime, ClipboardMode::Selection); #else return false; #endif @@ -1146,7 +1125,7 @@ text.append(m_inputSeparator); if ( toInt(value, &row) ) { const QByteArray bytes = row >= 0 ? m_proxy->browserItemData(m_tabName, row, mimeText) - : m_proxy->getClipboardData(mimeText); + : getClipboardData(mimeText); text.append( getTextData(bytes) ); } else { text.append( toString(value, this) ); @@ -1183,14 +1162,14 @@ result.append( m_inputSeparator.toUtf8() ); used = true; result.append( row >= 0 ? m_proxy->browserItemData(m_tabName, row, mime) - : m_proxy->getClipboardData(mime) ); + : getClipboardData(mime) ); } else { mime = toString(value, this); } } if (!used) - result.append( m_proxy->getClipboardData(mime) ); + result.append( getClipboardData(mime) ); return newByteArray(result); } @@ -1235,7 +1214,7 @@ m_skipArguments = i + 2; if (!anyRows) { - text = getTextData( m_proxy->getClipboardData(mimeText) ); + text = getTextData( getClipboardData(mimeText) ); } const QVariantMap data = createDataMap(mimeText, text); @@ -1732,6 +1711,12 @@ m_proxy->serverLog(arg(0)); } +QScriptValue Scriptable::logs() +{ + m_skipArguments = 0; + return readLogFile(50 * 1024 * 1024); +} + void Scriptable::setCurrentTab() { m_skipArguments = 1; @@ -2414,7 +2399,8 @@ if (!verifyClipboardAccess()) return; - ClipboardMonitor monitor( monitorFormatsToSave(m_proxy->automaticCommands()) ); + ClipboardMonitor monitor( + fromScriptValue(eval("clipboardFormatsToSave()"), this) ); QEventLoop loop; connect(this, &Scriptable::finished, &loop, &QEventLoop::quit); @@ -2437,6 +2423,28 @@ provideClipboard(ClipboardMode::Selection); } +QScriptValue Scriptable::clipboardFormatsToSave() +{ + ItemFactory factory; + factory.loadPlugins(); + + QSettings settings; + factory.loadItemFactorySettings(&settings); + + QStringList formats = factory.formatsToSave(); + COPYQ_LOG( "Clipboard formats to save: " + formats.join(", ") ); + + for (const auto &command : m_proxy->automaticCommands()) { + if ( !command.input.isEmpty() && !formats.contains(command.input) ) { + COPYQ_LOG( QString("Clipboard format to save for command \"%1\": %2") + .arg(command.name, command.input) ); + formats.append(command.input); + } + } + + return toScriptValue(formats, this); +} + void Scriptable::onExecuteOutput(const QByteArray &output) { m_executeStdoutData.append(output); @@ -2480,8 +2488,8 @@ auto data = createDataMap(mimeText, text); data[COPYQ_MIME_PREFIX "target-text-hash"] = QByteArray::number(targetTextHash); const auto command = sourceMode == ClipboardMode::Clipboard - ? "copyq synchronizeToSelection" - : "copyq synchronizeFromSelection"; + ? "copyq --clipboard-access synchronizeToSelection" + : "copyq --clipboard-access synchronizeFromSelection"; m_proxy->runInternalAction(data, command); #else Q_UNUSED(text); @@ -2544,6 +2552,9 @@ QScriptValue result; int skipArguments = 0; + if (!fnArgs.isEmpty() && toString(fnArgs[0], this) == "--clipboard-access") + ++skipArguments; + while ( skipArguments < fnArgs.size() && canContinue() && !m_engine->hasUncaughtException() ) { if ( result.isFunction() ) { m_skipArguments = -1; @@ -2675,7 +2686,7 @@ // Wait for clipboard to be set. for (int i = 0; i < 10; ++i) { - if ( m_proxy->getClipboardData(mime) != value ) + if ( getClipboardData(mime) != value ) return true; waitFor(5 + i * 25); } @@ -2885,11 +2896,6 @@ action->setWorkingDirectory( m_dirClass->getCurrentPath() ); action->start(); - if ( !action->waitForStarted(5000) ) { - action->terminate(); - return false; - } - while ( !action->waitForFinished(5000) && canContinue() ) {} if ( action->isRunning() && !action->waitForFinished(5000) ) { @@ -3005,9 +3011,14 @@ return runAction(&action) && action.exitCode() == 0; } +bool Scriptable::canAccessClipboard() const +{ + return qobject_cast(qApp); +} + bool Scriptable::verifyClipboardAccess() { - if ( qobject_cast(qApp) != nullptr ) + if ( canAccessClipboard() ) return true; throwError("Cannot access system clipboard with QCoreApplication"); @@ -3129,6 +3140,38 @@ } } +QByteArray Scriptable::getClipboardData(const QString &mime, ClipboardMode mode) +{ + if ( !canAccessClipboard() ) { + const auto command = QString("copyq --clipboard-access %1 %2") + .arg(mode == ClipboardMode::Clipboard ? "clipboard" : "selection") + .arg(mime); + return m_proxy->tryGetCommandOutput(command); + } + + const QMimeData *data = clipboardData(mode); + if (!data) + return QByteArray(); + + if (mime == "?") + return data->formats().join("\n").toUtf8() + '\n'; + + return cloneData(*data, QStringList(mime)).value(mime).toByteArray(); +} + +bool Scriptable::hasClipboardFormat(const QString &mime, ClipboardMode mode) +{ + if ( !canAccessClipboard() ) { + const auto command = QString("copyq --clipboard-access %1 %2") + .arg(mode == ClipboardMode::Clipboard ? "hasClipboardFormat" : "hasSelectionFormat") + .arg(mime); + return m_proxy->tryGetCommandOutput(command).startsWith("true"); + } + + const QMimeData *data = clipboardData(mode); + return data && data->hasFormat(mime); +} + void Scriptable::synchronizeSelection(ClipboardMode targetMode) { #ifdef HAS_MOUSE_SELECTIONS diff -Nru copyq-3.9.0/src/scriptable/scriptable.h copyq-3.9.1/src/scriptable/scriptable.h --- copyq-3.9.0/src/scriptable/scriptable.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/scriptable/scriptable.h 2019-08-18 09:07:57.000000000 +0000 @@ -255,6 +255,7 @@ void keys(); QScriptValue testSelected(); void serverLog(); + QScriptValue logs(); void setCurrentTab(); @@ -370,6 +371,8 @@ void provideClipboard(); void provideSelection(); + QScriptValue clipboardFormatsToSave(); + signals: void finished(); @@ -395,6 +398,7 @@ bool runCommands(CommandType::CommandType type); bool canExecuteCommand(const Command &command); bool canExecuteCommandFilter(const QString &matchCommand); + bool canAccessClipboard() const; bool verifyClipboardAccess(); void provideClipboard(ClipboardMode mode); @@ -409,6 +413,9 @@ void getActionData(); void setActionData(); + QByteArray getClipboardData(const QString &mime, ClipboardMode mode = ClipboardMode::Clipboard); + bool hasClipboardFormat(const QString &mime, ClipboardMode mode = ClipboardMode::Clipboard); + void synchronizeSelection(ClipboardMode targetMode); void saveData(const QString &tab); diff -Nru copyq-3.9.0/src/scriptable/scriptableproxy.cpp copyq-3.9.1/src/scriptable/scriptableproxy.cpp --- copyq-3.9.0/src/scriptable/scriptableproxy.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/scriptable/scriptableproxy.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -66,7 +66,6 @@ #include #include #include -#include #include #include #include @@ -1170,6 +1169,38 @@ m_wnd->runInternalAction(action); } +QByteArray ScriptableProxy::tryGetCommandOutput(const QString &command) +{ + INVOKE_NO_SNIP(tryGetCommandOutput, (command)); + + for (int i = 0; i < 3; ++i) { + Action action; + action.setCommand(command); + action.setReadOutput(true); + + QByteArray output; + connect( &action, &Action::actionOutput, + this, [&output](const QByteArray &actionOutput) { + output.append(actionOutput); + } ); + + action.start(); + if ( !action.waitForFinished(5000) ) { + if ( output.isEmpty() || !action.waitForFinished(30000) ) { + action.terminate(); + continue; + } + } + + if ( action.actionFailed() || action.exitCode() != 0 ) + continue; + + return output; + } + + return QByteArray(); +} + void ScriptableProxy::showMessage(const QString &title, const QString &msg, const QString &icon, @@ -1352,26 +1383,6 @@ return m_wnd->config(nameValue).toMap().constBegin().value(); } -QByteArray ScriptableProxy::getClipboardData(const QString &mime, ClipboardMode mode) -{ - INVOKE(getClipboardData, (mime, mode)); - const QMimeData *data = clipboardData(mode); - if (!data) - return QByteArray(); - - if (mime == "?") - return data->formats().join("\n").toUtf8() + '\n'; - - return cloneData(*data, QStringList(mime)).value(mime).toByteArray(); -} - -bool ScriptableProxy::hasClipboardFormat(const QString &mime, ClipboardMode mode) -{ - INVOKE(hasClipboardFormat, (mime, mode)); - const QMimeData *data = clipboardData(mode); - return data && data->hasFormat(mime); -} - int ScriptableProxy::browserLength(const QString &tabName) { INVOKE_NO_SNIP(browserLength, (tabName)); diff -Nru copyq-3.9.0/src/scriptable/scriptableproxy.h copyq-3.9.1/src/scriptable/scriptableproxy.h --- copyq-3.9.0/src/scriptable/scriptableproxy.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/scriptable/scriptableproxy.h 2019-08-18 09:07:57.000000000 +0000 @@ -125,6 +125,7 @@ void action(const QVariantMap &arg1, const Command &arg2); void runInternalAction(const QVariantMap &data, const QString &command); + QByteArray tryGetCommandOutput(const QString &command); void showMessage(const QString &title, const QString &msg, @@ -158,9 +159,6 @@ QVariant config(const QStringList &nameValue); QVariant toggleConfig(const QString &optionName); - QByteArray getClipboardData(const QString &mime, ClipboardMode mode = ClipboardMode::Clipboard); - bool hasClipboardFormat(const QString &mime, ClipboardMode mode = ClipboardMode::Clipboard); - int browserLength(const QString &tabName); bool browserOpenEditor(const QString &tabName, const QByteArray &arg1, bool changeClipboard); diff -Nru copyq-3.9.0/src/tests/testinterface.h copyq-3.9.1/src/tests/testinterface.h --- copyq-3.9.0/src/tests/testinterface.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/tests/testinterface.h 2019-08-18 09:07:57.000000000 +0000 @@ -73,12 +73,18 @@ /// Run client with given @a arguments and read output, check stderr and exit code. virtual QByteArray getClientOutput(const QStringList &arguments, QByteArray *stdoutActual) = 0; + /// Waits on client output. + virtual QByteArray waitOnOutput(const QStringList &arguments, const QByteArray &stdoutExpected) = 0; + /// Set clipboard through monitor process. virtual QByteArray setClipboard( const QByteArray &bytes, const QString &mime = QString("text/plain"), ClipboardMode mode = ClipboardMode::Clipboard) = 0; + /// Verify clipboard content. + virtual QByteArray verifyClipboard(const QByteArray &data, const QString &mime, bool exact = true) = 0; + /** * Return errors/warning from server (otherwise empty output). * If @a readAll is set, read all stderr. diff -Nru copyq-3.9.0/src/tests/tests.cpp copyq-3.9.1/src/tests/tests.cpp --- copyq-3.9.0/src/tests/tests.cpp 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/tests/tests.cpp 2019-08-18 09:07:57.000000000 +0000 @@ -24,6 +24,7 @@ #include "common/client_server.h" #include "common/common.h" #include "common/config.h" +#include "common/log.h" #include "common/mimetypes.h" #include "common/settings.h" #include "common/shortcuts.h" @@ -32,7 +33,6 @@ #include "item/itemfactory.h" #include "item/itemwidget.h" #include "item/serialize.h" -#include "gui/configtabshortcuts.h" #include "gui/tabicons.h" #include "platform/platformnativeinterface.h" @@ -66,6 +66,8 @@ namespace { +const int maxReadLogSize = 1 * 1024 * 1024; + const auto clipboardTabName = "CLIPBOARD"; const auto defaultSessionColor = "#ff8800"; const auto defaultTagColor = "#000000"; @@ -105,10 +107,16 @@ bool testStderr(const QByteArray &stderrData, TestInterface::ReadStderrFlag flag = TestInterface::ReadErrors) { - const QRegExp re("Warning:|ERROR:|ASSERT", Qt::CaseInsensitive); + static const QRegExp reFailure("Warning:|ERROR:|ASSERT", Qt::CaseInsensitive); + static const QRegExp reClient( + R"(CopyQ Note \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] data(mime) : QByteArray(); } -QByteArray waitUntilClipboardSet(const QByteArray &data, const QString &mime = QString("text/plain"), bool exact = true) -{ - PerformanceTimer perf; - - SleepTimer t(waitMsSetClipboard * 5); - do { - if ( exact ? getClipboard(mime) == data : getClipboard(mime).contains(data) ) { - perf.printPerformance("waitUntilClipboardSet", QStringList() << QString::fromUtf8(data) << mime); - waitFor(waitMsSetClipboard); - return data; - } - } while (t.sleep()); - - waitFor(waitMsSetClipboard); - return getClipboard(mime); -} - bool waitWhileFileExists(const QFile &file) { SleepTimer t(2000); @@ -200,16 +191,11 @@ if ( isServerRunning() ) return "Server is already running."; - m_server.reset(new QProcess); - m_serverErrorOutput.clear(); - QObject::connect( - m_server.get(), &QProcess::readyReadStandardError, - [this]() { - const auto errorOutput = m_server->readAllStandardError().replace('\r', ""); - m_serverErrorOutput.append(errorOutput); - }); + if ( !removeLogFiles() ) + return "Failed to remove log files"; - if ( !startTestProcess(m_server.get(), QStringList(), QIODevice::ReadOnly) ) { + m_server.reset(new QProcess); + if ( !startTestProcess(m_server.get(), QStringList(), QIODevice::NotOpen) ) { return QString("Failed to launch \"%1\": %2") .arg(QCoreApplication::applicationFilePath()) .arg(m_server->errorString()) @@ -218,7 +204,7 @@ m_server->closeReadChannel(QProcess::StandardOutput); - RETURN_ON_ERROR( readServerErrors(), "Failed to read server errors" ); + RETURN_ON_ERROR( readServerErrors(), "Failed to start server" ); return waitForServerToStart(); } @@ -351,7 +337,7 @@ return printClienAndServerStderr(stderrActual, exitCode); if (stdoutActual != stdoutExpected) { - return "Test failed: " + return "Test failed:" + decorateOutput("Unexpected output", stdoutActual) + decorateOutput("Expected output", stdoutExpected) + printClienAndServerStderr(stderrActual, exitCode); @@ -413,21 +399,38 @@ QGuiApplication::clipboard()->setMimeData(mimeData, qmode); waitFor(waitMsSetClipboard); + return verifyClipboard(bytes, mime); + } - waitUntilClipboardSet(bytes, mime); - RETURN_ON_ERROR( - testClipboard(bytes, mime), - "Failed to set " + QByteArray(mode == ClipboardMode::Clipboard ? "clipboard" : "selection") ); + QByteArray verifyClipboard(const QByteArray &data, const QString &mime, bool exact = true) override + { + PerformanceTimer perf; - return QByteArray(); + SleepTimer t(waitMsSetClipboard * 5); + do { + if ( exact ? getClipboard(mime) == data : getClipboard(mime).contains(data) ) { + perf.printPerformance("verifyClipboard", QStringList() << QString::fromUtf8(data) << mime); + waitFor(waitMsSetClipboard); + RETURN_ON_ERROR( readServerErrors(), "Failed to set or test clipboard content" ); + return QByteArray(); + } + } while (t.sleep()); + + const QByteArray actualBytes = getClipboard(mime); + return QString("Unexpected clipboard data for MIME \"%1\":") + .arg(mime).toUtf8() + + decorateOutput("Unexpected content", actualBytes) + + decorateOutput("Expected content", data) + + readServerErrors(ReadAllStderr); } QByteArray readServerErrors(ReadStderrFlag flag = ReadErrors) override { if (m_server) { QCoreApplication::processEvents(); - if ( flag == ReadAllStderr || !testStderr(m_serverErrorOutput, flag) ) - return decorateOutput("Server STDERR", m_serverErrorOutput); + const QByteArray output = readLogFile(maxReadLogSize).toUtf8(); + if ( flag == ReadAllStderr || !testStderr(output, flag) ) + return decorateOutput("Server STDERR", output); } return QByteArray(); @@ -442,11 +445,31 @@ if ( !testStderr(stderrActual) || exitCode != 0 ) return printClienAndServerStderr(stderrActual, exitCode); - RETURN_ON_ERROR( readServerErrors(), "Failed to read server errors" ); + RETURN_ON_ERROR( readServerErrors(), "Failed gettting client output" ); return ""; } + QByteArray waitOnOutput(const QStringList &arguments, const QByteArray &stdoutExpected) override + { + PerformanceTimer perf; + QByteArray stdoutActual; + + SleepTimer t_(8000); + do { + RETURN_ON_ERROR( getClientOutput(arguments, &stdoutActual), "Failed to wait on client output" ); + } while (stdoutActual != stdoutExpected && t_.sleep()); + + if (stdoutActual == stdoutExpected) + return QByteArray(); + + return QString("Unexpected output for command \"%1\":") + .arg(arguments.join(' ')).toUtf8() + + decorateOutput("Unexpected content", stdoutActual) + + decorateOutput("Expected content", stdoutExpected) + + readServerErrors(ReadAllStderr); + } + QByteArray cleanupTestCase() override { return cleanup(); @@ -518,7 +541,7 @@ RETURN_ON_ERROR( setClipboard(QByteArray(), mimeText, ClipboardMode::Selection), "Failed to reset selection" ); #endif - RETURN_ON_ERROR( startServer(), "Failed to start server" ); + RETURN_ON_ERROR( startServer(), "Failed to initialize server" ); // Always show main window first so that the results are consistent with desktop environments // where user cannot hide main window (tiling window managers without tray). @@ -582,19 +605,6 @@ return p->waitForStarted(10000); } - QByteArray testClipboard(const QByteArray &bytes, const QString &mime) - { - const QByteArray actualBytes = getClipboard(mime); - if (actualBytes != bytes) { - return QString("Test failed (clipboard data for MIME \"%1\"): ") - .arg(mime).toUtf8() - + decorateOutput("Unexpected content", actualBytes) - + decorateOutput("Expected content", bytes); - } - - return ""; - } - QByteArray waitForServerToStart() { SleepTimer t(15000); @@ -607,7 +617,6 @@ } std::unique_ptr m_server; - QByteArray m_serverErrorOutput; QProcessEnvironment m_env; QString m_testId; QVariantMap m_settings; @@ -618,6 +627,21 @@ return QKeySequence(standardKey).toString(); } +int count(const QStringList &items, const QString &pattern) +{ + int from = -1; + int count = 0; + const QRegExp re(pattern); + while ( (from = items.indexOf(re, from + 1)) != -1 ) + ++count; + return count; +} + +QStringList splitLines(const QString &nativeText) +{ + return nativeText.split(QRegExp("\r\n|\n|\r")); +} + } // namespace Tests::Tests(const TestInterfacePtr &test, QObject *parent) @@ -646,6 +670,28 @@ TEST( m_test->cleanup() ); } +void Tests::readLog() +{ + QByteArray stdoutActual; + QByteArray stderrActual; + QCOMPARE( run(Args("info") << "log", &stdoutActual, &stderrActual), 0 ); + QVERIFY2( testStderr(stderrActual), stderrActual ); + QCOMPARE( logFileName() + "\n", QString::fromUtf8(stdoutActual) ); + QTRY_VERIFY( !readLogFile(maxReadLogSize).isEmpty() ); + +#define LOGGED_ONCE(PATTERN) \ + QTRY_COMPARE( count(splitLines(readLogFile(maxReadLogSize)), PATTERN), 1 ) + + LOGGED_ONCE( + R"(^CopyQ DEBUG \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] : Loading configuration$)"); + + LOGGED_ONCE( + R"(^CopyQ DEBUG \[.*\] : Starting monitor$)"); + + LOGGED_ONCE( + R"(^.*: Clipboard formats to save: .*$)"); +} + void Tests::commandHelp() { QByteArray stdoutActual; @@ -1517,6 +1563,27 @@ RUN("add" << "B", ""); } +void Tests::commandServerLogAndLogs() +{ + const QByteArray data1 = generateData(); + QRegExp re("CopyQ Note \\[[^]]+\\] : " + QRegExp::escape(data1)); + + QByteArray stdoutActual; + QByteArray stderrActual; + + QCOMPARE( run(Args("logs"), &stdoutActual, &stderrActual), 0 ); + QVERIFY2( testStderr(stderrActual), stderrActual ); + QVERIFY( !stdoutActual.isEmpty() ); + QVERIFY( !QString::fromUtf8(stdoutActual).contains(re) ); + + RUN("serverLog" << data1, ""); + + QCOMPARE( run(Args("logs"), &stdoutActual, &stderrActual), 0 ); + QVERIFY2( testStderr(stderrActual), stderrActual ); + QVERIFY( !stdoutActual.isEmpty() ); + QVERIFY( QString::fromUtf8(stdoutActual).contains(re) ); +} + void Tests::classByteArray() { RUN("ByteArray('test')", "test"); @@ -2425,7 +2492,7 @@ RUN("config" << "check_clipboard" << "false", "false\n"); // Open preferences dialog. - RUN("keys" << ConfigTabShortcuts::tr("Ctrl+P"), ""); + RUN("keys" << "Ctrl+P", ""); // Focus and set wrap text option. // This behavior could differ on some systems and in other languages. @@ -3104,7 +3171,7 @@ RUN("keys" << logDialogId << "CTRL+A" << "CTRL+C" << logDialogId, ""); const QByteArray expectedLog = "Starting callback: onStart"; - QCOMPARE( waitUntilClipboardSet(expectedLog, mimeHtml, false), expectedLog ); + TEST( m_test->verifyClipboard(expectedLog, mimeHtml, false) ); RUN("keys" << logDialogId << "ESCAPE" << clipboardBrowserId, ""); } @@ -3116,7 +3183,7 @@ RUN("keys" << actionHandlerFilterId << "TAB" << actionHandlerTableId, ""); RUN("keys" << actionHandlerTableId << "RIGHT" << "CTRL+C", ""); - WAIT_FOR_CLIPBOARD("copyq monitorClipboard"); + WAIT_FOR_CLIPBOARD("copyq --clipboard-access monitorClipboard"); RUN("keys" << actionHandlerDialogId << "ESCAPE" << clipboardBrowserId, ""); @@ -3347,7 +3414,7 @@ { QByteArray out; run(Args("tab"), &out); - return QString::fromUtf8(out).split(QRegExp("\r\n|\n|\r")).contains(tabName); + return splitLines(QString::fromUtf8(out)).contains(tabName); } int runTests(int argc, char *argv[]) @@ -3370,14 +3437,15 @@ } } - QGuiApplication app(argc, argv); + const auto platform = platformNativeInterface(); + std::unique_ptr app( platform->createTestApplication(argc, argv) ); Q_UNUSED(app); const QString session = "copyq.test"; QCoreApplication::setOrganizationName(session); QCoreApplication::setApplicationName(session); Settings::canModifySettings = true; - platformNativeInterface()->loadSettings(); + platform->loadSettings(); int exitCode = 0; std::shared_ptr test(new TestInterfaceImpl); diff -Nru copyq-3.9.0/src/tests/tests.h copyq-3.9.1/src/tests/tests.h --- copyq-3.9.0/src/tests/tests.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/tests/tests.h 2019-08-18 09:07:57.000000000 +0000 @@ -44,6 +44,7 @@ void init(); void cleanup(); + void readLog(); void commandHelp(); void commandVersion(); void badCommand(); @@ -137,6 +138,8 @@ void commandUnload(); void commandForceUnload(); + void commandServerLogAndLogs(); + void classByteArray(); void classFile(); void classDir(); diff -Nru copyq-3.9.0/src/tests/test_utils.h copyq-3.9.1/src/tests/test_utils.h --- copyq-3.9.0/src/tests/test_utils.h 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/tests/test_utils.h 2019-08-18 09:07:57.000000000 +0000 @@ -59,7 +59,7 @@ ferr.open(stderr, QIODevice::WriteOnly); \ ferr.write(errors_ + "\n"); \ ferr.close(); \ - QVERIFY2(false, "Failed with errors above."); \ + QFAIL("Failed with errors above."); \ } \ } while (false) @@ -76,10 +76,10 @@ TEST( m_test->runClientWithError((Args() << ARGUMENTS), (EXIT_CODE), toByteArray(STDERR_CONTAINS)) ); #define WAIT_FOR_CLIPBOARD(DATA) \ - QCOMPARE( waitUntilClipboardSet(DATA), QByteArray(DATA) ) + TEST( m_test->verifyClipboard(DATA, "text/plain") ) #define WAIT_FOR_CLIPBOARD2(DATA, MIME) \ - QCOMPARE( waitUntilClipboardSet(DATA, MIME), QByteArray(DATA) ) + TEST( m_test->verifyClipboard((DATA), (MIME)) ) #define RETURN_ON_ERROR(CALLBACK, ERROR) \ do { \ @@ -103,17 +103,7 @@ } while(false) #define WAIT_ON_OUTPUT(ARGUMENTS, OUTPUT) \ -do { \ - PerformanceTimer perf; \ - QByteArray out_; \ - const QByteArray expected_(OUTPUT); \ - SleepTimer t_(8000); \ - do { \ - TEST( m_test->getClientOutput((Args() << ARGUMENTS), &out_) ); \ - } while (out_ != expected_ && t_.sleep()); \ - perf.printPerformance("WAIT_ON_OUTPUT", (Args() << ARGUMENTS)); \ - QCOMPARE(QString(out_), QString(expected_)); \ -} while(false) + TEST( m_test->waitOnOutput((Args() << ARGUMENTS), (OUTPUT)) ) #define SKIP_ON_ENV(ENV) \ if ( qgetenv(ENV) == "1" ) \ diff -Nru copyq-3.9.0/src/ui/configtabgeneral.ui copyq-3.9.1/src/ui/configtabgeneral.ui --- copyq-3.9.0/src/ui/configtabgeneral.ui 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/ui/configtabgeneral.ui 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,298 @@ + + + ConfigTabGeneral + + + + 0 + 0 + 639 + 523 + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 639 + 523 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + &Language: + + + comboBoxLanguage + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Break text if it's too long to fit on line + + + Wrap l&ong text + + + + + + + Keep main window above other windows + + + Alwa&ys on Top + + + + + + + Close main window when other application has focus + + + Close When Unfocused + + + + + + + Enable to open windows on current screen. Disable to open windows where they were last closed + + + O&pen windows on current screen + + + + + + + Confirm application exit + + + Confirm application e&xit + + + true + + + + + + + Run the application on system startup + + + &Autostart + + + + + + + Support for Vi navigation keys (H, J, K, L and more), slash (/) key to search + + + &Vi style navigation + + + + + + + Save and restore history of item filters + + + Save Filter History + + + + + + + Automatically show popup to complete function, type and variable names in commands + + + Auto-complete Commands + + + + + + + Clipboard Manipulation + + + false + + + false + + + + + + Allow to paste copied content the same way as mouse selections (usually by pressing middle mouse button) + + + (&3) Paste clipboard with mouse + + + + + + + Allow to paste mouse selections using shortcut (usually Ctrl+V or Shift+Insert) + + + (&4) Paste mouse selection with keyboard + + + + + + + Save clipboard in history + + + (&1) Store clipboard + + + true + + + + + + + Save text selected with mouse (primary selection) in history + + + (&2) Store text selected using mouse + + + + + + + (&5) Run automatic commands on selection + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + comboBoxLanguage + checkBoxTextWrap + checkBoxAlwaysOnTop + checkBoxCloseOnUnfocus + checkBoxOpenWindowsOnCurrentScreen + checkBoxConfirmExit + checkBoxAutostart + checkBoxViMode + checkBoxSaveFilterHistory + checkBoxAutocompleteCommands + checkBoxClip + checkBoxSel + checkBoxCopyClip + checkBoxCopySel + checkBoxRunSel + + + + diff -Nru copyq-3.9.0/src/ui/configtabhistory.ui copyq-3.9.1/src/ui/configtabhistory.ui --- copyq-3.9.0/src/ui/configtabhistory.ui 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/ui/configtabhistory.ui 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,319 @@ + + + ConfigTabHistory + + + + 0 + 0 + 639 + 523 + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 639 + 523 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Maximum &number of items in history: + + + spinBoxItems + + + + + + + + + Maximum number of items in each tab + + + 10000 + + + 200 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + &Unload tab after an interval in minutes: + + + spinBoxExpireTab + + + + + + + + + Unload each tab from memory after specified number of minutes of inactivity. + +Set to 0 not to unload tabs. + + + 480 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + External editor command (%&1 is file to edit): + + + lineEditEditor + + + + + + + External editor command (%1 is file to edit). + Examples: + gedit %1 + notepad %1 + gvim -f %1 + xterm -e vim %1 + + + + + + + + + + Ta&b for storing clipboard: + + + comboBoxClipboardTab + + + + + + + Name of tab that will automatically store new clipboard content. + +Leave empty to disable automatic storing. + + + true + + + + + + + + + Leave unchecked for Return key to save edited item and Ctrl+Return create new line. + +Note: Edited items can be saved with F2 disregarding this option. + + + Sa&ve edited item with Ctrl+Return and create new line with Return key + + + + + + + Show single line description of each item. + +Use Item Preview to display whole items. + + + Sho&w simple items + + + + + + + Enable searching for numbers, otherwise pressing a digit key activates item on that position + + + S&earch for numbers + + + + + + + After item is activated (double-click or Enter key), copy it to clipboard and ... + + + + + + Move item to the top of the list after it is activated + + + Move item to the t&op + + + + + + + Close main window after item is activated + + + &Close main window + + + + + + + Focus last window after item is activated + + + &Focus last window + + + + + + + Paste to current window after item is activated + + + &Paste to current window + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + comboBoxClipboardTab + spinBoxItems + spinBoxExpireTab + lineEditEditor + checkBoxEditCtrlReturn + checkBoxShowSimpleItems + checkBoxNumberSearch + checkBoxMove + checkBoxActivateCloses + checkBoxActivateFocuses + checkBoxActivatePastes + + + + diff -Nru copyq-3.9.0/src/ui/configtablayout.ui copyq-3.9.1/src/ui/configtablayout.ui --- copyq-3.9.0/src/ui/configtablayout.ui 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/ui/configtablayout.ui 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,255 @@ + + + ConfigTabLayout + + + + 9 + 9 + 639 + 523 + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 639 + 523 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Show/Hide + + + + + + Hide tabs (press Alt key to show) + + + Hi&de tabs + + + + + + + Hide toolbar + + + Hide too&lbar + + + + + + + Hide tool&bar labels + + + + + + + Hide main window when closed + + + Hide &main window + + + + + + + + + + Layout and Transparency + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Show tree with tabs instead of tab bar + + + Tab T&ree + + + + + + + &Focused transparency: + + + spinBoxTransparencyFocused + + + + + + + + + Transparency of main window if focused. + +Note: This is not supported on all systems. + + + % + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + &Unfocused transparency: + + + spinBoxTransparencyUnfocused + + + + + + + + + Transparency of main window if unfocused. + +Note: This is not supported on all systems. + + + % + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Show number of items in tabs + + + Sho&w Item Count + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff -Nru copyq-3.9.0/src/ui/configtabnotifications.ui copyq-3.9.1/src/ui/configtabnotifications.ui --- copyq-3.9.0/src/ui/configtabnotifications.ui 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/ui/configtabnotifications.ui 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,406 @@ + + + ConfigTabNotifications + + + + 9 + 9 + 639 + 523 + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 639 + 523 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + &Notification position: + + + comboBoxNotificationPosition + + + + + + + + + Position on screen for notifications + + + + Top + + + + + Bottom + + + + + Top Right + + + + + Bottom Right + + + + + Bottom Left + + + + + Top Left + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Int&erval in seconds to display notifications: + + + spinBoxNotificationPopupInterval + + + + + + + + + Interval in seconds to display notification for new clipboard content or if item is copied to clipboard (only if main window is closed). + +Set to 0 to disable this. + +Set to -1 to keep visible until clicked. + + + -1 + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Num&ber of lines for clipboard notification: + + + spinBoxClipboardNotificationLines + + + + + + + + + Number of lines to show for new clipboard content. + +Set to 0 to disable. + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Notification Geometry (in screen points) + + + + + + Hori&zontal offset: + + + spinBoxNotificationHorizontalOffset + + + + + + + + + Notification distance from left or right screen edge in screen points + + + -99999 + + + 99999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + &Vertical offset: + + + spinBoxNotificationVerticalOffset + + + + + + + + + Notification distance from top or bottom screen edge in screen points + + + -99999 + + + 99999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Maximum &width: + + + spinBoxNotificationMaximumWidth + + + + + + + + + Maximum width for notification in screen points + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Ma&ximum height: + + + spinBoxNotificationMaximumHeight + + + + + + + + + Maximum height for notification in screen points + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff -Nru copyq-3.9.0/src/ui/configtabshortcuts.ui copyq-3.9.1/src/ui/configtabshortcuts.ui --- copyq-3.9.0/src/ui/configtabshortcuts.ui 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/ui/configtabshortcuts.ui 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ - - - ConfigTabShortcuts - - - - 0 - 0 - 388 - 417 - - - - - - - - - - - ShortcutsWidget - QWidget -
gui/shortcutswidget.h
- 1 -
-
- - -
diff -Nru copyq-3.9.0/src/ui/configtabtray.ui copyq-3.9.1/src/ui/configtabtray.ui --- copyq-3.9.0/src/ui/configtabtray.ui 1970-01-01 00:00:00.000000000 +0000 +++ copyq-3.9.1/src/ui/configtabtray.ui 2019-08-18 09:07:57.000000000 +0000 @@ -0,0 +1,219 @@ + + + ConfigTabTray + + + + 9 + 9 + 639 + 523 + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 639 + 523 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Don't show tray icon; minimize window when closed + + + Disabl&e tray + + + + + + + Show command for current clipboard content in tray menu + + + Sho&w commands for clipboard content + + + + + + + + + N&umber of items in tray menu: + + + spinBoxTrayItems + + + + + + + Number of items in tray menu + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Show items from current tab in tray menu + + + Show cu&rrent tab in menu, + + + + + + + or &choose other tab: + + + comboBoxMenuTab + + + + + + + + 0 + 0 + + + + Name of tab to show in tray menu (empty for the first tab) + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Paste item to current window after selecting it in menu + + + &Paste activated item to current window + + + + + + + Show image preview next to menu items + + + Sh&ow image preview as menu item icon + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff -Nru copyq-3.9.0/src/ui/configurationmanager.ui copyq-3.9.1/src/ui/configurationmanager.ui --- copyq-3.9.0/src/ui/configurationmanager.ui 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/ui/configurationmanager.ui 2019-08-18 09:07:57.000000000 +0000 @@ -21,1525 +21,7 @@ - - - QTabWidget::North - - - 0 - - - Qt::ElideNone - - - true - - - - &General - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 639 - 523 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - 0 - - - - - &Language: - - - comboBoxLanguage - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Break text if it's too long to fit on line - - - Wrap l&ong text - - - - - - - Keep main window above other windows - - - Alwa&ys on Top - - - - - - - Close main window when other application has focus - - - Close When Unfocused - - - - - - - Enable to open windows on current screen. Disable to open windows where they were last closed - - - O&pen windows on current screen - - - - - - - Confirm application exit - - - Confirm application e&xit - - - true - - - - - - - Run the application on system startup - - - &Autostart - - - - - - - Support for Vi navigation keys (H, J, K, L and more), slash (/) key to search - - - &Vi style navigation - - - - - - - Save and restore history of item filters - - - Save Filter History - - - - - - - Automatically show popup to complete function, type and variable names in commands - - - Auto-complete Commands - - - - - - - Clipboard Manipulation - - - false - - - false - - - - - - Allow to paste copied content the same way as mouse selections (usually by pressing middle mouse button) - - - (&3) Paste clipboard with mouse - - - - - - - Allow to paste mouse selections using shortcut (usually Ctrl+V or Shift+Insert) - - - (&4) Paste mouse selection with keyboard - - - - - - - Save clipboard in history - - - (&1) Store clipboard - - - true - - - - - - - Save text selected with mouse (primary selection) in history - - - (&2) Store text selected using mouse - - - - - - - (&5) Run automatic commands on selection - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - &Layout - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 639 - 523 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Show/Hide - - - - - - Hide tabs (press Alt key to show) - - - Hi&de tabs - - - - - - - Hide toolbar - - - Hide too&lbar - - - - - - - Hide tool&bar labels - - - - - - - Hide main window when closed - - - Hide &main window - - - - - - - - - - Layout and Transparency - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Show tree with tabs instead of tab bar - - - Tab T&ree - - - - - - - &Focused transparency: - - - spinBoxTransparencyFocused - - - - - - - - - Transparency of main window if focused. - -Note: This is not supported on all systems. - - - % - - - 100 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - &Unfocused transparency: - - - spinBoxTransparencyUnfocused - - - - - - - - - Transparency of main window if unfocused. - -Note: This is not supported on all systems. - - - % - - - 100 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Show number of items in tabs - - - Sho&w Item Count - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - &History - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 639 - 523 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Maximum &number of items in history: - - - spinBoxItems - - - - - - - - - Maximum number of items in each tab - - - 10000 - - - 200 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - &Unload tab after an interval in minutes: - - - spinBoxExpireTab - - - - - - - - - Unload each tab from memory after specified number of minutes of inactivity. - -Set to 0 not to unload tabs. - - - 480 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - External editor command (%&1 is file to edit): - - - lineEditEditor - - - - - - - External editor command (%1 is file to edit). - Examples: - gedit %1 - notepad %1 - gvim -f %1 - xterm -e vim %1 - - - - - - - - - - Ta&b for storing clipboard: - - - comboBoxClipboardTab - - - - - - - Name of tab that will automatically store new clipboard content. - -Leave empty to disable automatic storing. - - - true - - - - - - - - - Leave unchecked for Return key to save edited item and Ctrl+Return create new line. - -Note: Edited items can be saved with F2 disregarding this option. - - - Sa&ve edited item with Ctrl+Return and create new line with Return key - - - - - - - Show single line description of each item. - -Use Item Preview to display whole items. - - - Sho&w simple items - - - - - - - Enable searching for numbers, otherwise pressing a digit key activates item on that position - - - S&earch for numbers - - - - - - - After item is activated (double-click or Enter key), copy it to clipboard and ... - - - - - - Move item to the top of the list after it is activated - - - Move item to the t&op - - - - - - - Close main window after item is activated - - - &Close main window - - - - - - - Focus last window after item is activated - - - &Focus last window - - - - - - - Paste to current window after item is activated - - - &Paste to current window - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - &Tray - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 639 - 523 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Don't show tray icon; minimize window when closed - - - Disabl&e tray - - - - - - - Show command for current clipboard content in tray menu - - - Sho&w commands for clipboard content - - - - - - - - - N&umber of items in tray menu: - - - spinBoxTrayItems - - - - - - - Number of items in tray menu - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Show items from current tab in tray menu - - - Show cu&rrent tab in menu, - - - - - - - or &choose other tab: - - - comboBoxMenuTab - - - - - - - - 0 - 0 - - - - Name of tab to show in tray menu (empty for the first tab) - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Paste item to current window after selecting it in menu - - - &Paste activated item to current window - - - - - - - Show image preview next to menu items - - - Sh&ow image preview as menu item icon - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - &Notifications - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 639 - 523 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - &Notification position: - - - comboBoxNotificationPosition - - - - - - - - - Position on screen for notifications - - - - Top - - - - - Bottom - - - - - Top Right - - - - - Bottom Right - - - - - Bottom Left - - - - - Top Left - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Int&erval in seconds to display notifications: - - - spinBoxNotificationPopupInterval - - - - - - - - - Interval in seconds to display notification for new clipboard content or if item is copied to clipboard (only if main window is closed). - -Set to 0 to disable this. - -Set to -1 to keep visible until clicked. - - - -1 - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Num&ber of lines for clipboard notification: - - - spinBoxClipboardNotificationLines - - - - - - - - - Number of lines to show for new clipboard content. - -Set to 0 to disable. - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Notification Geometry (in screen points) - - - - - - Hori&zontal offset: - - - spinBoxNotificationHorizontalOffset - - - - - - - - - Notification distance from left or right screen edge in screen points - - - -99999 - - - 99999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - &Vertical offset: - - - spinBoxNotificationVerticalOffset - - - - - - - - - Notification distance from top or bottom screen edge in screen points - - - -99999 - - - 99999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Maximum &width: - - - spinBoxNotificationMaximumWidth - - - - - - - - - Maximum width for notification in screen points - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Ma&ximum height: - - - spinBoxNotificationMaximumHeight - - - - - - - - - Maximum height for notification in screen points - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Ta&bs - - - - - - - - - - &Items - - - - - - - 0 - 0 - - - - You can change priority of formats to display by reordering items below. - - - true - - - - - - - Qt::WheelFocus - - - - - - - - &Shortcuts - - - - - - - - - - &Appearance - - - - - - - - + @@ -1555,84 +37,12 @@ - ConfigTabAppearance - QWidget -
gui/configtabappearance.h
- 1 -
- ItemOrderList QWidget
gui/itemorderlist.h
1
- - ConfigTabShortcuts - QWidget -
gui/configtabshortcuts.h
- 1 -
- - ConfigTabTabs - QWidget -
gui/configtabtabs.h
- 1 -
- - comboBoxLanguage - checkBoxTextWrap - checkBoxAlwaysOnTop - checkBoxOpenWindowsOnCurrentScreen - checkBoxConfirmExit - checkBoxAutostart - checkBoxViMode - checkBoxSaveFilterHistory - checkBoxAutocompleteCommands - checkBoxClip - checkBoxSel - checkBoxCopyClip - checkBoxCopySel - checkBoxRunSel - scrollArea - checkBoxHideTabs - checkBoxHideToolbar - checkBoxHideToolbarLabels - checkBoxTabTree - checkBoxShowTabItemCount - spinBoxTransparencyFocused - spinBoxTransparencyUnfocused - scrollArea_5 - comboBoxClipboardTab - spinBoxItems - spinBoxExpireTab - lineEditEditor - checkBoxEditCtrlReturn - checkBoxShowSimpleItems - checkBoxNumberSearch - checkBoxMove - checkBoxActivateCloses - checkBoxActivateFocuses - checkBoxActivatePastes - scrollArea_2 - checkBoxDisableTray - checkBoxTrayShowCommands - spinBoxTrayItems - checkBoxMenuTabIsCurrent - comboBoxMenuTab - checkBoxPasteMenuItem - checkBoxTrayImages - scrollArea_4 - comboBoxNotificationPosition - spinBoxNotificationPopupInterval - spinBoxClipboardNotificationLines - spinBoxNotificationHorizontalOffset - spinBoxNotificationVerticalOffset - spinBoxNotificationMaximumWidth - spinBoxNotificationMaximumHeight - scrollArea_3 - itemOrderListPlugins -
diff -Nru copyq-3.9.0/src/ui/itemorderlist.ui copyq-3.9.1/src/ui/itemorderlist.ui --- copyq-3.9.0/src/ui/itemorderlist.ui 2019-06-27 08:38:57.000000000 +0000 +++ copyq-3.9.1/src/ui/itemorderlist.ui 2019-08-18 09:07:57.000000000 +0000 @@ -114,30 +114,40 @@ - - - - 1 - 0 - - + - Qt::NoFocus - - - QFrame::NoFrame - - - QFrame::Plain - - - true + Qt::StrongFocus + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + listWidgetItems + widgetParent + pushButtonAdd + pushButtonRemove + pushButtonUp + pushButtonDown +