From 2eef14a14d42f3b967b4a43b618d47c6689e1efa Mon Sep 17 00:00:00 2001 From: El Thoro Date: Sun, 2 Oct 2022 15:11:08 +0200 Subject: [PATCH] Fix OpenWith on Windows (#1309) (#2801) * Fix OpenWith on Windows (#1309) * Clang-format and function name fix * Clang-format --- data/graphics.qrc | 4 + data/img/material/black/apps.svg | 1 + data/img/material/black/image.svg | 1 + data/img/material/white/apps.svg | 1 + data/img/material/white/image.svg | 1 + src/tools/launcher/applauncherwidget.cpp | 50 ++++++- src/tools/launcher/applauncherwidget.h | 11 +- src/utils/CMakeLists.txt | 9 +- src/utils/desktopfileparse.cpp | 6 +- src/utils/winlnkfileparse.cpp | 160 +++++++++++++++++++++++ src/utils/winlnkfileparse.h | 39 ++++++ src/widgets/capture/capturewidget.cpp | 3 + 12 files changed, 276 insertions(+), 10 deletions(-) create mode 100644 data/img/material/black/apps.svg create mode 100644 data/img/material/black/image.svg create mode 100644 data/img/material/white/apps.svg create mode 100644 data/img/material/white/image.svg create mode 100644 src/utils/winlnkfileparse.cpp create mode 100644 src/utils/winlnkfileparse.h diff --git a/data/graphics.qrc b/data/graphics.qrc index 06ef24f4..fd1d93d1 100644 --- a/data/graphics.qrc +++ b/data/graphics.qrc @@ -95,5 +95,9 @@ img/material/white/move_down.svg img/material/white/move_up.svg img/material/white/delete.svg + img/material/black/apps.svg + img/material/black/image.svg + img/material/white/apps.svg + img/material/white/image.svg diff --git a/data/img/material/black/apps.svg b/data/img/material/black/apps.svg new file mode 100644 index 00000000..be408313 --- /dev/null +++ b/data/img/material/black/apps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/img/material/black/image.svg b/data/img/material/black/image.svg new file mode 100644 index 00000000..f06871c5 --- /dev/null +++ b/data/img/material/black/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/img/material/white/apps.svg b/data/img/material/white/apps.svg new file mode 100644 index 00000000..6010350f --- /dev/null +++ b/data/img/material/white/apps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/img/material/white/image.svg b/data/img/material/white/image.svg new file mode 100644 index 00000000..2ad6b8c5 --- /dev/null +++ b/data/img/material/white/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/tools/launcher/applauncherwidget.cpp b/src/tools/launcher/applauncherwidget.cpp index d348b1c9..8a202c89 100644 --- a/src/tools/launcher/applauncherwidget.cpp +++ b/src/tools/launcher/applauncherwidget.cpp @@ -18,10 +18,15 @@ #include #include #include +#include #include namespace { - +#if defined(Q_OS_WIN) +QMap catIconNames({ { "Graphics", "image.svg" }, + { "Utility", "apps.svg" } }); +} +#else QMap catIconNames( { { "Multimedia", "applications-multimedia" }, { "Development", "applications-development" }, @@ -33,6 +38,7 @@ QMap catIconNames( { "System", "preferences-system" }, { "Utility", "applications-utilities" } }); } +#endif AppLauncherWidget::AppLauncherWidget(const QPixmap& p, QWidget* parent) : QWidget(parent) @@ -44,6 +50,18 @@ AppLauncherWidget::AppLauncherWidget(const QPixmap& p, QWidget* parent) m_keepOpen = ConfigHandler().keepOpenAppLauncher(); +#if defined(Q_OS_WIN) + QDir userAppsFolder( + QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation) + .at(0)); + m_parser.processDirectory(userAppsFolder); + + QString dir(m_parser.getAllUsersStartMenuPath()); + if (!dir.isEmpty()) { + QDir allUserAppsFolder(dir); + m_parser.processDirectory(allUserAppsFolder); + } +#else QString dirLocal = QDir::homePath() + "/.local/share/applications/"; QDir appsDirLocal(dirLocal); m_parser.processDirectory(appsDirLocal); @@ -51,6 +69,7 @@ AppLauncherWidget::AppLauncherWidget(const QPixmap& p, QWidget* parent) QString dir = QStringLiteral("/usr/share/applications/"); QDir appsDir(dir); m_parser.processDirectory(appsDir); +#endif initAppMap(); initListWidget(); @@ -99,7 +118,14 @@ void AppLauncherWidget::launch(const QModelIndex& index) // Heuristically, if there is a % in the command we assume it is the file // name slot QString command = index.data(Qt::UserRole).toString(); +#if defined(Q_OS_WIN) + // Do not split on Windows, since file path can contain spaces + // and % is not used in lnk files + QStringList prog_args; + prog_args << command; +#else QStringList prog_args = command.split(" "); +#endif // no quotes because it is going in an array! if (command.contains("%")) { // but that means we need to substitute IN the array not the string! @@ -121,8 +147,10 @@ void AppLauncherWidget::launch(const QModelIndex& index) this, tr("Error"), tr("Unable to launch in terminal.")); } } else { + QFileInfo fi(m_tempFile); + QString workingDir = fi.absolutePath(); prog_args.removeAt(0); // strip program name out - QProcess::startDetached(app_name, prog_args); + QProcess::startDetached(app_name, prog_args, workingDir); } if (!m_keepOpen) { close(); @@ -185,8 +213,17 @@ void AppLauncherWidget::initListWidget() const QVector& appList = m_appsMap[cat]; addAppsToListWidget(itemsWidget, appList); +#if defined(Q_OS_WIN) + QColor background = this->palette().window().color(); + bool isDark = ColorUtils::colorIsDark(background); + QString modifier = + isDark ? PathInfo::whiteIconPath() : PathInfo::blackIconPath(); + m_tabWidget->addTab( + itemsWidget, QIcon(modifier + iconName), QLatin1String("")); +#else m_tabWidget->addTab( itemsWidget, QIcon::fromTheme(iconName), QLatin1String("")); +#endif m_tabWidget->setTabToolTip(m_tabWidget->count(), cat); if (cat == QLatin1String("Graphics")) { m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); @@ -215,18 +252,21 @@ void AppLauncherWidget::initAppMap() QStringList multimediaNames; multimediaNames << QStringLiteral("AudioVideo") << QStringLiteral("Audio") << QStringLiteral("Video"); - for (const QString& name : multimediaNames) { + for (const QString& name : qAsConst(multimediaNames)) { if (!m_appsMap.contains(name)) { continue; } - for (auto i : m_appsMap[name]) { + for (const auto& i : m_appsMap[name]) { if (!multimediaList.contains(i)) { multimediaList.append(i); } } m_appsMap.remove(name); } - m_appsMap.insert(QStringLiteral("Multimedia"), multimediaList); + + if (!multimediaList.isEmpty()) { + m_appsMap.insert(QStringLiteral("Multimedia"), multimediaList); + } } void AppLauncherWidget::configureListView(QListWidget* widget) diff --git a/src/tools/launcher/applauncherwidget.h b/src/tools/launcher/applauncherwidget.h index 65a31f3c..7ed463a1 100644 --- a/src/tools/launcher/applauncherwidget.h +++ b/src/tools/launcher/applauncherwidget.h @@ -3,10 +3,15 @@ #pragma once -#include "src/utils/desktopfileparse.h" #include #include +#if defined(Q_OS_WIN) +#include "src/utils/winlnkfileparse.h" +#else +#include "src/utils/desktopfileparse.h" +#endif + class QTabWidget; class QCheckBox; class QVBoxLayout; @@ -32,7 +37,11 @@ private: const QVector& appList); void keyPressEvent(QKeyEvent* keyEvent) override; +#if defined(Q_OS_WIN) + WinLnkFileParser m_parser; +#else DesktopFileParser m_parser; +#endif QPixmap m_pixmap; QString m_tempFile; bool m_keepOpen; diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 1f787674..c6d23276 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -26,5 +26,12 @@ target_sources( colorutils.cpp history.cpp strfparse.cpp - request.cpp + request.cpp ) + +IF (WIN32) + target_sources( + flameshot + PRIVATE winlnkfileparse.cpp + ) +ENDIF() diff --git a/src/utils/desktopfileparse.cpp b/src/utils/desktopfileparse.cpp index c1a703b6..b04163d8 100644 --- a/src/utils/desktopfileparse.cpp +++ b/src/utils/desktopfileparse.cpp @@ -114,7 +114,7 @@ int DesktopFileParser::processDirectory(const QDir& dir) dir.entryList({ "*.desktop" }, QDir::NoDotAndDotDot | QDir::Files); bool ok; int length = m_appList.length(); - for (QString file : entries) { + for (const QString& file : entries) { DesktopAppData app = parseDesktopFile(dir.absoluteFilePath(file), ok); if (ok) { m_appList.append(app); @@ -127,7 +127,7 @@ QVector DesktopFileParser::getAppsByCategory( const QString& category) { QVector res; - for (const DesktopAppData& app : m_appList) { + for (const DesktopAppData& app : qAsConst(m_appList)) { if (app.categories.contains(category)) { res.append(app); } @@ -139,7 +139,7 @@ QMap> DesktopFileParser::getAppsByCategory( const QStringList& categories) { QMap> res; - for (const DesktopAppData& app : m_appList) { + for (const DesktopAppData& app : qAsConst(m_appList)) { for (const QString& category : categories) { if (app.categories.contains(category)) { res[category].append(app); diff --git a/src/utils/winlnkfileparse.cpp b/src/utils/winlnkfileparse.cpp new file mode 100644 index 00000000..9cda3c37 --- /dev/null +++ b/src/utils/winlnkfileparse.cpp @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors + +#include "winlnkfileparse.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +WinLnkFileParser::WinLnkFileParser() +{ + QStringList sListImgFileExt; + for (const auto& ext : QImageWriter::supportedImageFormats()) { + sListImgFileExt.append(ext); + } + this->getImageFileExtAssociates(sListImgFileExt); +} + +DesktopAppData WinLnkFileParser::parseLnkFile(const QFileInfo& fiLnk, + bool& ok) const +{ + DesktopAppData res; + ok = true; + + QFileInfo fiSymlink(fiLnk.symLinkTarget()); + if (!fiSymlink.exists() || !fiSymlink.fileName().endsWith(".exe") || + fiSymlink.baseName().contains("unins")) { + ok = false; + return res; + } + + res.name = fiLnk.baseName(); + res.exec = fiSymlink.absoluteFilePath(); + + // Get icon from exe + QFileSystemModel* model = new QFileSystemModel; + model->setRootPath(fiSymlink.path()); + res.icon = model->fileIcon(model->index(fiSymlink.filePath())); + + if (m_GraphicAppsList.contains(fiSymlink.fileName())) { + res.categories = QStringList() << "Graphics"; + } else { + res.categories = QStringList() << "Utility"; + } + + for (const auto& app : m_appList) { + if (app.exec == res.exec) { + ok = false; + break; + } + } + + if (res.exec.isEmpty() || res.name.isEmpty()) { + ok = false; + } + return res; +} + +int WinLnkFileParser::processDirectory(const QDir& dir) +{ + QStringList sListMenuFilter; + sListMenuFilter << "Accessibility" + << "Administrative Tools" + << "Setup" + << "System Tools" + << "Uninstall" + << "Update" + << "Updater" + << "Windows PowerShell"; + const QString sMenuFilter("\\b(" + sListMenuFilter.join('|') + ")\\b"); + QRegularExpression regexfilter(sMenuFilter); + + bool ok; + int length = m_appList.length(); + // Go through all subfolders and *.lnk files + QDirIterator it(dir.absolutePath(), + { "*.lnk" }, + QDir::NoFilter, + QDirIterator::Subdirectories); + while (it.hasNext()) { + QFileInfo fiLnk(it.next()); + if (!regexfilter.match(fiLnk.absoluteFilePath()).hasMatch()) { + DesktopAppData app = parseLnkFile(fiLnk, ok); + if (ok) { + m_appList.append(app); + } + } + } + + return m_appList.length() - length; +} + +QVector WinLnkFileParser::getAppsByCategory( + const QString& category) +{ + QVector res; + for (const DesktopAppData& app : qAsConst(m_appList)) { + if (app.categories.contains(category)) { + res.append(app); + } + } + + std::sort(res.begin(), res.end(), CompareAppByName()); + + return res; +} + +QMap> WinLnkFileParser::getAppsByCategory( + const QStringList& categories) +{ + QMap> res; + + QVector tmpAppList; + for (const QString& category : categories) { + tmpAppList = getAppsByCategory(category); + for (const DesktopAppData& app : qAsConst(tmpAppList)) { + res[category].append(app); + } + } + + return res; +} + +QString WinLnkFileParser::getAllUsersStartMenuPath() +{ + QString sRet(""); + WCHAR path[MAX_PATH]; + HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, path); + + if (SUCCEEDED(hr)) { + sRet = QDir(QString::fromWCharArray(path)).absolutePath(); + } + + return sRet; +} + +void WinLnkFileParser::getImageFileExtAssociates(const QStringList& sListImgExt) +{ + const QString sReg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\" + "CurrentVersion\\Explorer\\FileExts\\.%1\\OpenWithList"); + + for (const auto& sExt : qAsConst(sListImgExt)) { + QString sPath(sReg.arg(sExt)); + QSettings registry(sPath, QSettings::NativeFormat); + for (const auto& key : registry.allKeys()) { + if (1 == key.size()) { // Keys for OpenWith apps are a, b, c, ... + QString sVal = registry.value(key, "").toString(); + if (sVal.endsWith(".exe") && + !m_GraphicAppsList.contains(sVal)) { + m_GraphicAppsList << sVal; + } + } + } + } +} diff --git a/src/utils/winlnkfileparse.h b/src/utils/winlnkfileparse.h new file mode 100644 index 00000000..1177bebc --- /dev/null +++ b/src/utils/winlnkfileparse.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors + +#pragma once + +#include "desktopfileparse.h" +#include +#include +#include +#include + +class QDir; +class QString; + +struct CompareAppByName +{ + bool operator()(const DesktopAppData a, const DesktopAppData b) + { + return (a.name < b.name); + } +}; + +struct WinLnkFileParser +{ + WinLnkFileParser(); + DesktopAppData parseLnkFile(const QFileInfo& fiLnk, bool& ok) const; + int processDirectory(const QDir& dir); + QString getAllUsersStartMenuPath(); + + QVector getAppsByCategory(const QString& category); + QMap> getAppsByCategory( + const QStringList& categories); + +private: + void getImageFileExtAssociates(const QStringList& sListImgExt); + + QVector m_appList; + QStringList m_GraphicAppsList; +}; diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 3749784f..b4a9da24 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -118,9 +118,12 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, m_context.origScreenshot = m_context.screenshot; #if defined(Q_OS_WIN) +// Call cmake with -DFLAMESHOT_DEBUG_CAPTURE=ON to enable easier debugging +#if !defined(FLAMESHOT_DEBUG_CAPTURE) setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::SubWindow // Hides the taskbar icon ); +#endif for (QScreen* const screen : QGuiApplication::screens()) { QPoint topLeftScreen = screen->geometry().topLeft();