Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwindowsservices.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwindowsservices.h"
5#include <QtCore/qt_windows.h>
6
7#include <QtCore/qurl.h>
8#include <QtCore/qdebug.h>
9#include <QtCore/qdir.h>
10#include <QtCore/qscopedpointer.h>
11#include <QtCore/qthread.h>
12
13#include <QtCore/private/qwinregistry_p.h>
14#include <QtCore/private/qfunctions_win_p.h>
15
16#include <shlobj.h>
17#include <shlwapi.h>
18#include <intshcut.h>
19
21
22using namespace Qt::StringLiterals;
23
24enum { debug = 0 };
25
27{
28public:
29 explicit QWindowsShellExecuteThread(const wchar_t *operation, const wchar_t *file,
30 const wchar_t *parameters)
31 : m_operation(operation)
32 , m_file(file)
33 , m_parameters(parameters) { }
34
35 void run() override
36 {
37 QComHelper comHelper;
38 if (comHelper.isValid())
39 m_result = ShellExecute(nullptr, m_operation, m_file, m_parameters, nullptr,
40 SW_SHOWNORMAL);
41 }
42
43 HINSTANCE result() const { return m_result; }
44
45private:
46 HINSTANCE m_result = nullptr;
47 const wchar_t *m_operation;
48 const wchar_t *m_file;
49 const wchar_t *m_parameters;
50};
51
53{
55 QTextStream(&result) <<"ShellExecute '" << url.toString() << "' failed (error " << code << ").";
56 return result;
57}
58
59// Retrieve the web browser and open the URL. This should be used for URLs with
60// fragments which don't work when using ShellExecute() directly (QTBUG-14460,
61// QTBUG-55300).
62static bool openWebBrowser(const QUrl &url)
63{
64 WCHAR browserExecutable[MAX_PATH] = {};
65 const wchar_t operation[] = L"open";
66 DWORD browserExecutableSize = MAX_PATH;
67 if (FAILED(AssocQueryString(0, ASSOCSTR_EXECUTABLE, L"http", operation,
68 browserExecutable, &browserExecutableSize))) {
69 return false;
70 }
71 QString browser = QString::fromWCharArray(browserExecutable, browserExecutableSize - 1);
72 // Workaround for "old" MS Edge entries. Instead of LaunchWinApp.exe we can just use msedge.exe
73 if (browser.contains("LaunchWinApp.exe"_L1, Qt::CaseInsensitive))
74 browser = "msedge.exe"_L1;
76
77 // Run ShellExecute() in a thread since it may spin the event loop.
78 // Prevent it from interfering with processing of posted events (QTBUG-85676).
79 QWindowsShellExecuteThread thread(operation,
80 reinterpret_cast<const wchar_t *>(browser.utf16()),
81 reinterpret_cast<const wchar_t *>(urlS.utf16()));
82 thread.start();
83 thread.wait();
84
85 const auto result = reinterpret_cast<quintptr>(thread.result());
86 if (debug)
87 qDebug() << __FUNCTION__ << urlS << QString::fromWCharArray(browserExecutable) << result;
88 // ShellExecute returns a value greater than 32 if successful
89 if (result <= 32) {
91 return false;
92 }
93 return true;
94}
95
96static inline bool shellExecute(const QUrl &url)
97{
98 const QString nativeFilePath = url.isLocalFile() && !url.hasFragment() && !url.hasQuery()
101
102
103 // Run ShellExecute() in a thread since it may spin the event loop.
104 // Prevent it from interfering with processing of posted events (QTBUG-85676).
105 QWindowsShellExecuteThread thread(nullptr,
106 reinterpret_cast<const wchar_t *>(nativeFilePath.utf16()),
107 nullptr);
108 thread.start();
109 thread.wait();
110
111 const auto result = reinterpret_cast<quintptr>(thread.result());
112
113 // ShellExecute returns a value greater than 32 if successful
114 if (result <= 32) {
116 return false;
117 }
118 return true;
119}
120
121// Retrieve the commandline for the default mail client. It contains a
122// placeholder %1 for the URL. The default key used below is the
123// command line for the mailto: shell command.
124static inline QString mailCommand()
125{
126 enum { BufferSize = sizeof(wchar_t) * MAX_PATH };
127
128 const wchar_t mailUserKey[] = L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\mailto\\UserChoice";
129
130 // Check if user has set preference, otherwise use default.
131 QString keyName = QWinRegistryKey(HKEY_CURRENT_USER, mailUserKey)
132 .stringValue( L"Progid");
133 const auto mailto = keyName.isEmpty() ? "mailto"_L1 : QLatin1StringView();
134 keyName += mailto + "\\Shell\\Open\\Command"_L1;
135 if (debug)
136 qDebug() << __FUNCTION__ << "keyName=" << keyName;
137 const QString command = QWinRegistryKey(HKEY_CLASSES_ROOT, keyName).stringValue(L"");
138 // QTBUG-57816: As of Windows 10, if there is no mail client installed, an entry like
139 // "rundll32.exe .. url.dll,MailToProtocolHandler %l" is returned. Launching it
140 // silently fails or brings up a broken dialog after a long time, so exclude it and
141 // fall back to ShellExecute() which brings up the URL association dialog.
142 if (command.isEmpty() || command.contains(u",MailToProtocolHandler"))
143 return QString();
144 wchar_t expandedCommand[MAX_PATH] = {0};
145 return ExpandEnvironmentStrings(reinterpret_cast<const wchar_t *>(command.utf16()),
146 expandedCommand, MAX_PATH)
147 ? QString::fromWCharArray(expandedCommand) : command;
148}
149
150static inline bool launchMail(const QUrl &url)
151{
152 QString command = mailCommand();
153 if (command.isEmpty()) {
154 qWarning("Cannot launch '%ls': There is no mail program installed.", qUtf16Printable(url.toString()));
155 return false;
156 }
157 // Fix mail launch if no param is expected in this command.
158 if (command.indexOf("%1"_L1) < 0) {
159 qWarning() << "The mail command lacks the '%1' parameter.";
160 return false;
161 }
162 //Make sure the path for the process is in quotes
163 const QChar doubleQuote = u'"';
164 if (!command.startsWith(doubleQuote)) {
165 const int exeIndex = command.indexOf(".exe "_L1, 0, Qt::CaseInsensitive);
166 if (exeIndex != -1) {
167 command.insert(exeIndex + 4, doubleQuote);
168 command.prepend(doubleQuote);
169 }
170 }
171 // Pass the url as the parameter. Should use QProcess::startDetached(),
172 // but that cannot handle a Windows command line [yet].
173 command.replace("%1"_L1, url.toString(QUrl::FullyEncoded));
174 if (debug)
175 qDebug() << __FUNCTION__ << "Launching" << command;
176 //start the process
177 PROCESS_INFORMATION pi;
178 ZeroMemory(&pi, sizeof(pi));
179 STARTUPINFO si;
180 ZeroMemory(&si, sizeof(si));
181 si.cb = sizeof(si);
182 if (!CreateProcess(nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(command.utf16())),
183 nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
184 qErrnoWarning("Unable to launch '%ls'", qUtf16Printable(command));
185 return false;
186 }
187 CloseHandle(pi.hProcess);
188 CloseHandle(pi.hThread);
189 return true;
190}
191
193{
194 const QString scheme = url.scheme();
195 if (scheme == u"mailto" && launchMail(url))
196 return true;
197 return url.isLocalFile() && url.hasFragment()
199}
200
202{
203 return shellExecute(url);
204}
205
\inmodule QtCore
static QString toNativeSeparators(const QString &pathName)
Definition qdir.cpp:929
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
const ushort * utf16() const
Returns the QString as a '\0\'-terminated array of unsigned shorts.
Definition qstring.cpp:6995
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3132
bool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.h:1369
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1309
QString & prepend(QChar c)
Definition qstring.h:478
\inmodule QtCore
void start(Priority=InheritPriority)
Definition qthread.cpp:996
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
\inmodule QtCore
Definition qurl.h:94
bool hasQuery() const
Definition qurl.cpp:2513
bool isLocalFile() const
Definition qurl.cpp:3445
bool hasFragment() const
Definition qurl.cpp:2700
QString scheme() const
Returns the scheme of the URL.
Definition qurl.cpp:1991
@ FullyEncoded
Definition qurl.h:129
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2831
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3425
QString stringValue(QStringView subKey) const
bool openUrl(const QUrl &url) override
bool openDocument(const QUrl &url) override
QWindowsShellExecuteThread(const wchar_t *operation, const wchar_t *file, const wchar_t *parameters)
void qErrnoWarning(const char *msg,...)
Combined button and popup list for selecting options.
@ CaseInsensitive
static constexpr int BufferSize
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
GLuint64EXT * result
[6]
static QString keyName(const QString &rKey)
#define MAX_PATH
#define qPrintable(string)
Definition qstring.h:1531
#define qUtf16Printable(string)
Definition qstring.h:1543
size_t quintptr
Definition qtypes.h:167
unsigned short ushort
Definition qtypes.h:33
static bool launchMail(const QUrl &url)
static QString msgShellExecuteFailed(const QUrl &url, quintptr code)
static QString mailCommand()
static bool shellExecute(const QUrl &url)
static bool openWebBrowser(const QUrl &url)
QFile file
[0]
QUrl url("example.com")
[constructor-url-reference]