diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7dbbe77..012d836 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -137,17 +137,23 @@ pkg_check_modules(PLIST REQUIRED IMPORTED_TARGET libplist-2.0)
# )
file(GLOB PROJECT_SOURCES
- src/*.cpp
- src/core/helpers/*.cpp
- src/core/services/*.cpp
- src/platform/*.cpp
- src/platform/*.mm
- src/platform/*.m
- src/*.h
- src/*.ui
- resources.qrc
+src/*.cpp
+src/core/helpers/*.cpp
+src/core/services/*.cpp
+src/*.h
+src/*.ui
+resources.qrc
)
+if(MACOS)
+ list(APPEND PROJECT_SOURCES
+ src/platform/macos/*.mm
+ src/platform/macos/*.h
+ )
+endif()
+
+
+
add_subdirectory(lib/airplay)
add_subdirectory(lib/ipatool-go)
diff --git a/icons/ClarityHardDiskSolidAlerted.png b/icons/ClarityHardDiskSolidAlerted.png
new file mode 100644
index 0000000..f6af879
Binary files /dev/null and b/icons/ClarityHardDiskSolidAlerted.png differ
diff --git a/resources.qrc b/resources.qrc
index cc8c805..dbfad0c 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -4,8 +4,9 @@
icons/video-x-generic.png
icons/MdiLightningBolt.png
icons/MingcuteSettings7Line.png
+ icons/ClarityHardDiskSolidAlerted.png
qml/MapView.qml
resources/dump.js
resources/iphone.png
-
+
\ No newline at end of file
diff --git a/src/airplaywindow.cpp b/src/airplaywindow.cpp
index 958064e..abbfcd4 100644
--- a/src/airplaywindow.cpp
+++ b/src/airplaywindow.cpp
@@ -9,6 +9,7 @@
#include
#include
+#ifdef Q_OS_LINUX
// V4L2 includes
#include
#include
@@ -16,6 +17,7 @@
#include
#include
#include
+#endif
// Include the rpiplay server functions
extern "C" {
@@ -28,17 +30,22 @@ std::function qt_video_callback;
AirPlayWindow::AirPlayWindow(QWidget *parent)
: QMainWindow(parent), m_videoLabel(nullptr), m_statusLabel(nullptr),
- m_serverThread(nullptr), m_serverRunning(false), m_v4l2_fd(-1),
- m_v4l2_width(0), m_v4l2_height(0), m_v4l2_enabled(false)
+ m_serverThread(nullptr), m_serverRunning(false)
+#ifdef Q_OS_LINUX
+ ,
+ m_v4l2_fd(-1), m_v4l2_width(0), m_v4l2_height(0), m_v4l2_enabled(false)
+#endif
{
setupUI();
// Setup video callback
qt_video_callback = [this](uint8_t *data, int width, int height) {
+#ifdef Q_OS_LINUX
// V4L2 output if enabled
if (m_v4l2_enabled) {
writeFrameToV4L2(data, width, height);
}
+#endif
QByteArray frameData((const char *)data, width * height * 3);
QMetaObject::invokeMethod(this, "updateVideoFrame",
@@ -51,7 +58,9 @@ AirPlayWindow::AirPlayWindow(QWidget *parent)
AirPlayWindow::~AirPlayWindow()
{
stopAirPlayServer();
+#ifdef Q_OS_LINUX
closeV4L2();
+#endif
qt_video_callback = nullptr;
}
@@ -82,6 +91,7 @@ void AirPlayWindow::setupUI()
statusLayout->addWidget(m_statusLabel);
statusLayout->addStretch();
+#ifdef Q_OS_LINUX
// V4L2 controls
QCheckBox *v4l2CheckBox = new QCheckBox("Enable V4L2 Output");
connect(v4l2CheckBox, &QCheckBox::toggled, this, [this](bool enabled) {
@@ -99,6 +109,7 @@ void AirPlayWindow::setupUI()
statusLayout->addWidget(v4l2CheckBox);
statusLayout->addWidget(testV4L2Btn);
+#endif
statusLayout->addWidget(startBtn);
statusLayout->addWidget(stopBtn);
@@ -190,6 +201,7 @@ void AirPlayServerThread::run()
emit statusChanged(false);
}
+#ifdef Q_OS_LINUX
// V4L2 Implementation
void AirPlayWindow::initV4L2(int width, int height, const char *device)
{
@@ -308,3 +320,4 @@ void AirPlayWindow::testV4L2Device()
"• Record with: ffmpeg -f v4l2 -i %1 output.mp4")
.arg(device));
}
+#endif
diff --git a/src/airplaywindow.h b/src/airplaywindow.h
index 301673e..b88c68d 100644
--- a/src/airplaywindow.h
+++ b/src/airplaywindow.h
@@ -34,6 +34,7 @@ private:
AirPlayServerThread *m_serverThread;
bool m_serverRunning;
+#ifdef Q_OS_LINUX
// V4L2 members
int m_v4l2_fd;
int m_v4l2_width;
@@ -45,6 +46,7 @@ private:
void closeV4L2();
void writeFrameToV4L2(uint8_t *data, int width, int height);
void testV4L2Device();
+#endif
};
class AirPlayServerThread : public QThread
diff --git a/src/customtabwidget.cpp b/src/customtabwidget.cpp
index 6250e93..5b56c13 100644
--- a/src/customtabwidget.cpp
+++ b/src/customtabwidget.cpp
@@ -271,6 +271,7 @@ void CustomTabWidget::updateTabStyles()
" font-weight: 500;"
" font-size: 20px;"
" border: none;"
+ " outline: none;"
" border-radius: 27px;"
" background-color: transparent;"
"}"
@@ -283,6 +284,7 @@ void CustomTabWidget::updateTabStyles()
" font-weight: 500;"
" font-size: 20px;"
" border: none;"
+ " outline: none;"
" border-radius: 27px;"
" background-color: transparent;"
"}"
diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp
index 3a3df60..a04ba6e 100644
--- a/src/deviceinfowidget.cpp
+++ b/src/deviceinfowidget.cpp
@@ -4,6 +4,7 @@
#include "fileexplorerwidget.h"
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
+#include
#include
#include
#include
@@ -53,7 +54,6 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
infoContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
QVBoxLayout *infoLayout = new QVBoxLayout(infoContainer);
- // infoLayout->setContentsMargins(15, 15, 15, 15);
// infoLayout->setSpacing(10);
// Header
@@ -77,17 +77,20 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
(1000 * 1000 * 1000)) +
" GB");
+ diskCapacityLabel->setSizePolicy(QSizePolicy::Maximum,
+ QSizePolicy::Preferred);
diskCapacityLabel->setAttribute(Qt::WA_StyledBackground, true);
- diskCapacityLabel->setStyleSheet("background-color: rgba(0, 255, 30, 0.12);"
+ diskCapacityLabel->setStyleSheet("background-color: rgba(0, 255, 30, 0.5);"
"padding: 4px;"
- "border-radius: 4px;");
+ "border-radius: 13px;");
m_chargingStatusLabel =
new QLabel(device->deviceInfo.batteryInfo.isCharging ? "Charging"
: "Not Charging");
m_chargingStatusLabel->setStyleSheet(
- device->deviceInfo.batteryInfo.isCharging ? "color: green;"
- : "color: white;");
+ device->deviceInfo.batteryInfo.isCharging
+ ? QString("color: %1;").arg(COLOR_GREEN.name())
+ : "color: white;");
// Create the layout without a parent widget
QHBoxLayout *chargingLayout = new QHBoxLayout();
@@ -97,7 +100,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
// Create icon label
m_lightningIconLabel = new QLabel();
QPixmap lightningIcon(":/icons/MdiLightningBolt.png");
- QPixmap scaledIcon = lightningIcon.scaled(16, 16, Qt::KeepAspectRatio,
+ QPixmap scaledIcon = lightningIcon.scaled(26, 26, Qt::KeepAspectRatio,
Qt::SmoothTransformation);
m_lightningIconLabel->setPixmap(scaledIcon);
m_batteryWidget =
@@ -118,6 +121,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
headerLayout->addWidget(devProductType);
headerLayout->addWidget(diskCapacityLabel);
+ headerLayout->addStretch(); // Push items to the left
headerLayout->addLayout(chargingLayout);
headerLayout->addWidget(m_chargingWattsWithCableTypeLabel);
@@ -149,12 +153,16 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
// 3. Create the grid widget (the main content)
QWidget *gridWidget = new QWidget();
gridWidget->setObjectName("infoGrid");
- // Set a background color that matches the main window, with rounded corners
- gridWidget->setStyleSheet(
- "QWidget#infoGrid {"
- " background-color: #2e2e2e;" // Match your window background
- " border-radius: 8px;"
- "}");
+
+ QPalette palette = qApp->palette();
+ QColor background = palette.color(QPalette::Window);
+
+ gridWidget->setStyleSheet("QWidget#infoGrid {"
+ " background-color: " +
+ background.name() +
+ ";"
+ " border-radius: 8px;"
+ "}");
// 4. Create the light (top-left) shadow and apply to the grid widget
QGraphicsDropShadowEffect *lightShadow = new QGraphicsDropShadowEffect();
@@ -195,17 +203,17 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
switch (device->deviceInfo.activationState) {
case DeviceInfo::ActivationState::Activated:
stateText = "Activated";
- color = QColor(0, 180, 0); // Green
+ color = COLOR_GREEN;
tooltipText = "Device is activated and ready for use.";
break;
case DeviceInfo::ActivationState::FactoryActivated:
stateText = "Factory Activated";
- color = QColor(255, 140, 0); // Orange
+ color = COLOR_ORANGE;
tooltipText = "Activation is most likely bypassed.";
break;
default:
stateText = "Unactivated";
- color = QColor(220, 0, 0); // Red
+ color = COLOR_RED;
tooltipText = "Device is not activated and requires setup.";
break;
}
@@ -380,12 +388,13 @@ void DeviceInfoWidget::updateChargingStatusIcon()
{
if (m_device->deviceInfo.batteryInfo.isCharging) {
m_chargingStatusLabel->setText("Charging");
- m_chargingStatusLabel->setStyleSheet("color: green;");
+ m_chargingStatusLabel->setStyleSheet(
+ QString("color: %1;").arg(COLOR_GREEN.name()));
m_lightningIconLabel->show();
} else {
m_chargingStatusLabel->setText("Not Charging");
- m_chargingStatusLabel->setStyleSheet("color: white;");
+ m_chargingStatusLabel->setStyleSheet("");
m_lightningIconLabel->hide();
}
}
\ No newline at end of file
diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp
index a3ebd89..6e32da3 100644
--- a/src/devicemanagerwidget.cpp
+++ b/src/devicemanagerwidget.cpp
@@ -85,6 +85,8 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
<< QString::fromStdString(device->udid);
DeviceMenuWidget *deviceWidget = new DeviceMenuWidget(device, this);
+ deviceWidget->setContentsMargins(35, 15, 35, 15);
+
QString tabTitle = QString::fromStdString(device->deviceInfo.productType);
m_stackedWidget->addWidget(deviceWidget);
diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp
index ec502e1..08bce26 100644
--- a/src/diskusagewidget.cpp
+++ b/src/diskusagewidget.cpp
@@ -1,5 +1,6 @@
#include "diskusagewidget.h"
#include "iDescriptor.h"
+#include
#include
#include
#include
@@ -26,24 +27,25 @@ void DiskUsageWidget::paintEvent(QPaintEvent *event)
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
+ QColor textColor = qApp->palette().text().color();
if (m_state == Loading) {
- painter.setPen(Qt::black);
+ painter.setPen(textColor);
painter.drawText(rect(), Qt::AlignCenter, "Loading disk usage...");
return;
}
if (m_state == Error) {
- painter.setPen(Qt::black);
+ painter.setPen(textColor);
painter.drawText(rect(), Qt::AlignCenter, "Error: " + m_errorMessage);
return;
}
// Title
- painter.setPen(Qt::black);
QFont titleFont = font();
titleFont.setBold(true);
painter.setFont(titleFont);
+ painter.setPen(textColor);
QRectF titleRect(0, 5, width(), 20);
painter.drawText(titleRect, Qt::AlignHCenter | Qt::AlignTop, "Disk Usage");
painter.setFont(font()); // Reset font
@@ -91,7 +93,7 @@ void DiskUsageWidget::paintEvent(QPaintEvent *event)
drawSegment(m_freeSpace, freeColor);
// Legend
- painter.setPen(Qt::black);
+ painter.setPen(textColor);
qreal legendY = barRect.bottom() + 15;
const int legendBoxSize = 10;
const int legendSpacing = 5;
@@ -102,6 +104,7 @@ void DiskUsageWidget::paintEvent(QPaintEvent *event)
QRectF(currentLegendX, legendY, legendBoxSize, legendBoxSize),
color);
currentLegendX += legendBoxSize + legendSpacing;
+ painter.setPen(textColor);
QFontMetrics fm(font());
QRect textRect = fm.boundingRect(text);
diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h
index 2eda016..f10fbbf 100644
--- a/src/iDescriptor-ui.h
+++ b/src/iDescriptor-ui.h
@@ -2,6 +2,10 @@
#include
#include
+#define COLOR_GREEN QColor(0, 180, 0) // Green
+#define COLOR_ORANGE QColor(255, 140, 0) // Orange
+#define COLOR_RED QColor(255, 0, 0) // Red
+
// A custom QGraphicsView that keeps the content fitted with aspect ratio on
// resize
class ResponsiveGraphicsView : public QGraphicsView
diff --git a/src/ifusediskunmountbutton.cpp b/src/ifusediskunmountbutton.cpp
new file mode 100644
index 0000000..ec771b2
--- /dev/null
+++ b/src/ifusediskunmountbutton.cpp
@@ -0,0 +1,14 @@
+#include "ifusediskunmountbutton.h"
+#include
+#include
+
+iFuseDiskUnmountButton::iFuseDiskUnmountButton(const QString &path,
+ QWidget *parent)
+ : QPushButton{parent}
+{
+ setIcon(QIcon(":/icons/ClarityHardDiskSolidAlerted.png"));
+ setToolTip("Unmount iFuse at " + path);
+ setFlat(true);
+ setCursor(Qt::PointingHandCursor);
+ setFixedSize(24, 24);
+}
diff --git a/src/ifusediskunmountbutton.h b/src/ifusediskunmountbutton.h
new file mode 100644
index 0000000..1e7573b
--- /dev/null
+++ b/src/ifusediskunmountbutton.h
@@ -0,0 +1,16 @@
+#ifndef IFUSEDISKUNMOUNTBUTTON_H
+#define IFUSEDISKUNMOUNTBUTTON_H
+
+#include
+
+class iFuseDiskUnmountButton : public QPushButton
+{
+ Q_OBJECT
+public:
+ explicit iFuseDiskUnmountButton(const QString &path,
+ QWidget *parent = nullptr);
+
+signals:
+};
+
+#endif // IFUSEDISKUNMOUNTBUTTON_H
diff --git a/src/ifusemanager.cpp b/src/ifusemanager.cpp
new file mode 100644
index 0000000..aacfcf5
--- /dev/null
+++ b/src/ifusemanager.cpp
@@ -0,0 +1,53 @@
+#include "ifusemanager.h"
+#include
+#include
+
+QStringList iFuseManager::getMountArg(std::string &udid, QString &path)
+{
+ return QStringList() << "-u" << QString::fromStdString(udid) << path;
+}
+
+#ifdef Q_OS_LINUX
+QList iFuseManager::getMountPoints()
+{
+ QProcess mountProcess;
+ mountProcess.start("mount", QStringList() << "-t"
+ << "fuse.ifuse");
+ mountProcess.waitForFinished();
+
+ QString output = mountProcess.readAllStandardOutput();
+
+ if (output.trimmed().isEmpty()) {
+ qDebug() << "[iFuseWidget] No existing ifuse mounts found.";
+ return {};
+ }
+
+ QStringList mountPoints;
+ QStringList lines = output.split('\n', Qt::SkipEmptyParts);
+ for (const QString &line : lines) {
+ // A typical line is: "ifuse on /path/to/mount type fuse.ifuse (...)"
+ QString mountPath = line.section(" on ", 1).section(" type ", 0, 0);
+ if (!mountPath.isEmpty()) {
+ qDebug() << "[iFuseWidget] - Mount point:" << mountPath;
+ mountPoints.append(mountPath);
+ }
+ }
+ return mountPoints;
+}
+#endif
+
+bool iFuseManager::linuxUnmount(const QString &path)
+{
+ QProcess umountProcess;
+ umountProcess.start("fusermount", QStringList() << "-u" << path);
+ umountProcess.waitForFinished();
+
+ if (umountProcess.exitCode() != 0) {
+ qWarning() << "[iFuseWidget] Failed to unmount" << path << ":"
+ << umountProcess.readAllStandardError().trimmed();
+ return false;
+ }
+
+ qDebug() << "[iFuseWidget] Successfully unmounted" << path;
+ return true;
+}
\ No newline at end of file
diff --git a/src/ifusemanager.h b/src/ifusemanager.h
new file mode 100644
index 0000000..5a59ed3
--- /dev/null
+++ b/src/ifusemanager.h
@@ -0,0 +1,20 @@
+#ifndef IFUSEMANAGER_H
+#define IFUSEMANAGER_H
+
+#include
+
+class iFuseManager : public QObject
+{
+ Q_OBJECT
+public:
+ // explicit iFuseManager(QObject *parent = nullptr);
+ static QList getMountPoints();
+#ifdef Q_OS_LINUX
+ static QStringList getMountArg(std::string &udid, QString &path);
+#endif
+ // TODO: need to implement a cross-platform mount and unmount function
+ static bool linuxUnmount(const QString &path);
+signals:
+};
+
+#endif // IFUSEMANAGER_H
diff --git a/src/ifusewidget.cpp b/src/ifusewidget.cpp
new file mode 100644
index 0000000..705402d
--- /dev/null
+++ b/src/ifusewidget.cpp
@@ -0,0 +1,395 @@
+#include "ifusewidget.h"
+#include "clickablelabel.h"
+#include "iDescriptor.h"
+#include "ifusediskunmountbutton.h"
+#include "ifusemanager.h"
+#include "mainwindow.h"
+#include
+#include
+#include
+#include
+
+iFuseWidget::iFuseWidget(iDescriptorDevice *device, QWidget *parent)
+ : QWidget(parent), m_mainLayout(nullptr), m_ifuseProcess(nullptr),
+ m_device(device)
+{
+ setupUI();
+ updateDeviceComboBox();
+
+ // Connect to AppContext signals for device changes
+ connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
+ &iFuseWidget::refreshDevices);
+ connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
+ &iFuseWidget::refreshDevices);
+}
+
+iFuseWidget::~iFuseWidget()
+{
+ if (m_ifuseProcess && m_ifuseProcess->state() == QProcess::Running) {
+ m_ifuseProcess->kill();
+ m_ifuseProcess->waitForFinished(3000);
+ }
+}
+
+void iFuseWidget::setupUI()
+{
+ m_mainLayout = new QVBoxLayout(this);
+ m_mainLayout->setSpacing(15);
+ m_mainLayout->setContentsMargins(20, 20, 20, 20);
+
+ // Description label
+ m_descriptionLabel = new QLabel("This tool allows you to mount your "
+ "iPhone's disk as a drive on your PC");
+ m_descriptionLabel->setWordWrap(true);
+ m_descriptionLabel->setStyleSheet(
+ "font-size: 14px; color: #666; margin-bottom: 10px;");
+ m_mainLayout->addWidget(m_descriptionLabel);
+
+ // Status label
+ m_statusLabel = new QLabel();
+ m_statusLabel->setWordWrap(true);
+ m_statusLabel->hide();
+ m_statusLabel->setStyleSheet(
+ "padding: 8px; border-radius: 4px; margin: 5px 0;");
+ m_mainLayout->addWidget(m_statusLabel);
+
+ // Device selection
+ QWidget *deviceWidget = new QWidget();
+ QHBoxLayout *deviceLayout = new QHBoxLayout(deviceWidget);
+ deviceLayout->setContentsMargins(0, 0, 0, 0);
+
+ QLabel *deviceLabel = new QLabel("Select Device:");
+ deviceLabel->setMinimumWidth(100);
+ m_deviceComboBox = new QComboBox();
+ m_deviceComboBox->setMinimumHeight(35);
+
+ deviceLayout->addWidget(deviceLabel);
+ deviceLayout->addWidget(m_deviceComboBox, 1);
+ m_mainLayout->addWidget(deviceWidget);
+
+ // Mount path selection
+ QWidget *pathWidget = new QWidget();
+ QHBoxLayout *pathLayout = new QHBoxLayout(pathWidget);
+ pathLayout->setContentsMargins(0, 0, 0, 0);
+
+ m_mountPathLabel = new ClickableLabel();
+ m_mountPathLabel->setText("Mount directory will be shown here");
+ m_mountPathLabel->setStyleSheet("QLabel { "
+ "border: 1px solid #ccc; "
+ "padding: 8px; "
+ "border-radius: 4px; "
+ "background-color: #f9f9f9; "
+ "}"
+ "QLabel:hover { "
+ "background-color: #f0f0f0; "
+ "cursor: pointer; "
+ "}");
+ m_mountPathLabel->setMinimumHeight(35);
+
+ m_folderPickerButton = new QPushButton("Browse...");
+ m_folderPickerButton->setMinimumHeight(35);
+
+ pathLayout->addWidget(m_mountPathLabel, 1);
+ pathLayout->addWidget(m_folderPickerButton);
+ m_mainLayout->addWidget(pathWidget);
+
+ // Delete on unmount checkbox
+
+ // Mount button
+ m_mountButton = new QPushButton("Mount Device");
+ m_mountButton->setMinimumHeight(40);
+ m_mountButton->setStyleSheet("QPushButton { "
+ "background-color: #007aff; "
+ "color: white; "
+ "border: none; "
+ "border-radius: 6px; "
+ "font-weight: bold; "
+ "}"
+ "QPushButton:hover { "
+ "background-color: #0056cc; "
+ "}"
+ "QPushButton:disabled { "
+ "background-color: #cccccc; "
+ "}");
+ m_mainLayout->addWidget(m_mountButton);
+
+ // Add stretch to push everything to the top
+ m_mainLayout->addStretch();
+
+ // Connect signals
+ connect(m_folderPickerButton, &QPushButton::clicked, this,
+ &iFuseWidget::onFolderPickerClicked);
+ connect(m_mountPathLabel, &ClickableLabel::clicked, this,
+ &iFuseWidget::onMountPathClicked);
+ connect(m_mountButton, &QPushButton::clicked, this,
+ &iFuseWidget::onMountClicked);
+
+ connect(m_deviceComboBox, &QComboBox::currentTextChanged, this,
+ &iFuseWidget::onDeviceChanged);
+
+ // Set default mount path based on device
+ if (m_device) {
+ QString homeDir =
+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+ QString productType =
+ QString::fromStdString(m_device->deviceInfo.productType);
+ QString defaultMountPath = QDir(homeDir).absoluteFilePath(productType);
+ m_mountPathLabel->setText(defaultMountPath);
+ } else {
+ QString homeDir =
+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+ QString defaultMountPath = QDir(homeDir).absoluteFilePath("iPhone");
+ m_mountPathLabel->setText(defaultMountPath);
+ }
+}
+
+void iFuseWidget::updateDeviceComboBox()
+{
+ m_deviceComboBox->clear();
+
+ QList devices =
+ AppContext::sharedInstance()->getAllDevices();
+
+ if (devices.isEmpty()) {
+ close();
+ return;
+ }
+
+ m_deviceComboBox->setEnabled(true);
+ m_mountButton->setEnabled(true);
+
+ for (iDescriptorDevice *device : devices) {
+ QString displayText =
+ QString::fromStdString(device->deviceInfo.productType) + " / " +
+ QString::fromStdString(device->udid);
+ m_deviceComboBox->addItem(displayText,
+ QString::fromStdString(device->udid));
+ }
+
+ // Try to find and select the device passed to the widget
+ int deviceIndex = -1;
+ if (m_device) {
+ deviceIndex =
+ m_deviceComboBox->findData(QString::fromStdString(m_device->udid));
+ }
+
+ if (deviceIndex != -1) {
+ // Found the pre-selected device, so select it.
+ m_deviceComboBox->setCurrentIndex(deviceIndex);
+ } else if (!devices.isEmpty()) {
+ // Pre-selected device not found or not provided, so select the first
+ // one.
+ m_device = devices.first();
+ m_deviceComboBox->setCurrentIndex(0);
+ }
+}
+
+void iFuseWidget::onFolderPickerClicked()
+{
+ QString currentPath = m_mountPathLabel->text();
+ QString dir = QFileDialog::getExistingDirectory(
+ this, "Select Mount Directory", currentPath);
+ if (!dir.isEmpty()) {
+ m_mountPathLabel->setText(dir);
+ }
+}
+
+void iFuseWidget::onMountPathClicked()
+{
+ QString currentPath = m_mountPathLabel->text();
+ if (!currentPath.isEmpty() && QDir(currentPath).exists()) {
+ QDesktopServices::openUrl(QUrl::fromLocalFile(currentPath));
+ }
+}
+
+void iFuseWidget::onMountClicked()
+{
+ if (!validateInputs()) {
+ return;
+ }
+
+ // Check if ifuse binary exists
+ m_ifuseProcess = new QProcess(this);
+ connect(m_ifuseProcess,
+ QOverload::of(&QProcess::finished), this,
+ &iFuseWidget::onProcessFinished);
+ connect(m_ifuseProcess, &QProcess::errorOccurred, this,
+ &iFuseWidget::onProcessError);
+
+ // First check if ifuse exists
+ QProcess checkProcess;
+ checkProcess.start("which", QStringList() << "ifuse");
+ checkProcess.waitForFinished(3000);
+
+ // todo: ship with ifuse binary
+ if (checkProcess.exitCode() != 0) {
+ setStatusMessage(
+ "Error: ifuse binary not found. Please install ifuse first.", true);
+ return;
+ }
+
+ // Create the mount directory
+ QString homeDir =
+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+ QString productType =
+ m_device ? QString::fromStdString(m_device->deviceInfo.productType)
+ : "iPhone";
+ QString fullMountPath = QDir(homeDir).absoluteFilePath(productType);
+
+ QDir dir;
+ if (!QDir(fullMountPath).exists()) {
+ if (!dir.mkpath(fullMountPath)) {
+ setStatusMessage("Error: Failed to create mount directory: " +
+ fullMountPath,
+ true);
+ return;
+ }
+ }
+
+ m_currentMountPath = fullMountPath;
+
+ // Get selected device UDID
+ QString deviceUdid = getSelectedDeviceUdid();
+
+ setStatusMessage("Mounting device...", false);
+ m_mountButton->setText("Mounting...");
+ m_mountButton->setEnabled(false);
+
+ // Run ifuse command
+ QStringList arguments;
+ arguments << "-u" << deviceUdid << fullMountPath;
+
+ m_ifuseProcess->start("ifuse", arguments);
+}
+
+void iFuseWidget::onProcessFinished(int exitCode,
+ QProcess::ExitStatus exitStatus)
+{
+ m_mountButton->setText("Mount Device");
+ m_mountButton->setEnabled(true);
+
+ if (exitStatus == QProcess::CrashExit) {
+ setStatusMessage("Error: ifuse process crashed", true);
+ return;
+ }
+
+ if (exitCode == 0) {
+ setStatusMessage(
+ "Device mounted successfully at: " + m_currentMountPath, false);
+
+ auto *b = new iFuseDiskUnmountButton(m_currentMountPath);
+ MainWindow::sharedInstance()->statusBar()->addPermanentWidget(b);
+
+ connect(b, &iFuseDiskUnmountButton::clicked, this, [this, b]() {
+ qDebug() << "Unmounting" << m_currentMountPath;
+ bool ok = iFuseManager::linuxUnmount(m_currentMountPath);
+ if (!ok) {
+ QMessageBox::warning(nullptr, "Unmount Failed",
+ "Failed to unmount iFuse at " +
+ m_currentMountPath +
+ ". Please try again.");
+ return;
+ }
+ MainWindow::sharedInstance()->statusBar()->removeWidget(b);
+ b->deleteLater();
+ });
+ // Open the mounted directory
+ QDesktopServices::openUrl(QUrl::fromLocalFile(m_currentMountPath));
+ } else {
+ QString errorOutput = m_ifuseProcess->readAllStandardError();
+ setStatusMessage("Mount failed: " + errorOutput, true);
+ }
+
+ m_ifuseProcess->deleteLater();
+ m_ifuseProcess = nullptr;
+}
+
+void iFuseWidget::onProcessError(QProcess::ProcessError error)
+{
+ m_mountButton->setText("Mount Device");
+ m_mountButton->setEnabled(true);
+
+ QString errorMessage;
+ switch (error) {
+ case QProcess::FailedToStart:
+ errorMessage = "Failed to start ifuse. Make sure it's installed.";
+ break;
+ case QProcess::Crashed:
+ errorMessage = "ifuse process crashed.";
+ break;
+ case QProcess::Timedout:
+ errorMessage = "ifuse process timed out.";
+ break;
+ default:
+ errorMessage = "Unknown error occurred.";
+ break;
+ }
+
+ setStatusMessage("Error: " + errorMessage, true);
+
+ if (m_ifuseProcess) {
+ m_ifuseProcess->deleteLater();
+ m_ifuseProcess = nullptr;
+ }
+}
+
+void iFuseWidget::refreshDevices() { updateDeviceComboBox(); }
+
+bool iFuseWidget::validateInputs()
+{
+ if (m_deviceComboBox->currentData().toString().isEmpty()) {
+ setStatusMessage("Error: No device selected", true);
+ return false;
+ }
+
+ return true;
+}
+
+QString iFuseWidget::getSelectedDeviceUdid()
+{
+ return m_deviceComboBox->currentData().toString();
+}
+
+void iFuseWidget::setStatusMessage(const QString &message, bool isError)
+{
+ m_statusLabel->setText(message);
+ m_statusLabel->show();
+
+ if (isError) {
+ m_statusLabel->setStyleSheet(
+ "background-color: #ffe6e6; color: #d00; border: 1px solid "
+ "#ffcccc; padding: 8px; border-radius: 4px; margin: 5px 0;");
+ } else {
+ m_statusLabel->setStyleSheet(
+ "background-color: #e6ffe6; color: #060; border: 1px solid "
+ "#ccffcc; padding: 8px; border-radius: 4px; margin: 5px 0;");
+ }
+
+ // Auto-hide status after 5 seconds for non-error messages
+ if (!isError) {
+ QTimer::singleShot(5000, [this]() { m_statusLabel->hide(); });
+ }
+}
+
+void iFuseWidget::onDeviceChanged(const QString &text)
+{
+ QString selectedUdid = m_deviceComboBox->currentData().toString();
+ QList devices =
+ AppContext::sharedInstance()->getAllDevices();
+
+ for (iDescriptorDevice *device : devices) {
+ if (QString::fromStdString(device->udid) == selectedUdid) {
+ m_device = device;
+
+ // Update mount path to reflect new device
+ QString homeDir =
+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+ QString productType =
+ QString::fromStdString(device->deviceInfo.productType);
+ QString newMountPath = QDir(homeDir).absoluteFilePath(productType);
+ m_mountPathLabel->setText(newMountPath);
+
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ifusewidget.h b/src/ifusewidget.h
new file mode 100644
index 0000000..c62256a
--- /dev/null
+++ b/src/ifusewidget.h
@@ -0,0 +1,61 @@
+#ifndef IFUSEWIDGET_H
+#define IFUSEWIDGET_H
+
+#include "appcontext.h"
+#include "clickablelabel.h"
+#include "iDescriptor.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class iFuseWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit iFuseWidget(iDescriptorDevice *device, QWidget *parent = nullptr);
+ ~iFuseWidget();
+
+private slots:
+ void onFolderPickerClicked();
+ void onMountPathClicked();
+ void onMountClicked();
+ void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void onProcessError(QProcess::ProcessError error);
+ void refreshDevices();
+
+private:
+ void setupUI();
+ void updateDeviceComboBox();
+ bool validateInputs();
+ QString getSelectedDeviceUdid();
+ void setStatusMessage(const QString &message, bool isError = false);
+ void onDeviceChanged(const QString &deviceName);
+ // UI Components
+ QVBoxLayout *m_mainLayout;
+ QLabel *m_descriptionLabel;
+ QLabel *m_statusLabel;
+ QComboBox *m_deviceComboBox;
+ ClickableLabel *m_mountPathLabel;
+ QPushButton *m_folderPickerButton;
+ QLabel *m_folderNameLabel;
+ QPushButton *m_mountButton;
+ iDescriptorDevice *m_device;
+
+ // Data
+ QString m_selectedPath;
+ QProcess *m_ifuseProcess;
+ QString m_currentMountPath;
+};
+
+#endif // IFUSEWIDGET_H
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 2b63627..b998ca3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,4 @@
#include "mainwindow.h"
-
#include
int main(int argc, char *argv[])
@@ -10,7 +9,7 @@ int main(int argc, char *argv[])
// QCoreApplication::setOrganizationDomain("iDescriptor.com");
QCoreApplication::setApplicationName("iDescriptor");
- MainWindow w;
- w.show();
+ MainWindow *w = MainWindow::sharedInstance();
+ w->show();
return a.exec();
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 5867462..fda02e5 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2,6 +2,8 @@
#include "./ui_mainwindow.h"
#include "customtabwidget.h"
#include "detailwindow.h"
+#include "ifusediskunmountbutton.h"
+#include "ifusemanager.h"
#include "settingswidget.h"
#include
#include
@@ -120,11 +122,16 @@ void handleCallbackRecovery(const irecv_device_event_t *event, void *userData)
}
irecv_device_event_context_t context;
+MainWindow *MainWindow::sharedInstance()
+{
+ static MainWindow instance;
+ return &instance;
+}
+
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
-
// Create custom tab widget
m_customTabWidget = new CustomTabWidget(this);
m_customTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea,
@@ -217,6 +224,27 @@ MainWindow::MainWindow(QWidget *parent)
ui->statusbar->addPermanentWidget(settingsButton);
+#ifdef Q_OS_LINUX
+ QList mounted_iFusePaths = iFuseManager::getMountPoints();
+
+ for (const QString &path : mounted_iFusePaths) {
+ auto *p = new iFuseDiskUnmountButton(path);
+
+ ui->statusbar->addPermanentWidget(p);
+ connect(p, &iFuseDiskUnmountButton::clicked, this, [this, p, path]() {
+ bool ok = iFuseManager::linuxUnmount(path);
+ if (!ok) {
+ QMessageBox::warning(nullptr, "Unmount Failed",
+ "Failed to unmount iFuse at " + path +
+ ". Please try again.");
+ return;
+ }
+ ui->statusbar->removeWidget(p);
+ p->deleteLater();
+ });
+ }
+#endif
+
irecv_error_t res_recovery =
irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr);
@@ -233,6 +261,7 @@ MainWindow::MainWindow(QWidget *parent)
void MainWindow::createMenus()
{
+#ifdef Q_OS_MAC
QMenu *actionsMenu = menuBar()->addMenu("&Actions");
// Add a custom "About" action for your app
@@ -243,6 +272,7 @@ void MainWindow::createMenus()
"A modern device management tool.");
});
actionsMenu->addAction(aboutAct);
+#endif
}
void MainWindow::updateNoDevicesConnected()
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 61f73f7..d8da75e 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -20,6 +20,7 @@ class MainWindow : public QMainWindow
Q_OBJECT
public:
+ static MainWindow *sharedInstance();
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj);
diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp
index c4db591..1159ebd 100644
--- a/src/toolboxwidget.cpp
+++ b/src/toolboxwidget.cpp
@@ -3,6 +3,8 @@
#include "appcontext.h"
#include "devdiskimageswidget.h"
#include "iDescriptor.h"
+#include "ifusewidget.h"
+#include "pcfileexplorerwidget.h"
#include "querymobilegestaltwidget.h"
#include "realtimescreen.h"
#include "virtual_location.h"
@@ -138,6 +140,14 @@ void ToolboxWidget::setupUI()
createToolbox("Developer Disk Images", "Manage developer disk images",
"SP_DialogOkButton", false);
+ QWidget *wirelessImport = createToolbox(
+ "Wireless File Import", "Import files wirelessly to your iDevice",
+ "SP_DialogOkButton", false);
+
+ QWidget *mount_iPhone = createToolbox(
+ "Mount iPhone", "Mount your iPhone's filesystem on your PC",
+ "SP_DialogOkButton", false);
+
// Add toolboxes to grid (3 columns)
m_gridLayout->addWidget(airplayerBox, 0, 0);
m_gridLayout->addWidget(virtualLocationBox, 0, 1);
@@ -151,7 +161,8 @@ void ToolboxWidget::setupUI()
m_gridLayout->addWidget(enterRecoveryMode, 3, 0);
m_gridLayout->addWidget(unmountDevImage, 3, 1);
m_gridLayout->addWidget(devDiskImages, 3, 2);
-
+ m_gridLayout->addWidget(wirelessImport, 4, 0);
+ m_gridLayout->addWidget(mount_iPhone, 4, 1);
m_gridLayout->setRowStretch(3, 1);
m_scrollArea->setWidget(m_contentWidget);
@@ -402,15 +413,19 @@ void ToolboxWidget::onToolboxClicked(const QString &toolName)
m_devDiskImagesWidget->raise();
m_devDiskImagesWidget->activateWindow();
}
- } else if (toolName == "Touch ID Test") {
- // Handle Touch ID test
- QMessageBox::information(
- this, "Touch ID Test",
- "Touch ID test functionality not implemented.");
- } else if (toolName == "Face ID Test") {
- // Handle Face ID test
- QMessageBox::information(this, "Face ID Test",
- "Face ID test functionality not implemented.");
+ } else if (toolName == "Wireless File Import") {
+ // Handle wireless file import
+ PCFileExplorerWidget *fileExplorer = new PCFileExplorerWidget();
+ fileExplorer->setAttribute(Qt::WA_DeleteOnClose);
+ fileExplorer->setWindowFlag(Qt::Window);
+ fileExplorer->resize(800, 600);
+ fileExplorer->show();
+ } else if (toolName == "Mount iPhone") {
+ iFuseWidget *ifuseWidget = new iFuseWidget(m_currentDevice);
+ ifuseWidget->setAttribute(Qt::WA_DeleteOnClose);
+ ifuseWidget->setWindowFlag(Qt::Window);
+ ifuseWidget->resize(600, 400);
+ ifuseWidget->show();
}
// Implement specific tool functionality here
}