15 #include <QInputDialog>
16 #include <QMessageBox>
17 #include <QPlainTextEdit>
19 #include <QTableWidgetItem>
27 #include <ui_InstallDialog.h>
31 #if defined(Q_OS_WIN32)
35 #if defined(Q_OS_WIN32)
36 static BOOL GetFolderRights(LPCTSTR folderName, DWORD genericAccessRights, DWORD *grantedRights)
40 constexpr SECURITY_INFORMATION requestedInformation =
41 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
44 GetFileSecurity(folderName, requestedInformation,
nullptr,
NULL, &
length);
46 if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
51 PSECURITY_DESCRIPTOR security = LocalAlloc(LPTR,
length);
52 if (GetFileSecurity(folderName, requestedInformation, security,
length, &
length) ==
FALSE)
58 DWORD desiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ;
59 HANDLE hToken =
nullptr;
60 if (OpenProcessToken(GetCurrentProcess(), desiredAccess, &hToken) ==
FALSE)
66 HANDLE hImpersonatedToken =
nullptr;
67 if (DuplicateToken(hToken, SecurityImpersonation, &hImpersonatedToken) ==
FALSE)
74 GENERIC_MAPPING mapping = {0xFFFFFFFF};
75 PRIVILEGE_SET privileges = {0};
76 DWORD grantedAccess = 0, privilegesLength =
sizeof(privileges);
79 mapping.GenericRead = FILE_GENERIC_READ;
80 mapping.GenericWrite = FILE_GENERIC_WRITE;
81 mapping.GenericExecute = FILE_GENERIC_EXECUTE;
82 mapping.GenericAll = FILE_ALL_ACCESS;
84 MapGenericMask(&genericAccessRights, &mapping);
85 if (AccessCheck(security,
94 CloseHandle(hImpersonatedToken);
100 *grantedRights = grantedAccess;
102 CloseHandle(hImpersonatedToken);
109 static bool HasReadWriteAccessToFolder(
const QString &folderPath)
111 constexpr DWORD access_mask = MAXIMUM_ALLOWED;
114 const std::wstring str = folderPath.toStdWString();
115 BOOL ret = GetFolderRights(str.c_str(), access_mask, &grant);
117 const std::string str = folderPath.toStdString();
118 BOOL ret = GetFolderRights(str.c_str(), access_mask, &grant);
123 plgWarning() <<
"Failed to get access rights for path '" << folderPath <<
'\n';
127 bool hasRead =
false;
128 if (((grant & GENERIC_READ) == GENERIC_READ) ||
129 ((grant & FILE_GENERIC_READ) == FILE_GENERIC_READ))
134 bool hasWrite =
false;
135 if (((grant & GENERIC_WRITE) == GENERIC_WRITE) ||
136 ((grant & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE))
141 bool hasExecute =
false;
142 if (((grant & GENERIC_EXECUTE) == GENERIC_EXECUTE) ||
143 ((grant & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE))
147 return hasRead && hasWrite && hasExecute;
155 explicit InstallDialog(QWidget *parent =
nullptr) : QDialog(parent), m_ui(new Ui_InstallDialog)
161 return m_ui->forceCheckBox;
165 return m_ui->updateCheckBox;
169 return m_ui->packageNameEdit->text();
173 Ui_InstallDialog *m_ui;
182 : QDialog(parent), m_display(new QPlainTextEdit(this))
184 setWindowTitle(
"pip output");
185 m_display->setReadOnly(
true);
186 auto *widgetLayout =
new QVBoxLayout;
187 widgetLayout->addWidget(m_display);
188 setLayout(widgetLayout);
194 m_display->appendPlainText(text);
203 QPlainTextEdit *m_display;
208 m_ui(new Ui_PackageManager),
209 m_pythonProcess(new QProcess),
211 m_shouldUseUserOption(false)
214 connect(m_pythonProcess, &QProcess::started, [
this]() { setBusy(
true); });
215 connect(m_pythonProcess,
216 static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
217 [
this](
int, QProcess::ExitStatus) { setBusy(
false); });
221 m_ui->installedPackagesView->setColumnCount(2);
222 m_ui->installedPackagesView->setEditTriggers(QAbstractItemView::NoEditTriggers);
223 m_ui->installedPackagesView->setHorizontalHeaderLabels({
"Package Name",
"Version"});
224 m_ui->installedPackagesView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
225 connect(m_ui->installedPackagesView,
226 &QTableWidget::itemSelectionChanged,
228 &PackageManager::handleSelectionChanged);
230 m_ui->uninstallBtn->setEnabled(
false);
231 connect(m_ui->installBtn, &QPushButton::clicked,
this, &PackageManager::handleInstallPackage);
233 m_ui->uninstallBtn, &QPushButton::clicked,
this, &PackageManager::handleUninstallPackage);
235 connect(m_ui->searchBar, &QLineEdit::returnPressed,
this, &PackageManager::handleSearch);
237 refreshInstalledPackagesList();
239 m_ui->installBtn->setEnabled(
true);
240 m_ui->messageFrame->hide();
242 switch (config.
type())
253 #if defined Q_OS_WIN32
256 const bool hasEnoughRights = HasReadWriteAccessToFolder(config.
pythonHome());
259 const bool hasEnoughRights = dirInfo.isWritable();
261 if (!hasEnoughRights)
263 m_ui->installBtn->setEnabled(
false);
264 m_ui->messageIconLabel->setPixmap(QApplication::style()
265 ->standardIcon(QStyle::SP_MessageBoxCritical)
267 m_ui->messageTextLabel->setText(
"Admin rights are required to be able to install "
268 "packages in the current environment");
269 m_ui->messageFrame->show();
274 m_shouldUseUserOption =
true;
279 void PackageManager::refreshInstalledPackagesList()
281 m_pythonProcess->setProcessChannelMode(QProcess::SeparateChannels);
283 const QStringList arguments = {
"-m",
"pip",
"list"};
284 m_pythonProcess->setArguments(arguments);
289 static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
293 m_pythonProcess->start(QIODevice::ReadOnly);
294 if (m_pythonProcess->state() != QProcess::ProcessState::Starting &&
295 m_pythonProcess->state() != QProcess::ProcessState::Running)
302 if (m_pythonProcess->exitStatus() != QProcess::ExitStatus::NormalExit)
304 const QString errorMsg =
305 QString(
"Failed to list installed packages: '%1'").arg(m_pythonProcess->errorString());
306 QMessageBox::critical(
this,
"Package Manager Error", errorMsg);
310 const QString output =
319 if (lines.size() <= 3)
324 m_ui->installedPackagesView->setRowCount(lines.size() - 3);
326 for (
int i{2}; i < lines.size() - 1; ++i)
328 const auto ¤tLine = lines[i];
331 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
332 QRegularExpressionMatch match = regex.match(currentLine.toString());
333 if (match.hasMatch())
335 for (
int j = 1; j < 3; ++j)
337 const QString lol = match.captured(j);
339 int pos = regex.indexIn(currentLine.toString());
342 for (
int j = 1; j < 3; ++j)
344 const QString lol = regex.cap(j);
346 auto *thing =
new QTableWidgetItem(lol);
349 thing->setFlags(thing->flags() & ~Qt::ItemFlag::ItemIsSelectable);
351 m_ui->installedPackagesView->setItem(i - 2, j - 1, thing);
356 const QString errorOutput =
359 if (!errorOutput.isEmpty())
364 m_pythonProcess->setProcessChannelMode(QProcess::MergedChannels);
367 void PackageManager::handleInstallPackage()
370 if (installDialog.exec() != QDialog::Accepted)
375 const QString packageName = installDialog.packageName();
377 if (packageName.isEmpty())
382 QStringList arguments = {
"-m",
"pip",
"install", packageName};
383 if (installDialog.force())
385 arguments.push_back(
"--force");
387 if (installDialog.upgrade())
389 arguments.push_back(
"--upgrade");
391 if (m_shouldUseUserOption)
393 arguments.push_back(
"--user");
395 executeCommand(arguments);
397 if (m_pythonProcess->exitCode() != 0)
399 CVLog::Error(
"Failed to run install commands", m_pythonProcess->error());
402 refreshInstalledPackagesList();
405 void PackageManager::handleUninstallPackage()
407 const QList<QTableWidgetItem *> selectedItems = m_ui->installedPackagesView->selectedItems();
409 if (selectedItems.isEmpty())
414 for (
const QTableWidgetItem *item : selectedItems)
416 const QString packageName = item->text();
417 QMessageBox::StandardButton choice = QMessageBox::question(
418 this,
"Confirm", QString(
"Do you really want to uninstall: '%1' ?").arg(packageName));
420 if (choice != QMessageBox::StandardButton::Yes)
424 const QStringList arguments = {
"-m",
"pip",
"uninstall",
"--yes", packageName};
425 executeCommand(arguments);
427 if (m_pythonProcess->exitCode() != 0)
429 CVLog::Error(
"Failed to run uninstall commands", m_pythonProcess->error());
433 refreshInstalledPackagesList();
436 void PackageManager::handleSearch()
438 const QString searchString = m_ui->searchBar->text();
439 QTableWidget *table = m_ui->installedPackagesView;
441 if (searchString.isEmpty())
443 for (
int i = 0; i < table->rowCount(); ++i)
445 table->setRowHidden(i,
false);
450 for (
int i = 0; i < table->rowCount(); ++i)
453 for (
int j = 0; j < table->columnCount(); ++j)
455 QTableWidgetItem *item = table->item(i, j);
456 if (item->text().contains(searchString))
462 table->setRowHidden(i, !match);
467 void PackageManager::executeCommand(
const QStringList &arguments)
469 m_outputDialog->show();
470 m_outputDialog->
clear();
471 m_pythonProcess->setArguments(arguments);
472 m_pythonProcess->start(QIODevice::ReadOnly);
474 while (m_pythonProcess->state() != QProcess::ProcessState::NotRunning)
476 if (m_pythonProcess->waitForReadyRead())
478 const QString output =
481 QApplication::processEvents();
484 m_outputDialog->exec();
487 void PackageManager::handleSelectionChanged()
489 m_ui->uninstallBtn->setEnabled(!m_ui->installedPackagesView->selectedItems().isEmpty());
492 void PackageManager::setBusy(
bool isBusy)
494 m_ui->installBtn->setEnabled(!isBusy);
495 m_ui->uninstallBtn->setEnabled(!isBusy);
503 #include "PackageManager.moc"
QtCompatTextCodec * qtCompatCodecForName(const char *name)
QtCompatStringRefList qtCompatSplitRefChar(const QString &str, QChar sep)
QRegularExpression QtCompatRegExp
#define PACKAGE_MANAGER_ICON_PATH
PluginLogger< CVLog::LOG_WARNING > plgWarning
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
static bool Error(const char *format,...)
Display an error dialog with formatted message.
void appendPlainText(const QString &text) const
CommandOutputDialog(QWidget *parent=nullptr)
InstallDialog(QWidget *parent=nullptr)
QString packageName() const
PackageManager(const PythonConfig &config, QWidget *parent=nullptr)
~PackageManager() noexcept override
const QString & pythonHome() const
void preparePythonProcess(QProcess &pythonProcess) const
QString toUnicode(const char *chars, int len=-1)
__host__ __device__ float length(float2 v)