diff --git a/src/core/services/mount_dev_image.cpp b/src/core/services/mount_dev_image.cpp index e0db168..91b8135 100644 --- a/src/core/services/mount_dev_image.cpp +++ b/src/core/services/mount_dev_image.cpp @@ -89,7 +89,7 @@ static ssize_t mim_upload_cb(void *buf, size_t size, void *userdata) return fread(buf, 1, size, (FILE *)userdata); } // TODO: cleanup -bool mount_dev_image(char *udid) +bool mount_dev_image(const char *udid, const char *image_dir_path) { mobile_image_mounter_client_t mim = NULL; int res = -1; @@ -137,16 +137,14 @@ bool mount_dev_image(char *udid) char *image_path = nullptr; char *image_sig_path = nullptr; - if (asprintf(&image_path, - "%s/resources/dev-images/15/DeveloperDiskImage.dmg", - SOURCE_DIR) < 0) { + if (asprintf(&image_path, "%s/DeveloperDiskImage.dmg", image_dir_path) < + 0) { qDebug() << "Out of memory constructing image path!"; return false; } - if (asprintf(&image_sig_path, - "%s/resources/dev-images/15/DeveloperDiskImage.dmg.signature", - SOURCE_DIR) < 0) { + if (asprintf(&image_sig_path, "%s/DeveloperDiskImage.dmg.signature", + image_dir_path) < 0) { qDebug() << "Out of memory constructing signature path!"; free(image_path); return false; diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp new file mode 100644 index 0000000..c5316f3 --- /dev/null +++ b/src/devdiskimageswidget.cpp @@ -0,0 +1,490 @@ +#include "devdiskimageswidget.h" +#include "appcontext.h" +#include "iDescriptor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// Handle errors , event though it failed, ui thinks it is mounted +/*DetailedError: Error Domain=com.apple.MobileStorage.ErrorDomain Code=-2 + * "Failed to mount + * /private/var/run/mobile_image_mounter/42B093B66120045164A6781BD419867320D74D62DA078BB73979CD87C9AC14ECF4331C385F72EAF1F68C81C922D2EF3DE647BC0949DEE6557FBC06DAF7C13FEC/E9E8F8B5021B74DF4C10E6595BB4C16BB45CC55503B8B32085CE31C20A62812D4DB8264F758D5256DBA697139E56F2E8BEE3690EE33F252D17C044BB6C0446A2/tonVFp.dmg." + * UserInfo={NSLocalizedDescription=Failed to mount + * /private/var/run/mobile_image_mounter/42B093B66120045164A6781BD419867320D74D62DA078BB73979CD87C9AC14ECF4331C385F72EAF1F68C81C922D2EF3DE647BC0949DEE6557FBC06DAF7C13FEC/E9E8F8B5021B74DF4C10E6595BB4C16BB45CC55503B8B32085CE31C20A62812D4DB8264F758D5256DBA697139E56F2E8BEE3690EE33F252D17C044BB6C0446A2/tonVFp.dmg., + * NSUnderlyingError=0x12fe05ce0 {Error + * Domain=com.apple.MobileStorage.ErrorDomain Code=-2 "Invalid value for + * MountPath: Error Domain=com.apple.MobileStorage.ErrorDomain Code=-3 "A disk + * image of type Developer/(null) is already mounted at /Developer." + * UserInfo={NSLocalizedDescription=A disk image of type Developer/(null) is + * already mounted at /Developer.}" UserInfo={NSLocalizedDescription=Invalid + * value for MountPath: Error Domain=com.apple.MobileStorage.ErrorDomain Code=-3 + * "A disk image of type Developer/(null) is already mounted at /Developer." + * UserInfo={NSLocalizedDescription=A disk image of type Developer/(null) is + * already mounted at /Developer.}}}}*/ + +extern bool mount_dev_image(const char *udid, const char *image_dir_path); + +DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device, + QWidget *parent) + : QWidget{parent}, m_networkManager(new QNetworkAccessManager(this)), + m_currentDevice(device) +{ + setupUi(); + fetchImageList(); + updateDeviceList(); + connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this, + &DevDiskImagesWidget::updateDeviceList); + connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, + &DevDiskImagesWidget::updateDeviceList); +} + +void DevDiskImagesWidget::setupUi() +{ + auto *layout = new QVBoxLayout(this); + + auto *pathLayout = new QHBoxLayout(); + pathLayout->addWidget(new QLabel(tr("Download Path:"))); + m_downloadPathEdit = new QLineEdit(); + m_downloadPathEdit->setReadOnly(true); + pathLayout->addWidget(m_downloadPathEdit); + auto *changeDirButton = new QPushButton(tr("Change...")); + connect(changeDirButton, &QPushButton::clicked, this, + &DevDiskImagesWidget::changeDownloadDirectory); + pathLayout->addWidget(changeDirButton); + layout->addLayout(pathLayout); + + auto *mountLayout = new QHBoxLayout(); + mountLayout->addWidget(new QLabel(tr("Device:"))); + m_deviceComboBox = new QComboBox(this); + mountLayout->addWidget(m_deviceComboBox); + m_mountButton = new QPushButton(tr("Mount"), this); + connect(m_mountButton, &QPushButton::clicked, this, + &DevDiskImagesWidget::onMountButtonClicked); + mountLayout->addWidget(m_mountButton); + layout->addLayout(mountLayout); + + m_stackedWidget = new QStackedWidget(this); + layout->addWidget(m_stackedWidget); + + m_statusLabel = new QLabel(tr("Fetching image list...")); + m_statusLabel->setAlignment(Qt::AlignCenter); + m_stackedWidget->addWidget(m_statusLabel); + + m_imageListWidget = new QListWidget(this); + m_stackedWidget->addWidget(m_imageListWidget); + + m_downloadPath = + QDir(QCoreApplication::applicationDirPath()).filePath("devdiskimages"); + m_downloadPathEdit->setText(m_downloadPath); +} + +void DevDiskImagesWidget::fetchImageList() +{ + m_stackedWidget->setCurrentWidget(m_statusLabel); + m_statusLabel->setText(tr("Fetching image list...")); + + QUrl url("https://api.github.com/repos/mspvirajpatel/" + "Xcode_Developer_Disk_Images/git/trees/master?recursive=true"); + QNetworkRequest request(url); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, + [this, reply]() { onImageListFetchFinished(reply); }); +} + +void DevDiskImagesWidget::onImageListFetchFinished(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + m_statusLabel->setText( + tr("Error fetching image list: %1").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QByteArray data = reply->readAll(); + reply->deleteLater(); + + m_imageListJsonData = data; + parseAndDisplayImages(m_imageListJsonData); + m_stackedWidget->setCurrentWidget(m_imageListWidget); +} + +void DevDiskImagesWidget::parseAndDisplayImages(const QByteArray &jsonData) +{ + m_imageListWidget->clear(); + m_availableImages.clear(); + + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + if (!doc.isObject()) { + m_statusLabel->setText(tr("Failed to parse JSON response.")); + m_stackedWidget->setCurrentWidget(m_statusLabel); + return; + } + + QMap> + imageFiles; // dir -> {filename -> path} + + QJsonArray tree = doc.object()["tree"].toArray(); + for (const QJsonValue &value : tree) { + QJsonObject obj = value.toObject(); + QString path = obj["path"].toString(); + if (path.endsWith(".dmg") || path.endsWith(".dmg.signature")) { + QFileInfo fileInfo(path); + QString dir = fileInfo.path(); + QString filename = fileInfo.fileName(); + if (!dir.isEmpty() && dir != ".") + imageFiles[dir][filename] = path; + } + } + + for (auto it = imageFiles.constBegin(); it != imageFiles.constEnd(); ++it) { + if (it.value().contains("DeveloperDiskImage.dmg") && + it.value().contains("DeveloperDiskImage.dmg.signature")) { + QFileInfo dirInfo(it.key()); + QString version = dirInfo.fileName(); + m_availableImages[version] = { + it.value()["DeveloperDiskImage.dmg"], + it.value()["DeveloperDiskImage.dmg.signature"]}; + + auto *itemWidget = new QWidget(); + auto *itemLayout = new QHBoxLayout(itemWidget); + itemLayout->addWidget(new QLabel(version)); + + QString versionPath = QDir(m_downloadPath).filePath(version); + bool exists = QDir(versionPath).exists(); + + if (exists) { + itemLayout->addWidget(new QLabel(tr("(already exists)"))); + } + + itemLayout->addStretch(); + + auto *progressBar = new QProgressBar(); + progressBar->setVisible(false); + itemLayout->addWidget(progressBar); + + auto *downloadButton = + new QPushButton(exists ? tr("Re-download") : tr("Download")); + downloadButton->setProperty("version", version); + connect(downloadButton, &QPushButton::clicked, this, + &DevDiskImagesWidget::onDownloadButtonClicked); + itemLayout->addWidget(downloadButton); + + auto *listItem = new QListWidgetItem(m_imageListWidget); + listItem->setSizeHint(itemWidget->sizeHint()); + m_imageListWidget->addItem(listItem); + m_imageListWidget->setItemWidget(listItem, itemWidget); + } + } +} + +void DevDiskImagesWidget::onDownloadButtonClicked() +{ + auto *button = qobject_cast(sender()); + if (!button) + return; + + QString version = button->property("version").toString(); + + QString versionPath = QDir(m_downloadPath).filePath(version); + if (QDir(versionPath).exists()) { + auto reply = QMessageBox::question( + this, tr("Confirm Overwrite"), + tr("Directory '%1' already exists. Do you want to overwrite it?") + .arg(version), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::No) { + return; + } + } + + startDownload(version); +} + +void DevDiskImagesWidget::startDownload(const QString &version) +{ + if (!m_availableImages.contains(version)) + return; + + // Find the button and progress bar for this version + QPushButton *downloadButton = nullptr; + QProgressBar *progressBar = nullptr; + for (int i = 0; i < m_imageListWidget->count(); ++i) { + auto *item = m_imageListWidget->item(i); + auto *widget = m_imageListWidget->itemWidget(item); + auto *button = widget->findChild(); + if (button && button->property("version") == version) { + downloadButton = button; + progressBar = widget->findChild(); + break; + } + } + + if (!downloadButton || !progressBar) + return; + + downloadButton->setEnabled(false); + progressBar->setVisible(true); + progressBar->setValue(0); + + QString targetDir = QDir(m_downloadPath).filePath(version); + if (!QDir().mkpath(targetDir)) { + QMessageBox::critical( + this, tr("Error"), + tr("Could not create directory: %1").arg(targetDir)); + downloadButton->setEnabled(true); + progressBar->setVisible(false); + return; + } + + auto *downloadItem = new DownloadItem(); + downloadItem->version = version; + downloadItem->progressBar = progressBar; + downloadItem->downloadButton = downloadButton; + + QString dmgPath = m_availableImages[version].first; + QString sigPath = m_availableImages[version].second; + + QUrl dmgUrl("https://raw.githubusercontent.com/mspvirajpatel/" + "Xcode_Developer_Disk_Images/master/" + + dmgPath); + QNetworkRequest dmgRequest(dmgUrl); + downloadItem->dmgReply = m_networkManager->get(dmgRequest); + connect(downloadItem->dmgReply, &QNetworkReply::downloadProgress, this, + &DevDiskImagesWidget::onDownloadProgress); + connect(downloadItem->dmgReply, &QNetworkReply::finished, this, + &DevDiskImagesWidget::onFileDownloadFinished); + + QUrl sigUrl("https://raw.githubusercontent.com/mspvirajpatel/" + "Xcode_Developer_Disk_Images/master/" + + sigPath); + QNetworkRequest sigRequest(sigUrl); + downloadItem->sigReply = m_networkManager->get(sigRequest); + connect(downloadItem->sigReply, &QNetworkReply::downloadProgress, this, + &DevDiskImagesWidget::onDownloadProgress); + connect(downloadItem->sigReply, &QNetworkReply::finished, this, + &DevDiskImagesWidget::onFileDownloadFinished); + + m_activeDownloads[downloadItem->dmgReply] = downloadItem; + m_activeDownloads[downloadItem->sigReply] = downloadItem; +} + +void DevDiskImagesWidget::onDownloadProgress(qint64 bytesReceived, + qint64 bytesTotal) +{ + auto *reply = qobject_cast(sender()); + if (!reply || !m_activeDownloads.contains(reply)) + return; + + auto *item = m_activeDownloads[reply]; + + if (reply->property("totalSizeAdded").isNull() && bytesTotal > 0) { + item->totalSize += bytesTotal; + reply->setProperty("totalSizeAdded", true); + } + + if (reply == item->dmgReply) { + item->dmgReceived = bytesReceived; + } else if (reply == item->sigReply) { + item->sigReceived = bytesReceived; + } + + item->totalReceived = item->dmgReceived + item->sigReceived; + + if (item->totalSize > 0) { + item->progressBar->setValue((item->totalReceived * 100) / + item->totalSize); + } +} + +void DevDiskImagesWidget::onFileDownloadFinished() +{ + auto *reply = qobject_cast(sender()); + if (!reply || !m_activeDownloads.contains(reply)) + return; + + auto *item = m_activeDownloads[reply]; + m_activeDownloads.remove(reply); + + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::critical(this, tr("Download Error"), + tr("Failed to download %1: %2") + .arg(reply->url().path()) + .arg(reply->errorString())); + + if (reply == item->dmgReply && item->sigReply) + item->sigReply->abort(); + if (reply == item->sigReply && item->dmgReply) + item->dmgReply->abort(); + + item->downloadButton->setEnabled(true); + item->downloadButton->setText(tr("Retry")); + item->progressBar->setVisible(false); + + if (m_activeDownloads.key(item) == nullptr) { + delete item; + } + reply->deleteLater(); + return; + } + + QString path = QUrl::fromPercentEncoding(reply->url().path().toUtf8()); + QFileInfo fileInfo(path); + QString filename = fileInfo.fileName(); + QString targetPath = + QDir(QDir(m_downloadPath).filePath(item->version)).filePath(filename); + + QFile file(targetPath); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::critical(this, tr("File Error"), + tr("Could not save file: %1").arg(targetPath)); + } else { + file.write(reply->readAll()); + file.close(); + } + + reply->deleteLater(); + + if (m_activeDownloads.key(item) == nullptr) { // Both files downloaded + item->downloadButton->setText(tr("Downloaded")); + item->downloadButton->setEnabled(false); + item->progressBar->setValue(100); + item->progressBar->setVisible(false); + delete item; + } +} + +void DevDiskImagesWidget::updateDeviceList() +{ + QString currentUdid; + if (m_deviceComboBox->count() > 0 && + m_deviceComboBox->currentIndex() >= 0) { + currentUdid = m_deviceComboBox->currentData().toString(); + } + + m_deviceComboBox->clear(); + + auto devices = AppContext::sharedInstance()->getAllDevices(); + int newIndex = -1; + for (int i = 0; i < devices.size(); ++i) { + auto *device = devices.at(i); + m_deviceComboBox->addItem( + QString("%1 (%2)") + .arg(QString::fromStdString(device->deviceInfo.deviceName)) + .arg(QString::fromStdString(device->udid)), + QString::fromStdString(device->udid)); + if (QString().fromStdString((device->udid)) == currentUdid) { + newIndex = i; + } + } + + if (newIndex != -1) { + m_deviceComboBox->setCurrentIndex(newIndex); + } +} + +void DevDiskImagesWidget::onMountButtonClicked() +{ + if (m_deviceComboBox->currentIndex() < 0) { + QMessageBox::warning( + this, tr("No Device"), + tr("Please select a device to mount the image on.")); + return; + } + + auto *currentItem = m_imageListWidget->currentItem(); + if (!currentItem) { + QMessageBox::warning(this, tr("No Image Selected"), + tr("Please select a disk image to mount.")); + return; + } + + auto *widget = m_imageListWidget->itemWidget(currentItem); + auto *button = widget->findChild(); + if (!button) + return; + + QString version = button->property("version").toString(); + + mountImage(version); +} + +void DevDiskImagesWidget::mountImage(const QString &version) +{ + QString udid = m_deviceComboBox->currentData().toString(); + if (udid.isEmpty()) { + QMessageBox::warning(this, tr("No Device"), + tr("Please select a device.")); + return; + } + + QString versionPath = QDir(m_downloadPath).filePath(version); + if (!QDir(versionPath).exists()) { + QMessageBox::warning( + this, tr("Image Not Found"), + tr("The selected disk image for version %1 is not downloaded. " + "Please download it first.") + .arg(version)); + return; + } + + QString dmgPath = QDir(versionPath).filePath("DeveloperDiskImage.dmg"); + QString sigPath = + QDir(versionPath).filePath("DeveloperDiskImage.dmg.signature"); + + if (!QFile::exists(dmgPath) || !QFile::exists(sigPath)) { + QMessageBox::warning( + this, tr("Image Files Missing"), + tr("Image files are missing in %1. Please re-download.") + .arg(versionPath)); + return; + } + + m_mountButton->setEnabled(false); + m_mountButton->setText(tr("Mounting...")); + + bool success = mount_dev_image(udid.toUtf8().constData(), + versionPath.toUtf8().constData()); + + m_mountButton->setEnabled(true); + m_mountButton->setText(tr("Mount")); + + if (success) { + QMessageBox::information(this, tr("Success"), + tr("Image mounted successfully on %1.") + .arg(m_deviceComboBox->currentText())); + } else { + QMessageBox::critical(this, tr("Mount Failed"), + tr("Failed to mount image on %1.") + .arg(m_deviceComboBox->currentText())); + } +} + +void DevDiskImagesWidget::changeDownloadDirectory() +{ + QString dir = QFileDialog::getExistingDirectory( + this, tr("Select Download Directory"), m_downloadPath, + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (!dir.isEmpty() && dir != m_downloadPath) { + m_downloadPath = dir; + m_downloadPathEdit->setText(m_downloadPath); + if (!m_imageListJsonData.isEmpty()) { + parseAndDisplayImages(m_imageListJsonData); + } + } +} diff --git a/src/devdiskimageswidget.h b/src/devdiskimageswidget.h new file mode 100644 index 0000000..80d942d --- /dev/null +++ b/src/devdiskimageswidget.h @@ -0,0 +1,70 @@ +#ifndef DEVDISKIMAGESWIDGET_H +#define DEVDISKIMAGESWIDGET_H + +#include "iDescriptor.h" +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QListWidget; +class QStackedWidget; +class QLabel; +class QLineEdit; +class QPushButton; +class QProgressBar; +class QComboBox; + +class DevDiskImagesWidget : public QWidget +{ + Q_OBJECT +public: + explicit DevDiskImagesWidget(iDescriptorDevice *device, + QWidget *parent = nullptr); + +private slots: + void fetchImageList(); + void onImageListFetchFinished(QNetworkReply *reply); + void onDownloadButtonClicked(); + void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onFileDownloadFinished(); + void changeDownloadDirectory(); + void updateDeviceList(); + void onMountButtonClicked(); + +private: + void setupUi(); + void parseAndDisplayImages(const QByteArray &jsonData); + void startDownload(const QString &version); + void mountImage(const QString &version); + + struct DownloadItem { + QNetworkReply *dmgReply = nullptr; + QNetworkReply *sigReply = nullptr; + QProgressBar *progressBar = nullptr; + QPushButton *downloadButton = nullptr; + QString version; + qint64 totalSize = 0; + qint64 totalReceived = 0; + qint64 dmgReceived = 0; + qint64 sigReceived = 0; + }; + + QStackedWidget *m_stackedWidget; + QListWidget *m_imageListWidget; + QLabel *m_statusLabel; + QLineEdit *m_downloadPathEdit; + QComboBox *m_deviceComboBox; + QPushButton *m_mountButton; + + QNetworkAccessManager *m_networkManager; + QString m_downloadPath; + iDescriptorDevice *m_currentDevice; + + QMap> + m_availableImages; // version -> {dmg_path, sig_path} + QMap m_activeDownloads; + QByteArray m_imageListJsonData; +}; + +#endif // DEVDISKIMAGESWIDGET_H diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 4d5ae7e..d3dba2e 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -3,6 +3,7 @@ #include "./core/services/restart.cpp" #include "airplaywindow.h" #include "appcontext.h" +#include "devdiskimageswidget.h" #include "iDescriptor.h" #include "querymobilegestaltwidget.h" #include "realtimescreen.h" @@ -132,6 +133,10 @@ void ToolboxWidget::setupUI() createToolbox("Enter Recovery Mode", "Enter device recovery mode", "SP_DialogOkButton", true); + QWidget *devDiskImages = + createToolbox("Developer Disk Images", "Manage developer disk images", + "SP_DialogOkButton", false); + // Add toolboxes to grid (3 columns) m_gridLayout->addWidget(airplayerBox, 0, 0); m_gridLayout->addWidget(virtualLocationBox, 0, 1); @@ -145,6 +150,7 @@ void ToolboxWidget::setupUI() m_gridLayout->addWidget(enterRecoveryMode, 3, 0); m_gridLayout->addWidget(mountDevImage, 3, 1); m_gridLayout->addWidget(unmountDevImage, 3, 2); + m_gridLayout->addWidget(devDiskImages, 4, 0, 1, 3); m_gridLayout->setRowStretch(3, 1); @@ -161,9 +167,10 @@ QWidget *ToolboxWidget::createToolbox(const QString &title, bool requiresDevice) { QFrame *frame = new QFrame(); + frame->setObjectName("toolboxFrame"); frame->setFrameStyle(QFrame::Box); - frame->setStyleSheet( - "QFrame { border: 1px solid #ccc; border-radius: 5px; padding: 5px; }"); + frame->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; " + "border-radius: 5px; padding: 5px; }"); frame->setFixedSize(200, 120); QVBoxLayout *layout = new QVBoxLayout(frame); @@ -259,11 +266,12 @@ void ToolboxWidget::updateToolboxStates() toolbox->setEnabled(enabled); if (enabled) { - toolbox->setStyleSheet("QFrame { border: 1px solid #ccc; " + toolbox->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; " "border-radius: 5px; padding: 5px; }"); } else { toolbox->setStyleSheet( - "QFrame { border: 1px solid #ccc; border-radius: 5px; padding: " + "#toolboxFrame { border: 1px solid #ccc; border-radius: 5px; " + "padding: " "5px; background-color: #f0f0f0; color: #999; }"); } } @@ -323,16 +331,17 @@ void ToolboxWidget::onToolboxClicked(const QString &toolName) msgBox.exec(); } else if (toolName == "Mount Dev Image") { // Handle mounting device image - bool success = - mount_dev_image(const_cast(m_currentDevice->udid.c_str())); - QMessageBox msgBox; - msgBox.setWindowTitle("Mount Dev Image"); - if (success) { - msgBox.setText("Successfully mounted device image."); - } else { - msgBox.setText("Failed to mount device image."); - } - msgBox.exec(); + // bool success = + // mount_dev_image(const_cast(m_currentDevice->udid.c_str())); + // QMessageBox msgBox; + // msgBox.setWindowTitle("Mount Dev Image"); + // if (success) { + // msgBox.setText("Successfully mounted device image."); + // } else { + // msgBox.setText("Failed to mount device image."); + // } + // msgBox.exec(); } else if (toolName == "Virtual Location") { // Handle virtual location functionality VirtualLocation *virtualLocation = new VirtualLocation(m_currentDevice); @@ -369,6 +378,14 @@ void ToolboxWidget::onToolboxClicked(const QString &toolName) QueryMobileGestaltWidget *queryMobileGestaltWidget = new QueryMobileGestaltWidget(); queryMobileGestaltWidget->show(); + } else if (toolName == "Developer Disk Images") { + // Handle developer disk images + DevDiskImagesWidget *devDiskImagesWidget = + new DevDiskImagesWidget(m_currentDevice); + devDiskImagesWidget->setAttribute(Qt::WA_DeleteOnClose); + devDiskImagesWidget->setWindowFlag(Qt::Window); + devDiskImagesWidget->resize(800, 600); + devDiskImagesWidget->show(); } // Implement specific tool functionality here }