/
ArtNazarov
/
DesktopFileCleaner
Обзор
Документация
Войти
/
ArtNazarov
/
DesktopFileCleaner
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
mainwindow.cpp
510 строк
15 KB
Artem Nazarov
App code added
13 дек 2025, 12:17
13 дек 2025, 12:17
d3301ef
Код
Авторство
О чём код?
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QDesktopServices> #include <QDir> #include <QFile> #include <QTextStream> #include <QMessageBox> #include <QCheckBox> #include <QListWidgetItem> #include <QRegularExpression> #include <QDebug> #include <QFileInfo> #include <QStorageInfo> #include <unistd.h> #include <sys/stat.h> #include <pwd.h> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_process(new QProcess(this)) { ui->setupUi(this); // Connect process signals connect(m_process, &QProcess::readyReadStandardError, this, [this]() { QByteArray error = m_process->readAllStandardError(); qDebug() << "Error:" << error; }); connect(m_process, &QProcess::readyReadStandardOutput, this, [this]() { QByteArray output = m_process->readAllStandardOutput(); qDebug() << "Output:" << output; }); // Set up UI ui->passwordEdit->setEchoMode(QLineEdit::Password); ui->deleteButton->setEnabled(false); // Set default username to current user struct passwd *pw = getpwuid(getuid()); if (pw) { ui->usernameEdit->setText(QString(pw->pw_name)); } } MainWindow::~MainWindow() { delete ui; } QStringList MainWindow::findDesktopFiles() { QStringList desktopFiles; QStringList searchPaths = { QDir::homePath() + "/.local/share/applications", "/usr/share/applications", "/usr/local/share/applications", "/var/lib/flatpak/exports/share/applications", QDir::homePath() + "/.local/share/flatpak/exports/share/applications" }; // Always scan system directories - we'll handle permissions later for (const QString &path : searchPaths) { QDir dir(path); if (dir.exists()) { // Use QDir::NoDotAndDotDot to avoid . and .. entries QStringList files = dir.entryList(QStringList("*.desktop"), QDir::Files | QDir::NoDotAndDotDot, QDir::Name); for (const QString &file : files) { QString fullPath = dir.absoluteFilePath(file); desktopFiles.append(fullPath); } } else { qDebug() << "Directory does not exist or not accessible:" << path; } } return desktopFiles; } QString MainWindow::getExecutablePathWithSudo(const QString &filePath) { if (m_sudoPassword.isEmpty()) { return QString(); } // More robust command to extract Exec path QString command = QString("if [ -f \"%1\" ]; then cat \"%1\" 2>/dev/null | grep -m1 '^Exec=' | cut -d= -f2- | sed -e 's/%%[a-zA-Z]//g' -e 's/ .*//' | head -c -1; fi") .arg(filePath); QString fullCommand = QString("echo '%1' | sudo -S bash -c \"%2\"") .arg(m_sudoPassword) .arg(command.replace("\"", "\\\"")); QProcess proc; proc.start("bash", QStringList() << "-c" << fullCommand); if (!proc.waitForStarted(2000)) { return QString(); } if (!proc.waitForFinished(3000)) { proc.kill(); return QString(); } QString result = QString::fromUtf8(proc.readAllStandardOutput()).trimmed(); return result; } QString MainWindow::getExecutablePath(const QString &desktopFile) { // First try to open normally QFile file(desktopFile); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QString result = getExecutablePathFromFile(file); file.close(); if (!result.isEmpty()) { return result; } } else { qDebug() << "Cannot open file normally:" << desktopFile << "- trying sudo"; } // If normal open failed, try with sudo return getExecutablePathWithSudo(desktopFile); } QString MainWindow::getExecutablePathFromFile(const QFile &file) { QTextStream in(const_cast<QFile*>(&file)); bool inDesktopEntry = false; while (!in.atEnd()) { QString line = in.readLine().trimmed(); if (line.startsWith("[") && line.endsWith("]")) { inDesktopEntry = (line == "[Desktop Entry]"); continue; } if (inDesktopEntry && line.startsWith("Exec=")) { QString execLine = line.mid(5); // Remove % arguments execLine.remove(QRegularExpression("%[a-zA-Z]")); execLine = execLine.trimmed(); // Extract the first part (executable) QStringList parts = execLine.split(' ', Qt::SkipEmptyParts); if (!parts.isEmpty()) { return parts.first(); } } } return QString(); } bool MainWindow::checkExecutableExists(const QString &execPath) { if (execPath.isEmpty()) { return false; } // Check if it's an absolute path if (execPath.startsWith('/')) { QFileInfo execInfo(execPath); if (execInfo.exists() && execInfo.isExecutable()) { return true; } } // Check in PATH QStringList paths = QString(qgetenv("PATH")).split(':'); for (const QString &path : paths) { QFileInfo pathInfo(path + "/" + execPath); if (pathInfo.exists() && pathInfo.isExecutable()) { return true; } } return false; } QString MainWindow::getAppNameWithSudo(const QString &filePath) { if (m_sudoPassword.isEmpty()) { return QString(); } QString command = QString("if [ -f \"%1\" ]; then cat \"%1\" 2>/dev/null | grep -m1 '^Name=' | cut -d= -f2- | head -c -1; fi") .arg(filePath); QString fullCommand = QString("echo '%1' | sudo -S bash -c \"%2\"") .arg(m_sudoPassword) .arg(command.replace("\"", "\\\"")); QProcess proc; proc.start("bash", QStringList() << "-c" << fullCommand); if (!proc.waitForStarted(2000)) { return QString(); } if (!proc.waitForFinished(3000)) { proc.kill(); return QString(); } QString result = QString::fromUtf8(proc.readAllStandardOutput()).trimmed(); return result; } QString MainWindow::getAppNameFromDesktop(const QString &filePath) { QFile file(filePath); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); bool inDesktopEntry = false; while (!in.atEnd()) { QString line = in.readLine().trimmed(); if (line.startsWith("[") && line.endsWith("]")) { inDesktopEntry = (line == "[Desktop Entry]"); continue; } if (inDesktopEntry && line.startsWith("Name=")) { file.close(); return line.mid(5); } } file.close(); } else { // Try with sudo if we can't open normally return getAppNameWithSudo(filePath); } return QString(); } bool MainWindow::canReadFile(const QString &filePath) { QFileInfo fileInfo(filePath); return fileInfo.isReadable(); } bool MainWindow::isDesktopFileBroken(const QString &filePath) { // First check if file exists - try both normal and sudo bool fileExists = false; // Try normal check first if (QFile::exists(filePath)) { fileExists = true; } else { // Try with sudo to check existence if (!m_sudoPassword.isEmpty()) { QString command = QString("if [ -f \"%1\" ]; then echo 'exists'; fi").arg(filePath); QString fullCommand = QString("echo '%1' | sudo -S bash -c \"%2\"") .arg(m_sudoPassword) .arg(command.replace("\"", "\\\"")); QProcess proc; proc.start("bash", QStringList() << "-c" << fullCommand); if (proc.waitForFinished(2000)) { if (!proc.readAllStandardOutput().trimmed().isEmpty()) { fileExists = true; } } } } if (!fileExists) { return true; // File doesn't exist } QString execPath = getExecutablePath(filePath); if (execPath.isEmpty()) { return false; // Skip files without Exec line } return !checkExecutableExists(execPath); } void MainWindow::updateFileList() { ui->fileList->clear(); QStringList desktopFiles = findDesktopFiles(); int brokenCount = 0; // Store current credentials m_sudoPassword = ui->passwordEdit->text(); QString sudoUser = ui->usernameEdit->text(); bool hasSudoCredentials = !sudoUser.isEmpty() && !m_sudoPassword.isEmpty(); if (desktopFiles.isEmpty()) { ui->statusLabel->setText("No desktop files found. Check directory permissions."); return; } qDebug() << "Found" << desktopFiles.size() << "desktop files to check"; for (const QString &filePath : desktopFiles) { bool canReadNormally = canReadFile(filePath); // We can still process the file even if we can't read it normally, // as long as we have sudo credentials if (!canReadNormally && !hasSudoCredentials) { // Skip files we can't read and don't have sudo for continue; } qDebug() << "Checking file:" << filePath << "(readable:" << canReadNormally << ")"; if (isDesktopFileBroken(filePath)) { QString displayName = QFileInfo(filePath).fileName(); // Get application name QString appName; if (canReadNormally) { appName = getAppNameFromDesktop(filePath); } else if (hasSudoCredentials) { appName = getAppNameWithSudo(filePath); } if (!appName.isEmpty()) { displayName = appName + " (" + QFileInfo(filePath).fileName() + ")"; } // Create list widget item with checkbox QListWidgetItem *item = new QListWidgetItem(displayName); item->setData(Qt::UserRole, filePath); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); // Add tooltip with full path and permission info QString tooltip = QString("Path: %1\nPermissions: %2") .arg(filePath) .arg(canReadNormally ? "Readable" : "Requires sudo"); item->setToolTip(tooltip); // Mark root-owned files with different color if (!canReadNormally) { item->setForeground(Qt::darkRed); item->setBackground(Qt::lightGray); } ui->fileList->addItem(item); brokenCount++; qDebug() << "Added broken file:" << filePath; } } ui->statusLabel->setText(QString("Found %1 broken desktop files").arg(brokenCount)); ui->deleteButton->setEnabled(brokenCount > 0); } void MainWindow::on_findButton_clicked() { ui->fileList->clear(); ui->statusLabel->setText("Scanning for desktop files..."); QApplication::processEvents(); updateFileList(); } void MainWindow::on_deleteButton_clicked() { m_sudoPassword = ui->passwordEdit->text(); QString sudoUser = ui->usernameEdit->text(); if (m_sudoPassword.isEmpty() || sudoUser.isEmpty()) { QMessageBox::warning(this, "Warning", "Please enter superuser credentials"); return; } // Ask for confirmation QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "Confirm Deletion", "Are you sure you want to delete selected desktop files?\n\n" "This action cannot be undone.", QMessageBox::Yes | QMessageBox::No); if (reply != QMessageBox::Yes) { return; } // Get selected items int deletedCount = 0; int failedCount = 0; for (int i = 0; i < ui->fileList->count(); ++i) { QListWidgetItem *item = ui->fileList->item(i); if (item->checkState() == Qt::Checked) { QString filePath = item->data(Qt::UserRole).toString(); if (deleteDesktopFile(filePath)) { deletedCount++; } else { failedCount++; } } } // Update list after deletion QString message; if (deletedCount > 0 && failedCount == 0) { message = QString("Successfully deleted %1 desktop file(s)").arg(deletedCount); QMessageBox::information(this, "Success", message); updateFileList(); } else if (deletedCount > 0 && failedCount > 0) { message = QString("Deleted %1 file(s), failed to delete %2 file(s)") .arg(deletedCount).arg(failedCount); QMessageBox::warning(this, "Partial Success", message); updateFileList(); } else if (failedCount > 0) { message = QString("Failed to delete %1 file(s)").arg(failedCount); QMessageBox::critical(this, "Error", message); } } bool MainWindow::runAsRoot(const QString &command) { QString sudoUser = ui->usernameEdit->text(); if (m_sudoPassword.isEmpty() || sudoUser.isEmpty()) { return false; } QString fullCommand = QString("echo '%1' | sudo -S -p '' %2") .arg(m_sudoPassword) .arg(command); m_process->start("bash", QStringList() << "-c" << fullCommand); if (!m_process->waitForStarted()) { return false; } if (!m_process->waitForFinished(5000)) { m_process->kill(); return false; } return (m_process->exitCode() == 0); } bool MainWindow::deleteDesktopFile(const QString &filePath) { // First check if file exists using sudo bool fileExists = false; if (!m_sudoPassword.isEmpty()) { QString checkCmd = QString("if [ -f \"%1\" ]; then echo 'exists'; fi").arg(filePath); QString fullCheckCmd = QString("echo '%1' | sudo -S bash -c \"%2\"") .arg(m_sudoPassword) .arg(checkCmd.replace("\"", "\\\"")); QProcess checkProc; checkProc.start("bash", QStringList() << "-c" << fullCheckCmd); if (checkProc.waitForFinished(2000)) { if (!checkProc.readAllStandardOutput().trimmed().isEmpty()) { fileExists = true; } } } if (!fileExists && !QFile::exists(filePath)) { qDebug() << "File does not exist:" << filePath; return false; } // Try regular delete first if we have write permissions QFileInfo fileInfo(filePath); if (fileInfo.isWritable()) { if (QFile::remove(filePath)) { qDebug() << "Deleted with user permissions:" << filePath; return true; } } // Try with sudo QString modifiedPath = filePath; modifiedPath.replace("\"", "\\\""); QString command = QString("rm -f \"%1\"").arg(modifiedPath); if (runAsRoot(command)) { qDebug() << "Deleted with sudo:" << filePath; return true; } qDebug() << "Failed to delete:" << filePath; return false; } void MainWindow::on_checkBox_stateChanged(int state) { // Select/deselect all checkboxes for (int i = 0; i < ui->fileList->count(); ++i) { QListWidgetItem *item = ui->fileList->item(i); item->setCheckState(static_cast<Qt::CheckState>(state)); } }