Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
- [x] Add chaotic aur to search tab if enabled
- [x] Add launch button on package card if package is installed and remove when uninstalled
- [] Add detailed mirrorlist tab to set mirrorlist
- [] Add version information
- []

- [x] Add version information
- [] Implement an AUR helper in core to remove dependence on paru and yay
- [] Ask password only once on startup - startup_auth
- [] Improve settings page - settings_tab
- [] Move all styles to single stylesheet - style_and_theme
- [] Clean up UI; make UI look more modern (check gnome's styling options) - style_and_theme
- [] Set a light/dark theme toggle, or follow system's theme - style_and_theme
- [] Look into spdlog for logging
- [] Look into CppUTest or Google Test (gtest) for test

## Future Enhancements

Expand Down
10 changes: 5 additions & 5 deletions assets/alg-app-store.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
Type=Application
Version=1.0
Name=App Store
GenericName=ALG - App Store
Keywords=utility;system;welcome;
GenericName=ALG App Store
Keywords=app;store;software;install;system;
Encoding=UTF-8
Terminal=false
Exec=alg-app-store
Icon=/usr/share/pixmaps/alg-app-store.png
Comment=ALG - App Store
Categories=System;Go;
Icon=alg-app-store
Comment=Install all your favourite apps
Categories=System;Apps;
StartupNotify=true
4 changes: 2 additions & 2 deletions src/core/alpm_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ AlpmWrapper& AlpmWrapper::instance() {
return instance;
}

AlpmWrapper::AlpmWrapper()
: m_handle(nullptr), m_syncDbs(nullptr), m_initialized(false) {
AlpmWrapper::AlpmWrapper() {
// Member initialization is done in header file
}

AlpmWrapper::~AlpmWrapper() {
Expand Down
17 changes: 14 additions & 3 deletions src/core/alpm_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
#include <mutex>
#include "../utils/types.h"

/**
* @brief Singleton wrapper for libalpm (Arch Linux Package Manager library).
*
* Memory Management:
* - m_handle: Raw pointer to libalpm handle, manually managed via initialize()/release()
* - m_syncDbs: Raw pointer to libalpm list, managed by libalpm internally
* - Thread-safe via m_mutex
*
* Note: libalpm uses C-style memory management, so smart pointers are not
* directly applicable to the alpm types.
*/
class AlpmWrapper {
public:
static AlpmWrapper& instance();
Expand All @@ -34,10 +45,10 @@ class AlpmWrapper {
private:
AlpmWrapper();

alpm_handle_t* m_handle;
alpm_list_t* m_syncDbs;
alpm_handle_t* m_handle = nullptr;
alpm_list_t* m_syncDbs = nullptr;
std::mutex m_mutex;
bool m_initialized;
bool m_initialized = false;

QStringList convertDependList(alpm_list_t* deps);
void searchInDatabase(alpm_db_t* db, const QString& query,
Expand Down
7 changes: 7 additions & 0 deletions src/core/aur_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
#include <memory>
#include "../utils/types.h"

/**
* @brief Helper class for interacting with the Arch User Repository (AUR).
*
* Memory Management:
* - m_networkManager: Owned by std::unique_ptr for RAII-style cleanup
* - Network replies are managed via Qt parent-child and deleteLater()
*/
class AurHelper : public QObject {
Q_OBJECT

Expand Down
63 changes: 50 additions & 13 deletions src/core/package_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ PackageManager& PackageManager::instance() {

PackageManager::PackageManager()
: QObject(nullptr)
, m_helper(Helper::Pacman)
, m_process(std::make_unique<QProcess>()) {

detectHelper();
Expand Down Expand Up @@ -41,13 +40,13 @@ void PackageManager::detectHelper() {
return;
}

// Check for paru
QString paruPath = QStandardPaths::findExecutable("paru");
if (!paruPath.isEmpty()) {
m_helper = Helper::Paru;
Logger::info("Using paru as package helper");
return;
}
// Check for paru - deprecate because paru doesn't allow running with pkexec
// QString paruPath = QStandardPaths::findExecutable("paru");
// if (!paruPath.isEmpty()) {
// m_helper = Helper::Paru;
// Logger::info("Using paru as package helper");
// return;
// }

// Default to pacman
m_helper = Helper::Pacman;
Expand All @@ -57,7 +56,6 @@ void PackageManager::detectHelper() {
QString PackageManager::getHelperName() const {
switch (m_helper) {
case Helper::Yay: return "yay";
case Helper::Paru: return "paru";
case Helper::Pacman: return "pacman";
default: return "pacman";
}
Expand All @@ -75,9 +73,10 @@ void PackageManager::installPackage(const QString& packageName, const QString& r
QString helper = getHelperName();

QString command;
if (isAUR && (m_helper == Helper::Yay || m_helper == Helper::Paru)) {
// AUR packages - run helper as regular user (no pkexec)
command = QString("%1 -S %2 --noconfirm").arg(helper, packageName);
if (isAUR && (m_helper == Helper::Yay)) {
// AUR packages - use pkexec to get userpassword before hand
// Paru has a problem here, so default to yay
command = QString("pkexec %1 -S %2 --noconfirm").arg(helper, packageName);
} else {
// Official repos and chaotic-aur need root access and use pacman
command = QString("pkexec pacman -S %1 --noconfirm").arg(packageName);
Expand Down Expand Up @@ -110,7 +109,7 @@ void PackageManager::updatePackage(const QString& packageName, const QString& re
QString helper = getHelperName();

QString command;
if (isAUR && (m_helper == Helper::Yay || m_helper == Helper::Paru)) {
if (isAUR && (m_helper == Helper::Yay)) {
// AUR packages - run helper as regular user (no pkexec)
command = QString("%1 -S %2 --noconfirm").arg(helper, packageName);
} else {
Expand Down Expand Up @@ -187,3 +186,41 @@ void PackageManager::onProcessOutput() {
emit operationOutput(output);
}
}

void PackageManager::cancelRunningOperation() {
if (m_process && m_process->state() != QProcess::NotRunning) {
Logger::warning("Cancelling running operation...");
emit operationOutput("\n>>> Operation cancelled by user <<<\n");

// When using pkexec, we need to kill the actual pacman/yay/paru process
// not just the pkexec wrapper. Use pkill to terminate all package manager processes.
QProcess killProcess;
killProcess.start("pkexec", QStringList() << "bash" << "-c"
<< "pkill -TERM pacman; pkill -TERM yay; pkill -TERM paru");
killProcess.waitForFinished(2000);

// Also terminate the QProcess wrapper
m_process->terminate();

// Wait up to 3 seconds for graceful termination
if (!m_process->waitForFinished(3000)) {
// Force kill if still running
Logger::warning("Process did not terminate gracefully, forcing kill...");
killProcess.start("pkexec", QStringList() << "bash" << "-c"
<< "pkill -KILL pacman; pkill -KILL yay; pkill -KILL paru");
killProcess.waitForFinished(2000);

m_process->kill();
m_process->waitForFinished(1000);
}

emit operationCompleted(false, "Operation cancelled by user");
Logger::info("Operation cancelled successfully");
} else {
Logger::warning("No operation is currently running");
}
}

bool PackageManager::isOperationRunning() const {
return m_process && m_process->state() != QProcess::NotRunning;
}
13 changes: 11 additions & 2 deletions src/core/package_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
#include <memory>
#include <mutex>

/**
* @brief Singleton class for managing package operations (install, uninstall, update).
*
* Memory Management:
* - m_process: Owned by std::unique_ptr for RAII-style cleanup and clear ownership
* - Thread-safe via m_mutex for operation serialization
*/
class PackageManager : public QObject {
Q_OBJECT

Expand All @@ -31,6 +38,8 @@ class PackageManager : public QObject {
void uninstallPackage(const QString& packageName, const QString& repository = QString());
void updatePackage(const QString& packageName, const QString& repository = QString());
void updateAllPackages();
void cancelRunningOperation();
bool isOperationRunning() const;

Helper getHelper() const { return m_helper; }
QString getHelperName() const;
Expand All @@ -47,9 +56,9 @@ class PackageManager : public QObject {
void detectHelper();
void executeCommand(const QString& command, const QStringList& args);

Helper m_helper;
Helper m_helper = Helper::Pacman;
std::unique_ptr<QProcess> m_process;
std::mutex m_mutex;
mutable std::mutex m_mutex;

private slots:
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
Expand Down
30 changes: 17 additions & 13 deletions src/gui/home_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,25 @@ void HomeWidget::loadFeaturedPackages() {
{"zoom", "Latest", "Video Conferencing and Web Conferencing Service", "AUR"}
};

// Check if packages marked as AUR are actually available in automated repos (like chaotic-aur)
// Fetch actual package information from repositories
for (auto& pkg : m_featuredPackages) {
if (pkg.repository.toLower() == "aur") {
Logger::debug(QString("Checking if AUR package %1 is available in official repos...").arg(pkg.name));
PackageInfo repoInfo = AlpmWrapper::instance().getPackageInfo(pkg.name);
if (!repoInfo.name.isEmpty() && !repoInfo.repository.isEmpty()) {
// Package found in automated repos, use that repository instead
pkg.repository = repoInfo.repository;
pkg.version = repoInfo.version;
pkg.description = repoInfo.description;
Logger::info(QString("✅ Package %1 found in %2 repository, will use pacman instead of AUR helper")
.arg(pkg.name, pkg.repository));
} else {
Logger::debug(QString("Package %1 not found in official repos, will use AUR helper").arg(pkg.name));
PackageInfo repoInfo = AlpmWrapper::instance().getPackageInfo(pkg.name);

if (!repoInfo.name.isEmpty() && !repoInfo.repository.isEmpty()) {
// Package found in official repos (including chaotic-aur), update with actual information
pkg.repository = repoInfo.repository;
pkg.version = repoInfo.version;
pkg.description = repoInfo.description;

if (pkg.repository.toLower() != "aur") {
Logger::debug(QString("Package %1 found in %2 repository with version %3")
.arg(pkg.name, pkg.repository, pkg.version));
}
} else if (pkg.repository.toLower() == "aur") {
// Package not found in official repos (including chaotic-aur)
// Default to AUR helper (yay/paru) since chaotic-aur is not enabled or doesn't have this package
pkg.repository = "aur";
Logger::debug(QString("Package %1 not found in enabled repositories, defaulting to AUR helper").arg(pkg.name));
}
}

Expand Down
19 changes: 14 additions & 5 deletions src/gui/home_widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

class PackageCard;

/**
* @brief Widget displaying featured packages on the home screen.
*
* Memory Management:
* - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning)
* - m_packageCards contains non-owning pointers to cards owned by m_contentWidget
*/
class HomeWidget : public QWidget {
Q_OBJECT

Expand All @@ -24,11 +31,13 @@ class HomeWidget : public QWidget {
void checkInstalledPackages();

QVector<PackageInfo> m_featuredPackages;
QVector<PackageCard*> m_packageCards;
QScrollArea* m_scrollArea;
QWidget* m_contentWidget;
QGridLayout* m_gridLayout;
QTimer* m_updateTimer;
QVector<PackageCard*> m_packageCards; // Non-owning pointers, owned by m_contentWidget

// Qt parent-child managed widgets (non-owning pointers)
QScrollArea* m_scrollArea = nullptr;
QWidget* m_contentWidget = nullptr;
QGridLayout* m_gridLayout = nullptr;
QTimer* m_updateTimer = nullptr;

private slots:
void onPackageClicked(const PackageInfo& info);
Expand Down
20 changes: 14 additions & 6 deletions src/gui/installed_widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
#include <QVector>
#include "../utils/types.h"

/**
* @brief Widget displaying installed packages.
*
* Memory Management:
* - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning)
* - Package cards are dynamically created/destroyed in displayPackages/clearResults
*/
class InstalledWidget : public QWidget {
Q_OBJECT

Expand All @@ -25,12 +32,13 @@ class InstalledWidget : public QWidget {
void filterPackages(const QString& query);
void clearResults();

QLineEdit* m_filterInput;
QScrollArea* m_scrollArea;
QWidget* m_contentWidget;
QGridLayout* m_gridLayout;
QLabel* m_statusLabel;
QLabel* m_countLabel;
// Qt parent-child managed widgets (non-owning pointers)
QLineEdit* m_filterInput = nullptr;
QScrollArea* m_scrollArea = nullptr;
QWidget* m_contentWidget = nullptr;
QGridLayout* m_gridLayout = nullptr;
QLabel* m_statusLabel = nullptr;
QLabel* m_countLabel = nullptr;

QVector<PackageInfo> m_allPackages;
QVector<PackageInfo> m_filteredPackages;
Expand Down
11 changes: 8 additions & 3 deletions src/gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ void MainWindow::createMenuBar() {
QMessageBox::about(this, "About ALG App Store",
"ALG App Store (Beta)\n\n"
"A modern package manager for Arch Linux\n"
"Version: 0.2.16\n"
"Version: 0.2.26\n"
"Built with Qt6 and C++17\n\n"
"© 2025 Arka Linux GUI");
});
Expand All @@ -128,15 +128,20 @@ void MainWindow::loadStyleSheet() {
QFile styleFile(":/stylesheet.qss");

if (!styleFile.exists()) {
// Try loading from file system
// Try loading from current directory (for development)
styleFile.setFileName("stylesheet.qss");
}

if (!styleFile.exists()) {
// Try loading from system installation path
styleFile.setFileName("/usr/share/alg-app-store/stylesheet.qss");
}

if (styleFile.open(QFile::ReadOnly)) {
QString styleSheet = QLatin1String(styleFile.readAll());
qApp->setStyleSheet(styleSheet);
styleFile.close();
Logger::info("Stylesheet loaded successfully");
Logger::info(QString("Stylesheet loaded successfully from: %1").arg(styleFile.fileName()));
} else {
Logger::warning("Could not load stylesheet");
}
Expand Down
Loading