2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2018 Mike Tzou
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "foreignapps.h"
36 #include <QCoreApplication>
38 #include <QRegularExpression>
39 #include <QStringList>
45 #include "base/global.h"
46 #include "base/logger.h"
47 #include "base/path.h"
48 #include "base/preferences.h"
49 #include "base/utils/bytearray.h"
51 using namespace Utils::ForeignApps
;
55 bool testPythonInstallation(const QString
&exeName
, PythonInfo
&info
)
60 proc
.start(exeName
, {u
"--version"_s
}, QIODevice::ReadOnly
);
61 if (proc
.waitForFinished() && (proc
.exitCode() == QProcess::NormalExit
))
63 QByteArray procOutput
= proc
.readAllStandardOutput();
64 if (procOutput
.isEmpty())
65 procOutput
= proc
.readAllStandardError();
66 procOutput
= procOutput
.simplified();
68 // Software 'Anaconda' installs its own python interpreter
69 // and `python --version` returns a string like this:
70 // "Python 3.4.3 :: Anaconda 2.3.0 (64-bit)"
71 const QList
<QByteArrayView
> outputSplit
= Utils::ByteArray::splitToViews(procOutput
, " ", Qt::SkipEmptyParts
);
72 if (outputSplit
.size() <= 1)
75 // User reports: `python --version` -> "Python 3.6.6+"
76 // So trim off unrelated characters
77 const auto versionStr
= QString::fromLocal8Bit(outputSplit
[1]);
78 const int idx
= versionStr
.indexOf(QRegularExpression(u
"[^\\.\\d]"_s
));
79 const auto version
= PythonInfo::Version::fromString(versionStr
.left(idx
));
80 if (!version
.isValid())
83 info
= {exeName
, version
};
84 LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Found Python executable. Name: \"%1\". Version: \"%2\"")
85 .arg(info
.executableName
, info
.version
.toString()), Log::INFO
);
100 QStringList
getRegSubkeys(const HKEY handle
)
105 DWORD cMaxSubKeyLen
= 0;
106 LONG res
= ::RegQueryInfoKeyW(handle
, NULL
, NULL
, NULL
, &cSubKeys
, &cMaxSubKeyLen
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
108 if (res
== ERROR_SUCCESS
)
110 ++cMaxSubKeyLen
; // For null character
111 LPWSTR lpName
= new WCHAR
[cMaxSubKeyLen
];
114 for (DWORD i
= 0; i
< cSubKeys
; ++i
)
116 cName
= cMaxSubKeyLen
;
117 res
= ::RegEnumKeyExW(handle
, i
, lpName
, &cName
, NULL
, NULL
, NULL
, NULL
);
118 if (res
== ERROR_SUCCESS
)
119 keys
.push_back(QString::fromWCharArray(lpName
));
128 QString
getRegValue(const HKEY handle
, const QString
&name
= {})
130 const std::wstring nameWStr
= name
.toStdWString();
134 // Discover the size of the value
135 ::RegQueryValueExW(handle
, nameWStr
.c_str(), NULL
, &type
, NULL
, &cbData
);
136 DWORD cBuffer
= (cbData
/ sizeof(WCHAR
)) + 1;
137 LPWSTR lpData
= new WCHAR
[cBuffer
];
138 LONG res
= ::RegQueryValueExW(handle
, nameWStr
.c_str(), NULL
, &type
, reinterpret_cast<LPBYTE
>(lpData
), &cbData
);
141 if (res
== ERROR_SUCCESS
)
143 lpData
[cBuffer
- 1] = 0;
144 result
= QString::fromWCharArray(lpData
);
151 QString
pythonSearchReg(const REG_SEARCH_TYPE type
)
155 hkRoot
= HKEY_CURRENT_USER
;
157 hkRoot
= HKEY_LOCAL_MACHINE
;
159 REGSAM samDesired
= KEY_READ
;
160 if (type
== SYSTEM_32BIT
)
161 samDesired
|= KEY_WOW64_32KEY
;
162 else if (type
== SYSTEM_64BIT
)
163 samDesired
|= KEY_WOW64_64KEY
;
168 res
= ::RegOpenKeyExW(hkRoot
, L
"SOFTWARE\\Python\\PythonCore", 0, samDesired
, &hkPythonCore
);
170 if (res
== ERROR_SUCCESS
)
172 QStringList versions
= getRegSubkeys(hkPythonCore
);
176 while (!found
&& !versions
.empty())
178 const std::wstring version
= QString(versions
.takeLast() + u
"\\InstallPath").toStdWString();
181 res
= ::RegOpenKeyExW(hkPythonCore
, version
.c_str(), 0, samDesired
, &hkInstallPath
);
183 if (res
== ERROR_SUCCESS
)
185 qDebug("Detected possible Python v%ls location", version
.c_str());
186 path
= getRegValue(hkInstallPath
);
187 ::RegCloseKey(hkInstallPath
);
191 const QDir baseDir
{path
};
193 if (baseDir
.exists(u
"python3.exe"_s
))
196 path
= baseDir
.filePath(u
"python3.exe"_s
);
198 else if (baseDir
.exists(u
"python.exe"_s
))
201 path
= baseDir
.filePath(u
"python.exe"_s
);
210 ::RegCloseKey(hkPythonCore
);
216 QString
findPythonPath()
218 QString path
= pythonSearchReg(USER
);
222 path
= pythonSearchReg(SYSTEM_32BIT
);
226 path
= pythonSearchReg(SYSTEM_64BIT
);
230 // Fallback: Detect python from default locations
231 const QFileInfoList dirs
= QDir(u
"C:/"_s
).entryInfoList({u
"Python*"_s
}, QDir::Dirs
, (QDir::Name
| QDir::Reversed
));
232 for (const QFileInfo
&info
: dirs
)
234 const QString py3Path
{info
.absolutePath() + u
"/python3.exe"};
235 if (QFile::exists(py3Path
))
238 const QString pyPath
{info
.absolutePath() + u
"/python.exe"};
239 if (QFile::exists(pyPath
))
248 bool Utils::ForeignApps::PythonInfo::isValid() const
250 return (!executableName
.isEmpty() && version
.isValid());
253 bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
255 return (version
>= Version
{3, 9, 0});
258 PythonInfo
Utils::ForeignApps::pythonInfo()
260 static PythonInfo pyInfo
;
262 const QString preferredPythonPath
= Preferences::instance()->getPythonExecutablePath().toString();
263 if (pyInfo
.isValid() && (preferredPythonPath
== pyInfo
.executableName
))
266 if (!preferredPythonPath
.isEmpty())
268 if (testPythonInstallation(preferredPythonPath
, pyInfo
))
270 LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable. Path: \"%1\".")
271 .arg(preferredPythonPath
), Log::WARNING
);
275 // auto detect only when there are no preferred python path
277 if (!pyInfo
.isValid())
279 if (testPythonInstallation(u
"python3"_s
, pyInfo
))
281 LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python3` executable in PATH environment variable. PATH: \"%1\"")
282 .arg(qEnvironmentVariable("PATH")), Log::INFO
);
284 if (testPythonInstallation(u
"python"_s
, pyInfo
))
286 LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python` executable in PATH environment variable. PATH: \"%1\"")
287 .arg(qEnvironmentVariable("PATH")), Log::INFO
);
289 #if defined(Q_OS_WIN)
290 if (testPythonInstallation(findPythonPath(), pyInfo
))
292 LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python` executable in Windows Registry."), Log::INFO
);
295 LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable"), Log::WARNING
);