Skip to content

Commit 280ab20

Browse files
authored
Merge pull request #13 from arch-linux-gui/qt6/c++
RAII refactor, Update Settings Tab, Use Yay as default.
2 parents 5b4481f + 4ed6c99 commit 280ab20

21 files changed

+459
-229
lines changed

TODO.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
- [x] Add chaotic aur to search tab if enabled
22
- [x] Add launch button on package card if package is installed and remove when uninstalled
33
- [] Add detailed mirrorlist tab to set mirrorlist
4-
- [] Add version information
5-
- []
6-
4+
- [x] Add version information
5+
- [] Implement an AUR helper in core to remove dependence on paru and yay
6+
- [] Ask password only once on startup - startup_auth
7+
- [] Improve settings page - settings_tab
8+
- [] Move all styles to single stylesheet - style_and_theme
9+
- [] Clean up UI; make UI look more modern (check gnome's styling options) - style_and_theme
10+
- [] Set a light/dark theme toggle, or follow system's theme - style_and_theme
11+
- [] Look into spdlog for logging
12+
- [] Look into CppUTest or Google Test (gtest) for test
713

814
## Future Enhancements
915

assets/alg-app-store.desktop

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
Type=Application
33
Version=1.0
44
Name=App Store
5-
GenericName=ALG - App Store
6-
Keywords=utility;system;welcome;
5+
GenericName=ALG App Store
6+
Keywords=app;store;software;install;system;
77
Encoding=UTF-8
88
Terminal=false
99
Exec=alg-app-store
10-
Icon=/usr/share/pixmaps/alg-app-store.png
11-
Comment=ALG - App Store
12-
Categories=System;Go;
10+
Icon=alg-app-store
11+
Comment=Install all your favourite apps
12+
Categories=System;Apps;
1313
StartupNotify=true

src/core/alpm_wrapper.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ AlpmWrapper& AlpmWrapper::instance() {
1010
return instance;
1111
}
1212

13-
AlpmWrapper::AlpmWrapper()
14-
: m_handle(nullptr), m_syncDbs(nullptr), m_initialized(false) {
13+
AlpmWrapper::AlpmWrapper() {
14+
// Member initialization is done in header file
1515
}
1616

1717
AlpmWrapper::~AlpmWrapper() {

src/core/alpm_wrapper.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
#include <mutex>
1010
#include "../utils/types.h"
1111

12+
/**
13+
* @brief Singleton wrapper for libalpm (Arch Linux Package Manager library).
14+
*
15+
* Memory Management:
16+
* - m_handle: Raw pointer to libalpm handle, manually managed via initialize()/release()
17+
* - m_syncDbs: Raw pointer to libalpm list, managed by libalpm internally
18+
* - Thread-safe via m_mutex
19+
*
20+
* Note: libalpm uses C-style memory management, so smart pointers are not
21+
* directly applicable to the alpm types.
22+
*/
1223
class AlpmWrapper {
1324
public:
1425
static AlpmWrapper& instance();
@@ -34,10 +45,10 @@ class AlpmWrapper {
3445
private:
3546
AlpmWrapper();
3647

37-
alpm_handle_t* m_handle;
38-
alpm_list_t* m_syncDbs;
48+
alpm_handle_t* m_handle = nullptr;
49+
alpm_list_t* m_syncDbs = nullptr;
3950
std::mutex m_mutex;
40-
bool m_initialized;
51+
bool m_initialized = false;
4152

4253
QStringList convertDependList(alpm_list_t* deps);
4354
void searchInDatabase(alpm_db_t* db, const QString& query,

src/core/aur_helper.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
#include <memory>
1010
#include "../utils/types.h"
1111

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

src/core/package_manager.cpp

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ PackageManager& PackageManager::instance() {
1010

1111
PackageManager::PackageManager()
1212
: QObject(nullptr)
13-
, m_helper(Helper::Pacman)
1413
, m_process(std::make_unique<QProcess>()) {
1514

1615
detectHelper();
@@ -41,13 +40,13 @@ void PackageManager::detectHelper() {
4140
return;
4241
}
4342

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

5251
// Default to pacman
5352
m_helper = Helper::Pacman;
@@ -57,7 +56,6 @@ void PackageManager::detectHelper() {
5756
QString PackageManager::getHelperName() const {
5857
switch (m_helper) {
5958
case Helper::Yay: return "yay";
60-
case Helper::Paru: return "paru";
6159
case Helper::Pacman: return "pacman";
6260
default: return "pacman";
6361
}
@@ -75,9 +73,10 @@ void PackageManager::installPackage(const QString& packageName, const QString& r
7573
QString helper = getHelperName();
7674

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

112111
QString command;
113-
if (isAUR && (m_helper == Helper::Yay || m_helper == Helper::Paru)) {
112+
if (isAUR && (m_helper == Helper::Yay)) {
114113
// AUR packages - run helper as regular user (no pkexec)
115114
command = QString("%1 -S %2 --noconfirm").arg(helper, packageName);
116115
} else {
@@ -187,3 +186,41 @@ void PackageManager::onProcessOutput() {
187186
emit operationOutput(output);
188187
}
189188
}
189+
190+
void PackageManager::cancelRunningOperation() {
191+
if (m_process && m_process->state() != QProcess::NotRunning) {
192+
Logger::warning("Cancelling running operation...");
193+
emit operationOutput("\n>>> Operation cancelled by user <<<\n");
194+
195+
// When using pkexec, we need to kill the actual pacman/yay/paru process
196+
// not just the pkexec wrapper. Use pkill to terminate all package manager processes.
197+
QProcess killProcess;
198+
killProcess.start("pkexec", QStringList() << "bash" << "-c"
199+
<< "pkill -TERM pacman; pkill -TERM yay; pkill -TERM paru");
200+
killProcess.waitForFinished(2000);
201+
202+
// Also terminate the QProcess wrapper
203+
m_process->terminate();
204+
205+
// Wait up to 3 seconds for graceful termination
206+
if (!m_process->waitForFinished(3000)) {
207+
// Force kill if still running
208+
Logger::warning("Process did not terminate gracefully, forcing kill...");
209+
killProcess.start("pkexec", QStringList() << "bash" << "-c"
210+
<< "pkill -KILL pacman; pkill -KILL yay; pkill -KILL paru");
211+
killProcess.waitForFinished(2000);
212+
213+
m_process->kill();
214+
m_process->waitForFinished(1000);
215+
}
216+
217+
emit operationCompleted(false, "Operation cancelled by user");
218+
Logger::info("Operation cancelled successfully");
219+
} else {
220+
Logger::warning("No operation is currently running");
221+
}
222+
}
223+
224+
bool PackageManager::isOperationRunning() const {
225+
return m_process && m_process->state() != QProcess::NotRunning;
226+
}

src/core/package_manager.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
#include <memory>
88
#include <mutex>
99

10+
/**
11+
* @brief Singleton class for managing package operations (install, uninstall, update).
12+
*
13+
* Memory Management:
14+
* - m_process: Owned by std::unique_ptr for RAII-style cleanup and clear ownership
15+
* - Thread-safe via m_mutex for operation serialization
16+
*/
1017
class PackageManager : public QObject {
1118
Q_OBJECT
1219

@@ -31,6 +38,8 @@ class PackageManager : public QObject {
3138
void uninstallPackage(const QString& packageName, const QString& repository = QString());
3239
void updatePackage(const QString& packageName, const QString& repository = QString());
3340
void updateAllPackages();
41+
void cancelRunningOperation();
42+
bool isOperationRunning() const;
3443

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

50-
Helper m_helper;
59+
Helper m_helper = Helper::Pacman;
5160
std::unique_ptr<QProcess> m_process;
52-
std::mutex m_mutex;
61+
mutable std::mutex m_mutex;
5362

5463
private slots:
5564
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);

src/gui/home_widget.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,25 @@ void HomeWidget::loadFeaturedPackages() {
6565
{"zoom", "Latest", "Video Conferencing and Web Conferencing Service", "AUR"}
6666
};
6767

68-
// Check if packages marked as AUR are actually available in automated repos (like chaotic-aur)
68+
// Fetch actual package information from repositories
6969
for (auto& pkg : m_featuredPackages) {
70-
if (pkg.repository.toLower() == "aur") {
71-
Logger::debug(QString("Checking if AUR package %1 is available in official repos...").arg(pkg.name));
72-
PackageInfo repoInfo = AlpmWrapper::instance().getPackageInfo(pkg.name);
73-
if (!repoInfo.name.isEmpty() && !repoInfo.repository.isEmpty()) {
74-
// Package found in automated repos, use that repository instead
75-
pkg.repository = repoInfo.repository;
76-
pkg.version = repoInfo.version;
77-
pkg.description = repoInfo.description;
78-
Logger::info(QString("✅ Package %1 found in %2 repository, will use pacman instead of AUR helper")
79-
.arg(pkg.name, pkg.repository));
80-
} else {
81-
Logger::debug(QString("Package %1 not found in official repos, will use AUR helper").arg(pkg.name));
70+
PackageInfo repoInfo = AlpmWrapper::instance().getPackageInfo(pkg.name);
71+
72+
if (!repoInfo.name.isEmpty() && !repoInfo.repository.isEmpty()) {
73+
// Package found in official repos (including chaotic-aur), update with actual information
74+
pkg.repository = repoInfo.repository;
75+
pkg.version = repoInfo.version;
76+
pkg.description = repoInfo.description;
77+
78+
if (pkg.repository.toLower() != "aur") {
79+
Logger::debug(QString("Package %1 found in %2 repository with version %3")
80+
.arg(pkg.name, pkg.repository, pkg.version));
8281
}
82+
} else if (pkg.repository.toLower() == "aur") {
83+
// Package not found in official repos (including chaotic-aur)
84+
// Default to AUR helper (yay/paru) since chaotic-aur is not enabled or doesn't have this package
85+
pkg.repository = "aur";
86+
Logger::debug(QString("Package %1 not found in enabled repositories, defaulting to AUR helper").arg(pkg.name));
8387
}
8488
}
8589

src/gui/home_widget.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010

1111
class PackageCard;
1212

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

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

2633
QVector<PackageInfo> m_featuredPackages;
27-
QVector<PackageCard*> m_packageCards;
28-
QScrollArea* m_scrollArea;
29-
QWidget* m_contentWidget;
30-
QGridLayout* m_gridLayout;
31-
QTimer* m_updateTimer;
34+
QVector<PackageCard*> m_packageCards; // Non-owning pointers, owned by m_contentWidget
35+
36+
// Qt parent-child managed widgets (non-owning pointers)
37+
QScrollArea* m_scrollArea = nullptr;
38+
QWidget* m_contentWidget = nullptr;
39+
QGridLayout* m_gridLayout = nullptr;
40+
QTimer* m_updateTimer = nullptr;
3241

3342
private slots:
3443
void onPackageClicked(const PackageInfo& info);

src/gui/installed_widget.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
#include <QVector>
1010
#include "../utils/types.h"
1111

12+
/**
13+
* @brief Widget displaying installed packages.
14+
*
15+
* Memory Management:
16+
* - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning)
17+
* - Package cards are dynamically created/destroyed in displayPackages/clearResults
18+
*/
1219
class InstalledWidget : public QWidget {
1320
Q_OBJECT
1421

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

28-
QLineEdit* m_filterInput;
29-
QScrollArea* m_scrollArea;
30-
QWidget* m_contentWidget;
31-
QGridLayout* m_gridLayout;
32-
QLabel* m_statusLabel;
33-
QLabel* m_countLabel;
35+
// Qt parent-child managed widgets (non-owning pointers)
36+
QLineEdit* m_filterInput = nullptr;
37+
QScrollArea* m_scrollArea = nullptr;
38+
QWidget* m_contentWidget = nullptr;
39+
QGridLayout* m_gridLayout = nullptr;
40+
QLabel* m_statusLabel = nullptr;
41+
QLabel* m_countLabel = nullptr;
3442

3543
QVector<PackageInfo> m_allPackages;
3644
QVector<PackageInfo> m_filteredPackages;

0 commit comments

Comments
 (0)