diff --git a/linux/psi-extra-action1.desktop b/linux/psi-extra-action1.desktop index b76b07f9f7..239a88931e 100644 --- a/linux/psi-extra-action1.desktop +++ b/linux/psi-extra-action1.desktop @@ -3,3 +3,8 @@ Exec=psi --choose-profile Name=Start with another profile Icon=psi + +[Desktop Action QuitApplication] +Exec=psi --quit +Name=Quit the application +Icon=application-exit diff --git a/src/activeprofiles.cpp b/src/activeprofiles.cpp index f1a57ea5f9..9997f527fa 100644 --- a/src/activeprofiles.cpp +++ b/src/activeprofiles.cpp @@ -114,3 +114,14 @@ ActiveProfiles *ActiveProfiles::instance() * \fn void ActiveProfiles::raiseRequested() * \brief Signal emitted when other Psi instance requested to raise main window. */ + +/** + * \fn bool ActiveProfiles::quit(const QString &profile) const + * \brief Closes the active Psi application instance running \a profile. + * If \a profile is empty, other running instance is selected. + */ + +/** + * \fn void ActiveProfiles::quitRequested() + * \brief Signal emitted when other Psi instance requested to quit application. + */ diff --git a/src/activeprofiles.h b/src/activeprofiles.h index 65d8243713..a512b4e3d7 100644 --- a/src/activeprofiles.h +++ b/src/activeprofiles.h @@ -40,6 +40,7 @@ class ActiveProfiles : public QObject { bool setStatus(const QString &profile, const QString &status, const QString &message) const; bool openUri(const QString &profile, const QString &uri) const; bool raise(const QString &profile, bool withUI) const; + bool quit(const QString &profile) const; ~ActiveProfiles(); @@ -48,6 +49,7 @@ class ActiveProfiles : public QObject { void setStatusRequested(const QString &status, const QString &message); void openUriRequested(const QString &uri); void raiseRequested(); + void quitRequested(); protected: static ActiveProfiles *instance_; diff --git a/src/activeprofiles_dbus.cpp b/src/activeprofiles_dbus.cpp index 688366cbe6..d95ad5a255 100644 --- a/src/activeprofiles_dbus.cpp +++ b/src/activeprofiles_dbus.cpp @@ -213,3 +213,9 @@ bool ActiveProfiles::raise(const QString &profile, bool withUI) const } return rmsg.type() == QDBusMessage::ReplyMessage; } + +bool ActiveProfiles::quit(const QString &profile) const +{ + QDBusInterface(d->dbusName(profile), "/Main", PSIDBUSMAINIF).call(QDBus::NoBlock, "quit"); + return true; +} diff --git a/src/activeprofiles_stub.cpp b/src/activeprofiles_stub.cpp index 2cf561b7e5..4aecba4a2f 100644 --- a/src/activeprofiles_stub.cpp +++ b/src/activeprofiles_stub.cpp @@ -72,3 +72,9 @@ bool ActiveProfiles::raise(const QString &profile, bool withUI) const Q_UNUSED(withUI); return true; } + +bool ActiveProfiles::quit(const QString &profile) const +{ + Q_UNUSED(profile); + return true; +} diff --git a/src/activeprofiles_win.cpp b/src/activeprofiles_win.cpp index 55114f0a8e..b3690db912 100644 --- a/src/activeprofiles_win.cpp +++ b/src/activeprofiles_win.cpp @@ -198,15 +198,20 @@ bool ActiveProfiles::Private::nativeEvent(const QByteArray &eventType, void *mes } if (list.count() > 1) { - if (list[0] == "openUri") { + if (list[0] == QStringLiteral("openUri")) { emit ap->openUriRequested(list.value(1)); *result = TRUE; - } else if (list[0] == "setStatus") { + } else if (list[0] == QStringLiteral("setStatus")) { emit ap->setStatusRequested(list.value(1), list.value(2)); *result = TRUE; - } else if (list[0] == "recvNextEvent") { + } + } else if (list.count() == 1) { + if (list[0] == QStringLiteral("recvNextEvent")) { emit ap->recvNextEventRequested(); *result = TRUE; + } else if (list[0] == QStringLiteral("quit")) { + emit ap->quitRequested(); + *result = TRUE; } } } @@ -324,21 +329,24 @@ bool ActiveProfiles::raise(const QString &profile, bool withUI) const bool ActiveProfiles::openUri(const QString &profile, const QString &uri) const { - QStringList list; - list << "openUri" << uri; + QStringList list { QStringLiteral("openUri"), uri }; return d->sendStringList(profile.isEmpty() ? d->pickProfile() : profile, list); } bool ActiveProfiles::recvNextEvent(const QString &profile) const { - QStringList list; - list << "recvNextEvent"; + QStringList list { QStringLiteral("recvNextEvent") }; return d->sendStringList(profile.isEmpty() ? d->pickProfile() : profile, list); } bool ActiveProfiles::setStatus(const QString &profile, const QString &status, const QString &message) const { - QStringList list; - list << "setStatus" << status << message; + QStringList list { QStringLiteral("setStatus"), status, message }; + return d->sendStringList(profile.isEmpty() ? d->pickProfile() : profile, list); +} + +bool ActiveProfiles::quit(const QString &profile) const +{ + QStringList list { QStringLiteral("quit") }; return d->sendStringList(profile.isEmpty() ? d->pickProfile() : profile, list); } diff --git a/src/dbus.cpp b/src/dbus.cpp index 2cdb5f368d..cb818a8089 100644 --- a/src/dbus.cpp +++ b/src/dbus.cpp @@ -28,6 +28,7 @@ public Q_SLOTS: void sleep(); void wake(); void recvNextEvent(); + void quit(); /*Q_SIGNALS: void psi_pong(); */ @@ -39,15 +40,15 @@ PsiConAdapter::PsiConAdapter(PsiCon *psicon_) : QDBusAbstractAdaptor(psicon_) { PsiConAdapter::~PsiConAdapter() { } -void PsiConAdapter::openURI(QString uri) { emit ActiveProfiles::instance() -> openUriRequested(uri); } +void PsiConAdapter::openURI(QString uri) { emit ActiveProfiles::instance()->openUriRequested(uri); } void PsiConAdapter::setStatus(QString status, QString message) { - emit ActiveProfiles::instance() -> setStatusRequested(status, message); + emit ActiveProfiles::instance()->setStatusRequested(status, message); } // FIXME libguniqueapp uses activate -void PsiConAdapter::raise() { emit ActiveProfiles::instance() -> raiseRequested(); } +void PsiConAdapter::raise() { emit ActiveProfiles::instance()->raiseRequested(); } void PsiConAdapter::sleep() { psicon->doSleep(); } @@ -61,4 +62,6 @@ void addPsiConAdapter(PsiCon *psicon) QDBusConnection::sessionBus().registerObject("/Main", psicon); } +void PsiConAdapter::quit() { emit ActiveProfiles::instance()->quitRequested(); } + #include "dbus.moc" diff --git a/src/main.cpp b/src/main.cpp index 9753120d8f..071a283dca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -132,6 +132,10 @@ bool PsiMain::useActiveInstance() ActiveProfiles::instance()->raise(cmdline.value("profile"), true); } + if (cmdline.contains("quit")) { + ActiveProfiles::instance()->quit(cmdline.value("profile")); + } + return true; } else return cmdline.contains("remote"); @@ -282,11 +286,11 @@ void PsiMain::sessionStart() connect(pcon, SIGNAL(quit(int)), SLOT(sessionQuit(int))); if (cmdline.contains("uri")) { - emit ActiveProfiles::instance() -> openUriRequested(cmdline.value("uri")); + emit ActiveProfiles::instance()->openUriRequested(cmdline.value("uri")); cmdline.remove("uri"); } if (cmdline.contains("status") || cmdline.contains("status-message")) { - emit ActiveProfiles::instance() -> setStatusRequested(cmdline.value("status"), cmdline.value("status-message")); + emit ActiveProfiles::instance()->setStatusRequested(cmdline.value("status"), cmdline.value("status-message")); cmdline.remove("status"); cmdline.remove("status-message"); } diff --git a/src/psicli.h b/src/psicli.h index e2ffee879e..21783a0949 100644 --- a/src/psicli.h +++ b/src/psicli.h @@ -50,6 +50,8 @@ class PsiCli : public SimpleCli { tr("Use software widgets rendering. In some cases default hardware rendering may lead to graphical " "glitches and crashes. This option may help.")); + defineSwitch("quit", tr("Quit the application")); + defineSwitch("help", tr("Show this help message and exit.")); defineAlias("h", "help"); defineAlias("?", "help"); diff --git a/src/psicon.cpp b/src/psicon.cpp index 2551d6ca23..ba811ab1fb 100644 --- a/src/psicon.cpp +++ b/src/psicon.cpp @@ -719,6 +719,7 @@ bool PsiCon::init() SLOT(setStatusFromCommandline(const QString &, const QString &))); connect(ActiveProfiles::instance(), SIGNAL(openUriRequested(const QString &)), SLOT(openUri(const QString &))); connect(ActiveProfiles::instance(), SIGNAL(raiseRequested()), SLOT(raiseMainwin())); + connect(ActiveProfiles::instance(), SIGNAL(quitRequested()), SLOT(closeProgram())); DesktopUtil::setUrlHandler("xmpp", this, "openUri"); DesktopUtil::setUrlHandler("x-psi-atstyle", this, "openAtStyleUri"); diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index ca467c6ead..96f0a1754a 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -63,7 +63,7 @@ set(HEADERS stretchwidget.h ) -if(USE_DBUS OR WIN32) +if(USE_TASKBARNOTIFIER) list(APPEND SOURCES taskbarnotifier.cpp) list(APPEND HEADERS taskbarnotifier.h) if(WIN32 AND (${QT_DEFAULT_MAJOR_VERSION} VERSION_LESS "6")) @@ -91,6 +91,9 @@ endif() qt_wrap_ui(UI_FORMS ${FORMS}) add_library(widgets STATIC ${SOURCES} ${HEADERS} ${UI_FORMS}) +if(WIN32) +target_link_libraries(widgets shlwapi) +endif() target_link_libraries(widgets ${QT_LIBRARIES} ${iris_LIB} tools) target_include_directories(widgets PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}) if(IS_WEBKIT OR IS_WEBENGINE) diff --git a/src/widgets/taskbarnotifier.cpp b/src/widgets/taskbarnotifier.cpp index c639a2898c..5aacfd27fc 100644 --- a/src/widgets/taskbarnotifier.cpp +++ b/src/widgets/taskbarnotifier.cpp @@ -40,6 +40,13 @@ #endif #ifdef Q_OS_WIN +#include + +#include "applicationinfo.h" +#include "psiiconset.h" + +#include +#include #include #include @@ -73,13 +80,16 @@ class TaskBarNotifier::Private { #endif #ifdef Q_OS_WIN void setFlashWindow(bool enabled); + void addJumpListItem(); #endif private: #ifdef Q_OS_WIN - void setTaskBarIcon(const HICON &icon = {}); - HICON makeIconCaption(const QString &number) const; - HICON getHICONfromQImage(const QImage &image) const; - void doFlashTaskbarIcon(); + void setTaskBarIcon(const HICON &icon = {}); + HICON makeIconCaption(const QString &number) const; + HICON getHICONfromQImage(const QImage &image) const; + void doFlashTaskbarIcon(); + IShellLink *createShellLink(const QString &path, const QString &name, const QString &tooltip, const QString &args, + const QString &icon); #else QIcon setImageCountCaption(uint count = 0); #ifdef USE_DBUS @@ -300,12 +310,86 @@ void TaskBarNotifier::Private::doFlashTaskbarIcon() fi.dwTimeout = 0; FlashWindowEx(&fi); } + +void TaskBarNotifier::Private::addJumpListItem() +{ + // Create an object collection + IObjectCollection *pCollection = nullptr; + if (SUCCEEDED(CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pCollection)))) { + // Create shell link object + auto path = qApp->applicationFilePath(); + auto nameString = QString("Quit %1 application").arg(qApp->applicationName()); + auto cachedIconFile + = ApplicationInfo::homeDir(ApplicationInfo::CacheLocation) + QStringLiteral("/quit_icon.ico"); + auto pixmap = PsiIconset::instance() + ->system() + .icon("psi/quit") + ->pixmap(QSize(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)) * devicePixelRatio_); + pixmap.save(cachedIconFile, "ICO"); + IShellLink *quitShellLink + = createShellLink(path, nameString, nameString, QStringLiteral("--quit"), cachedIconFile); + if (quitShellLink != nullptr) { + pCollection->AddObject(quitShellLink); + quitShellLink->Release(); + // Create custom Jump list + ICustomDestinationList *destinationList = nullptr; + if (SUCCEEDED(CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, + IID_ICustomDestinationList, + reinterpret_cast(&(destinationList))))) { + IObjectArray *objectArray = nullptr; + UINT cMaxSlots; + // Init Jump list and add items to it + if (SUCCEEDED(destinationList->BeginList(&cMaxSlots, IID_IObjectArray, + reinterpret_cast(&(objectArray))))) { + destinationList->AddUserTasks(pCollection); + destinationList->CommitList(); + objectArray->Release(); + } + destinationList->Release(); + } + } + pCollection->Release(); + } +} + +IShellLink *TaskBarNotifier::Private::createShellLink(const QString &path, const QString &name, const QString &tooltip, + const QString &args, const QString &icon) +{ + IShellLink *pShellLink = nullptr; + if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink)))) { + auto wPath = reinterpret_cast(path.utf16()); + auto wName = reinterpret_cast(name.utf16()); + auto wDesc = reinterpret_cast(tooltip.utf16()); + auto wArgs = reinterpret_cast(args.utf16()); + auto wIcon = reinterpret_cast(icon.utf16()); + pShellLink->SetPath(wPath); + pShellLink->SetArguments(wArgs); + pShellLink->SetDescription(wDesc); + pShellLink->SetIconLocation(wIcon, 0); + // Change shell link object name + IPropertyStore *propertyStore = nullptr; + if (SUCCEEDED(pShellLink->QueryInterface(IID_IPropertyStore, (LPVOID *)&propertyStore))) { + PROPVARIANT pv; + if (SUCCEEDED(InitPropVariantFromString(wName, &pv))) { + if (SUCCEEDED(propertyStore->SetValue(PKEY_Title, pv))) + propertyStore->Commit(); + PropVariantClear(&pv); + } + propertyStore->Release(); + } + } + return pShellLink; +} #endif TaskBarNotifier::TaskBarNotifier(QWidget *parent) { d = std::make_unique(Private()); d->setParent(parent); +#ifdef Q_OS_WIN + d->addJumpListItem(); +#endif } TaskBarNotifier::~TaskBarNotifier() = default;