88#include < QDockWidget>
99#include < QFile>
1010#include < QFileDialog>
11+ #include < QFileInfo>
1112#include < QFrame>
1213#include < QHeaderView>
1314#include < QInputDialog>
1415#include < QMenuBar>
1516#include < QMessageBox>
17+ #include < QPixmap>
1618#include < QPushButton>
1719#include < QProcess>
20+ #include < QProcessEnvironment>
1821#include < QSettings>
1922#include < QSignalBlocker>
23+ #include < QStandardPaths>
2024#include < QStatusBar>
2125#include < QTabWidget>
26+ #include < QTextStream>
2227#include < QTextDocumentFragment>
2328#include < QThread>
2429#include < QTimer>
2934#include " apkdecompileworker.h"
3035#include " apkrecompileworker.h"
3136#include " apksignworker.h"
37+ #include " desktopdatabaseupdateworker.h"
3238#include " deviceselectiondialog.h"
3339#include " findreplacedialog.h"
3440#include " hexedit.h"
@@ -72,7 +78,14 @@ MainWindow::MainWindow(const QMap<QString, QString> &versions, QWidget *parent)
7278 setMenuBar (buildMenuBar ());
7379 setMinimumSize (WINDOW_WIDTH, WINDOW_HEIGHT);
7480 setStatusBar (buildStatusBar (versions));
75- setWindowTitle (tr (" APK Studio" ).append (" - https://vaibhavpandey.com/apkstudio/" ));
81+ updateWindowTitle ();
82+
83+ #ifdef Q_OS_LINUX
84+ // Set window icon explicitly for Linux window managers
85+ // This ensures the icon appears in the sidebar/dock when the window is running
86+ setWindowIcon (QIcon (" :/images/icon.png" ));
87+ #endif
88+
7689 connect (QApplication::clipboard (), &QClipboard::dataChanged, this , &MainWindow::handleClipboardDataChanged);
7790 QSettings settings;
7891 if (settings.value (" app_maximized" ).toBool ()) {
@@ -92,7 +105,12 @@ MainWindow::MainWindow(const QMap<QString, QString> &versions, QWidget *parent)
92105 // Ensure main window gets focus instead of search boxes
93106 setFocus ();
94107
95- QTimer::singleShot (100 , [=] {
108+ #ifdef Q_OS_LINUX
109+ // Check and install desktop file on Linux (after window is shown)
110+ QTimer::singleShot (500 , this , &MainWindow::checkAndInstallDesktopFile);
111+ #endif
112+
113+ QTimer::singleShot (500 , [=] {
96114 QSettings settings;
97115 const QStringList files = settings.value (" open_files" ).toStringList ();
98116 foreach (const QString &file, files) {
@@ -1445,6 +1463,7 @@ void MainWindow::openProject(const QString &folder, const bool last)
14451463 openFile (manifest);
14461464 }
14471465 }
1466+ updateWindowTitle ();
14481467}
14491468
14501469void MainWindow::reloadChildren (QTreeWidgetItem *item)
@@ -1488,6 +1507,175 @@ bool MainWindow::saveTab(int i)
14881507 return true ;
14891508}
14901509
1510+ void MainWindow::updateWindowTitle ()
1511+ {
1512+ QString title = tr (" APK Studio by VPZ" );
1513+
1514+ // Get the first (most recent) project from the tree
1515+ if (m_ProjectsTree->topLevelItemCount () > 0 ) {
1516+ QTreeWidgetItem *firstProject = m_ProjectsTree->topLevelItem (0 );
1517+ if (firstProject) {
1518+ QString projectFolder = firstProject->data (0 , Qt::UserRole + 2 ).toString ();
1519+ if (!projectFolder.isEmpty ()) {
1520+ QFileInfo info (projectFolder);
1521+ title += tr (" - %1" ).arg (info.fileName ());
1522+ }
1523+ }
1524+ }
1525+
1526+ setWindowTitle (title);
1527+ }
1528+
1529+ #ifdef Q_OS_LINUX
1530+ void MainWindow::checkAndInstallDesktopFile ()
1531+ {
1532+ QString applicationsDir = QStandardPaths::writableLocation (QStandardPaths::ApplicationsLocation);
1533+ if (applicationsDir.isEmpty ()) {
1534+ // Fallback to ~/.local/share/applications
1535+ applicationsDir = QStandardPaths::writableLocation (QStandardPaths::HomeLocation) + " /.local/share/applications" ;
1536+ }
1537+
1538+ QString desktopFilePath = applicationsDir + " /apkstudio.desktop" ;
1539+ QFileInfo desktopFileInfo (desktopFilePath);
1540+
1541+ // Check if .desktop file already exists
1542+ if (desktopFileInfo.exists ()) {
1543+ return ; // Already installed
1544+ }
1545+
1546+ // Ask user for confirmation
1547+ QMessageBox msgBox (this );
1548+ msgBox.setWindowTitle (tr (" Install desktop entry" ));
1549+ msgBox.setText (tr (" Would you like to install a desktop entry for APK Studio?\n\n This will allow you to launch APK Studio from your application menu." ));
1550+ msgBox.setStandardButtons (QMessageBox::Yes | QMessageBox::No);
1551+ msgBox.setDefaultButton (QMessageBox::Yes);
1552+ msgBox.setIcon (QMessageBox::Question);
1553+
1554+ if (msgBox.exec () != QMessageBox::Yes) {
1555+ return ; // User declined
1556+ }
1557+
1558+ // Get the executable path
1559+ // Check if running from AppImage - if so, use the AppImage file path
1560+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment ();
1561+ QString executablePath;
1562+ QString appImagePath = env.value (" APPIMAGE" );
1563+
1564+ if (!appImagePath.isEmpty () && QFile::exists (appImagePath)) {
1565+ // Running from AppImage - use the AppImage file itself
1566+ executablePath = appImagePath;
1567+ } else {
1568+ // Not running from AppImage - use the regular executable path
1569+ executablePath = QApplication::applicationFilePath ();
1570+ QFileInfo exeInfo (executablePath);
1571+ if (!exeInfo.exists ()) {
1572+ // If the executable path doesn't exist (e.g., running from build directory),
1573+ // try to use argv[0] or a generic command
1574+ executablePath = " apkstudio" ;
1575+ } else {
1576+ executablePath = exeInfo.absoluteFilePath ();
1577+ }
1578+ }
1579+
1580+ // Extract icon from bundled resources and save it to a location the desktop file can use
1581+ QString iconPath = " apkstudio" ; // Fallback to generic name
1582+
1583+ // Try to extract icon from bundled resources
1584+ QPixmap iconPixmap (" :/images/icon.png" );
1585+ if (!iconPixmap.isNull ()) {
1586+ // Save icon to ~/.local/share/icons/apkstudio.png
1587+ QString iconsDir = QStandardPaths::writableLocation (QStandardPaths::HomeLocation) + " /.local/share/icons" ;
1588+ QDir iconsDirObj;
1589+ if (iconsDirObj.mkpath (iconsDir)) {
1590+ QString iconFilePath = iconsDir + " /apkstudio.png" ;
1591+ if (iconPixmap.save (iconFilePath, " PNG" )) {
1592+ iconPath = iconFilePath;
1593+ } else {
1594+ // Fallback: try ~/.local/share/apkstudio/icon.png
1595+ QString appDataDir = QStandardPaths::writableLocation (QStandardPaths::AppDataLocation);
1596+ if (appDataDir.isEmpty ()) {
1597+ appDataDir = QStandardPaths::writableLocation (QStandardPaths::HomeLocation) + " /.apkstudio" ;
1598+ }
1599+ if (iconsDirObj.mkpath (appDataDir)) {
1600+ iconFilePath = appDataDir + " /icon.png" ;
1601+ if (iconPixmap.save (iconFilePath, " PNG" )) {
1602+ iconPath = iconFilePath;
1603+ }
1604+ }
1605+ }
1606+ }
1607+ }
1608+
1609+ // If icon extraction failed, try to find it relative to executable (for non-AppImage installations)
1610+ QFileInfo exeInfo (executablePath);
1611+ if (iconPath == " apkstudio" && exeInfo.exists () && appImagePath.isEmpty ()) {
1612+ QString exeDir = exeInfo.absolutePath ();
1613+ QFileInfo iconInfo (exeDir + " /../share/pixmaps/apkstudio.png" );
1614+ if (iconInfo.exists ()) {
1615+ iconPath = iconInfo.absoluteFilePath ();
1616+ } else {
1617+ // Try relative to executable
1618+ iconInfo.setFile (exeDir + " /../share/apkstudio/icon.png" );
1619+ if (iconInfo.exists ()) {
1620+ iconPath = iconInfo.absoluteFilePath ();
1621+ }
1622+ }
1623+ }
1624+
1625+ // Create the applications directory if it doesn't exist
1626+ QDir dir;
1627+ if (!dir.mkpath (applicationsDir)) {
1628+ QMessageBox::warning (this , tr (" Error" ),
1629+ tr (" Failed to create applications directory:\n %1" ).arg (applicationsDir));
1630+ return ;
1631+ }
1632+
1633+ // Create the .desktop file content
1634+ QFile desktopFile (desktopFilePath);
1635+ if (!desktopFile.open (QIODevice::WriteOnly | QIODevice::Text)) {
1636+ QMessageBox::warning (this , tr (" Error" ),
1637+ tr (" Failed to create desktop file:\n %1" ).arg (desktopFilePath));
1638+ return ;
1639+ }
1640+
1641+ QTextStream out (&desktopFile);
1642+ out << " [Desktop Entry]\n " ;
1643+ out << " Type=Application\n " ;
1644+ out << " Name=APK Studio\n " ;
1645+ out << " Name[en]=APK Studio\n " ;
1646+ out << " Comment=Android APK reverse engineering tool\n " ;
1647+ out << " Comment[en]=Android APK reverse engineering tool\n " ;
1648+ // Escape the executable path if it contains spaces
1649+ QString escapedExecPath = executablePath;
1650+ if (executablePath.contains (" " )) {
1651+ escapedExecPath = " \" " + executablePath + " \" " ;
1652+ }
1653+ out << " Exec=" << escapedExecPath << " %F\n " ;
1654+ out << " Icon=" << iconPath << " \n " ;
1655+ out << " Terminal=false\n " ;
1656+ out << " Categories=Development;Utility;\n " ;
1657+ out << " StartupNotify=true\n " ;
1658+ out << " StartupWMClass=apkstudio\n " ;
1659+ desktopFile.close ();
1660+
1661+ // Set appropriate permissions (readable by user, group, and others)
1662+ desktopFile.setPermissions (QFile::ReadUser | QFile::WriteUser |
1663+ QFile::ReadGroup | QFile::ReadOther);
1664+
1665+ // Update desktop database to make it appear in Ubuntu launcher (async using worker)
1666+ QThread *thread = new QThread (this );
1667+ DesktopDatabaseUpdateWorker *worker = new DesktopDatabaseUpdateWorker (applicationsDir);
1668+ worker->moveToThread (thread);
1669+
1670+ connect (thread, &QThread::started, worker, &DesktopDatabaseUpdateWorker::updateDatabase);
1671+ connect (worker, &DesktopDatabaseUpdateWorker::finished, thread, &QThread::quit);
1672+ connect (worker, &DesktopDatabaseUpdateWorker::finished, worker, &QObject::deleteLater);
1673+ connect (thread, &QThread::finished, thread, &QObject::deleteLater);
1674+ // Note: We don't show error messages for database update failures as they're not critical
1675+ thread->start ();
1676+ }
1677+ #endif
1678+
14911679MainWindow::~MainWindow ()
14921680{
14931681}
0 commit comments