diff --git a/CMakeLists.txt b/CMakeLists.txt
index 16d2d0f..c095d48 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,12 +1,13 @@
cmake_minimum_required(VERSION 3.16)
-project(iDescriptor VERSION 0.3.0 LANGUAGES CXX)
+project(iDescriptor VERSION 0.3.0 LANGUAGES C CXX)
if(WIN32)
set(PKG_CONFIG_EXECUTABLE "C:/msys64/mingw64/bin/pkg-config.exe" CACHE FILEPATH "" FORCE)
endif()
# Feature options
-set(PACKAGE_MANAGER_HINT "" CACHE STRING "Name of package manager(s) used to manage this build (e.g. paru, yay, pamac)")
+option(ENABLE_RECOVERY_DEVICE_SUPPORT "Enable recovery device support (requires libirecovery)" ON)
+set(PACKAGE_MANAGER_HINT "" CACHE STRING "Name of package manager(s) used to manage future updates (e.g. paru, yay, pamac), only used if PACKAGE_MANAGER_MANAGED is ON)")
option(PACKAGE_MANAGER_MANAGED "Build as package manager managed version (auto updates will be handled by the package manager)" OFF)
option(DEPLOY "Deploy the application (WIN32 only)" ON)
@@ -40,7 +41,7 @@ if(WIN32)
set(ENV{PKG_CONFIG_PATH} "${CUSTOM_PKGCONFIG_PATH};$ENV{PKG_CONFIG_PATH}")
elseif(APPLE)
set(CUSTOM_LIB_PATH "/usr/local/lib")
- # Remove the problematic include path that's causing conflicts
+ # TODO: do we need this on macOS?
# set(CUSTOM_INCLUDE_PATH "/usr/local/include")
set(CUSTOM_PKGCONFIG_PATH "/usr/local/lib/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "${CUSTOM_PKGCONFIG_PATH}:$ENV{PKG_CONFIG_PATH}")
@@ -92,7 +93,6 @@ set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/debug/libidevice_ffi.a)
if(APPLE)
add_custom_command(
OUTPUT ${IDEVICE_RS_LIB_PATH}
- # Ensure rustc is visible in the build environment
COMMAND ${CMAKE_COMMAND} -E env
"PATH=$ENV{HOME}/.cargo/bin:/usr/local/bin:$ENV{PATH}"
"RUSTC=/usr/local/bin/rustc"
@@ -101,6 +101,7 @@ add_custom_command(
COMMENT "Building idevice-rs FFI libraryy"
VERBATIM
)
+# We must use +stable-x86_64-pc-windows-gnu on Windows
elseif(WIN32)
add_custom_command(
OUTPUT ${IDEVICE_RS_LIB_PATH}
@@ -109,6 +110,7 @@ add_custom_command(
COMMENT "Building idevice-rs FFI libraryy"
VERBATIM
)
+# Linux
else()
add_custom_command(
OUTPUT ${IDEVICE_RS_LIB_PATH}
@@ -166,7 +168,7 @@ find_package(Threads REQUIRED)
target_link_libraries(idevice_cpp PUBLIC idevice_ffi Threads::Threads)
if (UNIX AND NOT APPLE)
- pkg_check_modules(UDEV REQUIRED IMPORTED_TARGET udev)
+ pkg_check_modules(UDEV REQUIRED IMPORTED_TARGET libudev)
target_link_libraries(idevice_cpp PUBLIC PkgConfig::UDEV dl m)
elseif(APPLE)
find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
@@ -218,6 +220,20 @@ endif()
pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml)
+if(ENABLE_RECOVERY_DEVICE_SUPPORT)
+ find_library(IRECOVERY_LIBRARY
+ NAMES irecovery-1.0
+ ${CUSTOM_FIND_LIB_ARGS}
+ )
+ if(IRECOVERY_LIBRARY)
+ message(STATUS "Building with recovery device support enabled")
+ else()
+ message(WARNING "libirecovery not found. Recovery device support will be disabled. This is to be expected if you are installing from Arch AUR.")
+ set(ENABLE_RECOVERY_DEVICE_SUPPORT OFF)
+ endif()
+else()
+ message(STATUS "Recovery device support disabled")
+endif()
file(GLOB PROJECT_SOURCES
src/*.h
@@ -229,6 +245,15 @@ src/base/*.h
resources.qrc
)
+if (NOT ENABLE_RECOVERY_DEVICE_SUPPORT)
+ list(REMOVE_ITEM PROJECT_SOURCES
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/recoverydeviceinfowidget.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/recoverydeviceinfowidget.h
+ src/recoverydeviceinfowidget.cpp
+ src/recoverydeviceinfowidget.h
+ )
+endif()
+
if(APPLE)
list(APPEND PROJECT_SOURCES
src/platform/macos/macos.mm
@@ -324,6 +349,11 @@ target_link_libraries(iDescriptor PRIVATE
SQLite::SQLite3
)
+if(ENABLE_RECOVERY_DEVICE_SUPPORT)
+ target_link_libraries(iDescriptor PRIVATE ${IRECOVERY_LIBRARY})
+ target_compile_definitions(iDescriptor PRIVATE ENABLE_RECOVERY_DEVICE_SUPPORT)
+endif()
+
target_include_directories(iDescriptor PRIVATE
# Put idevice-rs includes FIRST
${IDEVICE_CPP_INCLUDE_DIR}
@@ -401,6 +431,15 @@ set_target_properties(iDescriptor PROPERTIES
)
if (UNIX AND NOT APPLE)
+
+ # Required on Linux to find libirecovery-1.0.so.5 at runtime
+ if (ENABLE_RECOVERY_DEVICE_SUPPORT)
+ set_target_properties(iDescriptor PROPERTIES
+ # Control library search order - system libs first, then /usr/local/lib
+ INSTALL_RPATH "/usr/lib/x86_64-linux-gnu:/usr/lib:/usr/local/lib:$ORIGIN"
+ )
+ endif()
+
# Add install rules for the project
# include(GNUInstallDirs)
diff --git a/com.idescriptor.idescriptor.json b/com.idescriptor.idescriptor.json
index bcb58b8..9cb125b 100644
--- a/com.idescriptor.idescriptor.json
+++ b/com.idescriptor.idescriptor.json
@@ -10,6 +10,10 @@
"add-ld-path": "."
}
},
+ "sdk-extensions": [
+ "org.freedesktop.Sdk.Extension.rust-stable",
+ "org.freedesktop.Sdk.Extension.llvm20"
+ ],
"cleanup-commands": ["mkdir -p ${FLATPAK_DEST}/lib/ffmpeg"],
"command": "iDescriptor",
"finish-args": [
@@ -19,20 +23,8 @@
"--share=network",
"--device=all",
"--socket=system-bus",
- "--filesystem=/run/usbmuxd:rw",
- "--filesystem=/run/udev:ro",
- "--filesystem=/dev:ro",
- "--filesystem=/sys/bus:ro",
- "--filesystem=/sys/class:ro",
- "--filesystem=/sys/devices:ro",
+ "--filesystem=/run/usbmuxd",
"--system-talk-name=org.freedesktop.Avahi",
- "--talk-name=org.libimobiledevice.usbmuxd",
- "--system-talk-name=org.libimobiledevice.usbmuxd",
- "--system-talk-name=org.freedesktop.UDisks2",
- "--system-talk-name=org.freedesktop.login1",
- "--system-talk-name=org.freedesktop.NetworkManager",
- "--system-talk-name=org.freedesktop.UPower",
- "--system-talk-name=org.freedesktop.systemd1",
"--talk-name=org.freedesktop.Platform.ffmpeg-full",
"--env=QT_MEDIA_BACKEND=ffmpeg",
"--env=AVAHI_COMPAT_NOWARN=1"
@@ -90,7 +82,6 @@
{
"name": "libplist",
"buildsystem": "autotools",
- "config-opts": ["--without-cython"],
"sources": [
{
"type": "git",
@@ -98,26 +89,6 @@
}
]
},
- {
- "name": "libimobiledevice-glue",
- "buildsystem": "autotools",
- "sources": [
- {
- "type": "git",
- "url": "https://github.com/libimobiledevice/libimobiledevice-glue.git"
- }
- ]
- },
- {
- "name": "libtatsu",
- "buildsystem": "autotools",
- "sources": [
- {
- "type": "git",
- "url": "https://github.com/libimobiledevice/libtatsu.git"
- }
- ]
- },
{
"name": "libusb",
"config-opts": ["--disable-static", "--enable-udev"],
@@ -133,26 +104,6 @@
"install -Dm644 COPYING ${FLATPAK_DEST}/share/licenses/libusb/COPYING"
]
},
- {
- "name": "libusbmuxd",
- "buildsystem": "autotools",
- "sources": [
- {
- "type": "git",
- "url": "https://github.com/libimobiledevice/libusbmuxd.git"
- }
- ]
- },
- {
- "name": "libimobiledevice",
- "buildsystem": "autotools",
- "sources": [
- {
- "type": "git",
- "url": "https://github.com/libimobiledevice/libimobiledevice.git"
- }
- ]
- },
{
"name": "libdaemon",
"buildsystem": "autotools",
@@ -204,16 +155,6 @@
}
]
},
- {
- "name": "libirecovery",
- "buildsystem": "autotools",
- "sources": [
- {
- "type": "git",
- "url": "https://github.com/libimobiledevice/libirecovery.git"
- }
- ]
- },
{
"name": "libde265",
"buildsystem": "autotools",
@@ -271,22 +212,36 @@
},
{
"name": "iDescriptor",
- "buildsystem": "cmake-ninja",
+ "buildsystem": "simple",
"build-options": {
+ "append-path": "/usr/lib/sdk/rust-stable/bin",
+ "env": {
+ "CARGO_HOME": "/run/build/iDescriptor/cargo",
+ "CARGO_NET_OFFLINE": "true",
+ "RUST_BACKTRACE": "1",
+ "CUSTOM_LIB_PATH": "/app/lib",
+ "CUSTOM_INCLUDE_PATH": "/app/include",
+ "CUSTOM_PKGCONFIG_PATH": "/app/lib/pkgconfig",
+ "GO_EXECUTABLE": "/app/sdk/golang/bin/go",
+ "FLATPAK_BUILD": "ON"
+ },
"build-args": ["--share=network"]
},
- "config-opts": [
- "-DCUSTOM_LIB_PATH=/app/lib",
- "-DCUSTOM_INCLUDE_PATH=/app/include",
- "-DCUSTOM_PKGCONFIG_PATH=/app/lib/pkgconfig",
- "-DGO_EXECUTABLE=/app/sdk/golang/bin/go",
- "-DFLATPAK_BUILD=ON"
+ "build-commands": [
+ "cargo --offline fetch --manifest-path lib/idevice-rs/Cargo.toml --verbose",
+ "cargo build --offline --release --all-features --manifest-path lib/idevice-rs/Cargo.toml",
+ "cmake -B build -DGO_EXECUTABLE=$GO_EXECUTABLE .",
+ "cmake --build build --config Release",
+ "install -Dm0755 build/iDescriptor ${FLATPAK_DEST}/bin/iDescriptor"
],
"sources": [
{
"type": "dir",
"path": "."
- }
+ },
+ "cargo-sources.idevice-rs.json",
+ "cargo-sources.idevice-rs.idevice.json",
+ "cargo-sources.idevice-rs.cpp.plist_ffi.json"
]
}
]
diff --git a/resources.qrc b/resources.qrc
index 3a4a582..93991bc 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -37,6 +37,7 @@
resources/icons/MaterialSymbolsFolder.png
resources/icons/QlementineIconsWireless116.png
resources/icons/UimProcess.png
+ resources/icons/LetsIconsHorizontalDownLeftMainLight.png
qml/MapView.qml
resources/iphone.png
resources/ios-wallpapers/iphone-ios4.png
diff --git a/resources/icons/LetsIconsHorizontalDownLeftMainLight.png b/resources/icons/LetsIconsHorizontalDownLeftMainLight.png
new file mode 100644
index 0000000..ba3badf
Binary files /dev/null and b/resources/icons/LetsIconsHorizontalDownLeftMainLight.png differ
diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp
index 5769e2d..6fd5b50 100644
--- a/src/afcexplorerwidget.cpp
+++ b/src/afcexplorerwidget.cpp
@@ -44,7 +44,8 @@
#include
#include
-AfcExplorerWidget::AfcExplorerWidget(iDescriptorDevice *device, bool favEnabled,
+AfcExplorerWidget::AfcExplorerWidget(const iDescriptorDevice *device,
+ bool favEnabled,
AfcClientHandle *afcClient, QString root,
QWidget *parent)
: QWidget(parent), m_device(device), m_favEnabled(favEnabled),
diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h
index a20c434..51f0c66 100644
--- a/src/afcexplorerwidget.h
+++ b/src/afcexplorerwidget.h
@@ -46,7 +46,7 @@ class AfcExplorerWidget : public QWidget
{
Q_OBJECT
public:
- explicit AfcExplorerWidget(iDescriptorDevice *device = nullptr,
+ explicit AfcExplorerWidget(const iDescriptorDevice *device = nullptr,
bool favEnabled = false,
AfcClientHandle *afcClient = nullptr,
QString root = "/", QWidget *parent = nullptr);
@@ -87,7 +87,7 @@ private:
ZIconWidget *m_homeButton;
ZIconWidget *m_upButton;
ZIconWidget *m_enterButton;
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
bool m_favEnabled;
AfcClientHandle *m_afc;
QString m_errorMessage;
diff --git a/src/appcontext.cpp b/src/appcontext.cpp
index b31f96f..4224477 100644
--- a/src/appcontext.cpp
+++ b/src/appcontext.cpp
@@ -25,6 +25,7 @@
#include "networkdevicemanager.h"
#include
#include
+#include
#include
#include
#include
@@ -98,11 +99,13 @@ void AppContext::cachePairedDevices()
auto conn = UsbmuxdConnection::default_new(0);
if (conn.is_err()) {
qDebug() << "ERROR: Failed to connect to usbmuxd!";
+ return;
}
auto devices = conn.unwrap().get_devices();
if (devices.is_err()) {
qDebug() << "ERROR: Failed to get device list!";
+ return;
}
for (const auto &device : devices.unwrap()) {
@@ -190,6 +193,12 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
{
emit initStarted(uniq);
+
+ if (auto device = getDevice(uniq)) {
+ emit deviceAlreadyExists(uniq);
+ return;
+ }
+
try {
auto initResult = std::make_shared();
@@ -237,7 +246,7 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
watcher->setFuture(future);
connect(
watcher, &QFutureWatcher::finished, this,
- [this, uniq, initResult, addType, conn_type, watcher]() {
+ [this, uniq, initResult, addType, conn_type, watcher]() mutable {
watcher->deleteLater();
qDebug() << "init_idescriptor_device success ?: "
<< initResult->success;
@@ -245,6 +254,9 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
if (!initResult->success) {
qDebug() << "Failed to initialize device with" << uniq;
emit initFailed(uniq);
+ // TODO:it could also be password protected, so check for
+ // that Initialization failed, cleaning up resources.
+ // PasswordProtected
if (initResult->error && initResult->error->code ==
PairingDialogResponsePending) {
if (addType == AddType::Regular) {
@@ -266,7 +278,8 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
}
});
// FIXME: free properly and move to a better place
- QtConcurrent::run([uniq, this]() {
+ QThreadPool::globalInstance()->start([uniq,
+ this]() {
UsbmuxdConnectionHandle *usbmuxd_conn = nullptr;
UsbmuxdAddrHandle *addr_handle = nullptr;
IdeviceProviderHandle *provider = nullptr;
@@ -433,6 +446,16 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
}
qDebug() << "Device initialized: " << uniq;
+ /*
+ We need this because wireless devices get initialized
+ with Mac addresses. Even though a Mac address is unique, we
+ are better off using the "UDID" as the unique identifier.
+ This is only required for wireless devices, Usb devices
+ already use the UDID but it doesn't hurt to set it for them
+ as well
+ */
+ uniq.set(initResult->deviceInfo.UniqueDeviceID, false);
+
iDescriptorDevice *device = new iDescriptorDevice{
.udid = uniq.get().toStdString(),
.conn_type = conn_type,
@@ -444,8 +467,10 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
.diagRelay = initResult->diagRelay,
.heartbeatThread = initResult->heartbeatThread};
m_devices[device->udid] = device;
- if (addType == AddType::Regular) {
- qDebug() << "Regular device added: " << uniq;
+ if (addType == AddType::Wireless ||
+ addType == AddType::UpgradeToWireless ||
+ addType == AddType::Regular) {
+ qDebug() << "Wireless device added: " << uniq;
// SettingsManager::sharedInstance()->doIfEnabled(
// SettingsManager::Setting::AutoRaiseWindow, []() {
// if (MainWindow *mainWindow =
@@ -477,12 +502,6 @@ int AppContext::getConnectedDeviceCount() const
// #endif
}
-/*
- FIXME:
- on macOS, sometimes you get wireless disconnects even though we are not
- listening for wireless devices it does not have any to do with us, but
- it still happens so be aware of that
- */
void AppContext::removeDevice(QString _udid)
{
const std::string udid = _udid.toStdString();
@@ -513,7 +532,11 @@ void AppContext::removeDevice(QString _udid)
device->deviceInfo.isWireless);
emit deviceChange();
+ qDebug() << "Waiting to acquire lock for device cleanup: "
+ << QString::fromStdString(udid);
std::lock_guard lock(device->mutex);
+ qDebug() << "Acquired lock, cleaning up device: "
+ << QString::fromStdString(udid);
// FIXME: implement proper cleanup
if (device->afcClient)
@@ -524,7 +547,7 @@ void AppContext::removeDevice(QString _udid)
if (device->heartbeatThread) {
device->heartbeatThread->requestInterruption();
- device->heartbeatThread->wait();
+ // device->heartbeatThread->wait();
delete device->heartbeatThread;
}
@@ -534,30 +557,29 @@ void AppContext::removeDevice(QString _udid)
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
void AppContext::removeRecoveryDevice(uint64_t ecid)
{
- // if (!m_recoveryDevices.contains(ecid)) {
- // qDebug() << "Device with ECID " + QString::number(ecid) +
- // " not found. Please report this issue.";
- // return;
- // }
+ if (!m_recoveryDevices.contains(ecid)) {
+ qDebug() << "Device with ECID " + QString::number(ecid) +
+ " not found. Please report this issue.";
+ return;
+ }
- // qDebug() << "Removing recovery device with ECID:" << ecid;
+ qDebug() << "Removing recovery device with ECID:" << ecid;
- // // Fix use-after-free: get pointer before removing from map
- // iDescriptorRecoveryDevice *deviceInfo =
- // m_recoveryDevices.value(ecid); m_recoveryDevices.remove(ecid);
+ iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices.value(ecid);
+ m_recoveryDevices.remove(ecid);
- // emit recoveryDeviceRemoved(ecid);
+ emit recoveryDeviceRemoved(ecid);
+ // TODO: do we need this ?
// emit deviceChange();
- // std::lock_guard lock(*deviceInfo->mutex);
- // delete deviceInfo->mutex;
- // delete deviceInfo;
+ std::lock_guard lock(deviceInfo->mutex);
+ delete deviceInfo;
}
#endif
-iDescriptorDevice *AppContext::getDevice(const std::string &udid)
+iDescriptorDevice *AppContext::getDevice(const std::string &uniq)
{
- return m_devices.value(udid, nullptr);
+ return m_devices.value(uniq, nullptr);
}
QList AppContext::getAllDevices()
@@ -565,12 +587,12 @@ QList AppContext::getAllDevices()
return m_devices.values();
}
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// QList AppContext::getAllRecoveryDevices()
-// {
-// // return m_recoveryDevices.values();
-// }
-// #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+QList AppContext::getAllRecoveryDevices()
+{
+ return m_recoveryDevices.values();
+}
+#endif
// Returns whether there are any devices connected (regular or recovery)
bool AppContext::noDevicesConnected() const
@@ -586,27 +608,35 @@ bool AppContext::noDevicesConnected() const
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
void AppContext::addRecoveryDevice(uint64_t ecid)
{
- // iDescriptorInitDeviceResultRecovery res =
- // init_idescriptor_recovery_device(ecid);
+ auto res = std::make_shared();
- // if (!res.success) {
- // qDebug() << "Failed to initialize recovery device with ECID: "
- // << QString::number(ecid);
- // qDebug() << "Error code: " << res.error;
- // return;
- // }
+ QFuture future = QtConcurrent::run(
+ [this, ecid, res]() { init_idescriptor_recovery_device(ecid, *res); });
+ QFutureWatcher *watcher = new QFutureWatcher();
+ watcher->setFuture(future);
+ connect(watcher, &QFutureWatcher::finished, this,
+ [this, ecid, res, watcher]() {
+ watcher->deleteLater();
+ if (!res->success) {
+ qDebug()
+ << "Failed to initialize recovery device with ECID: "
+ << QString::number(ecid);
+ qDebug() << "Error code: " << res->error;
+ return;
+ }
- // iDescriptorRecoveryDevice *recoveryDevice = new
- // iDescriptorRecoveryDevice(); recoveryDevice->ecid =
- // res.deviceInfo.ecid; recoveryDevice->mode = res.mode;
- // recoveryDevice->cpid = res.deviceInfo.cpid;
- // recoveryDevice->bdid = res.deviceInfo.bdid;
- // recoveryDevice->displayName = res.displayName;
- // recoveryDevice->mutex = new std::recursive_mutex();
+ iDescriptorRecoveryDevice *recoveryDevice =
+ new iDescriptorRecoveryDevice();
+ recoveryDevice->ecid = res->deviceInfo.ecid;
+ recoveryDevice->mode = res->mode;
+ recoveryDevice->cpid = res->deviceInfo.cpid;
+ recoveryDevice->bdid = res->deviceInfo.bdid;
+ recoveryDevice->displayName = res->displayName;
- // m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice;
- // emit recoveryDeviceAdded(recoveryDevice);
- // emit deviceChange();
+ m_recoveryDevices[res->deviceInfo.ecid] = recoveryDevice;
+ emit recoveryDeviceAdded(recoveryDevice);
+ emit deviceChange();
+ });
}
#endif
@@ -631,21 +661,16 @@ AppContext::~AppContext()
}
}
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // for (auto recoveryDevice : m_recoveryDevices) {
- // emit recoveryDeviceRemoved(recoveryDevice->ecid);
- // delete recoveryDevice->mutex;
- // delete recoveryDevice;
- // }
- // #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ for (auto recoveryDevice : m_recoveryDevices) {
+ emit recoveryDeviceRemoved(recoveryDevice->ecid);
+ delete recoveryDevice;
+ }
+#endif
}
void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection)
{
- qDebug() << "New selection -"
- << " Type:" << selection.type
- << " UDID:" << QString::fromStdString(selection.udid)
- << " ECID:" << selection.ecid << " Section:" << selection.section;
if (m_currentSelection.type == selection.type &&
m_currentSelection.udid == selection.udid &&
m_currentSelection.ecid == selection.ecid &&
@@ -712,4 +737,4 @@ void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device)
void AppContext::emitNoPairingFileForWirelessDevice(const QString &udid)
{
emit noPairingFileForWirelessDevice(udid);
-}
\ No newline at end of file
+}
diff --git a/src/appcontext.h b/src/appcontext.h
index 279b2e5..4461a30 100644
--- a/src/appcontext.h
+++ b/src/appcontext.h
@@ -34,14 +34,13 @@ public:
QList getAllDevices();
explicit AppContext(QObject *parent = nullptr);
bool noDevicesConnected() const;
- // QMap
void cachePairingFile(const QString &udid, const QString &pairingFilePath);
const QString getCachedPairingFile(const QString &udid) const;
void tryToConnectToNetworkDevice(const NetworkDevice &device);
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // QList getAllRecoveryDevices();
- // #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ QList getAllRecoveryDevices();
+#endif
~AppContext();
int getConnectedDeviceCount() const;
@@ -52,24 +51,25 @@ public:
private:
QMap m_devices;
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // QMap m_recoveryDevices;
- // #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ QMap m_recoveryDevices;
+#endif
QStringList m_pendingDevices;
DeviceSelection m_currentSelection = DeviceSelection("");
QMap m_pairingFileCache;
void cachePairedDevices();
void emitNoPairingFileForWirelessDevice(const QString &udid);
signals:
- void deviceAdded(iDescriptorDevice *device);
+ void deviceAdded(const iDescriptorDevice *device);
void deviceRemoved(const std::string &udid, const std::string &macAddress,
const std::string &ipAddress, bool wasWireless);
- void devicePaired(iDescriptorDevice *device);
+ void devicePaired(const iDescriptorDevice *device);
void devicePasswordProtected(const QString &udid);
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // void recoveryDeviceAdded(const iDescriptorRecoveryDevice
- // *deviceInfo); void recoveryDeviceRemoved(uint64_t ecid);
- // #endif
+ void deviceAlreadyExists(const iDescriptor::Uniq &uniq);
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ void recoveryDeviceAdded(const iDescriptorRecoveryDevice *deviceInfo);
+ void recoveryDeviceRemoved(uint64_t ecid);
+#endif
void devicePairPending(const QString &udid);
void devicePairingExpired(const QString &udid);
// only fired on wireless devices when we have no pairing file for them
diff --git a/src/core/helpers/parse_recovery_mode.cpp b/src/core/helpers/parse_recovery_mode.cpp
deleted file mode 100644
index 05db856..0000000
--- a/src/core/helpers/parse_recovery_mode.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * iDescriptor: A free and open-source idevice management tool.
- *
- * Copyright (C) 2025 Uncore
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-#include "libirecovery.h"
-#include
-
-std::string parse_recovery_mode(irecv_mode productType)
-{
- switch (productType) {
- case irecv_mode::IRECV_K_RECOVERY_MODE_1:
- case irecv_mode::IRECV_K_RECOVERY_MODE_2:
- case irecv_mode::IRECV_K_RECOVERY_MODE_3:
- case irecv_mode::IRECV_K_RECOVERY_MODE_4:
- return "Recovery Mode";
- case irecv_mode::IRECV_K_WTF_MODE:
- return "WTF Mode";
- case irecv_mode::IRECV_K_DFU_MODE:
- case irecv_mode::IRECV_K_PORT_DFU_MODE:
- return "DFU Mode";
- default:
- return "Unknown Mode";
- }
-}
diff --git a/src/core/helpers/read_afc_file_to_byte_array.cpp b/src/core/helpers/read_afc_file_to_byte_array.cpp
index 9739de7..197ce82 100644
--- a/src/core/helpers/read_afc_file_to_byte_array.cpp
+++ b/src/core/helpers/read_afc_file_to_byte_array.cpp
@@ -47,10 +47,7 @@ QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device,
ServiceManager::safeAfcFileClose(device, handle); // Close handle
return QByteArray();
}
- // Note: afc_file_info_free will be called later if the function returns
- // successfully or when returning early after the file size check.
- qDebug() << "File size of" << path << "is" << info.size;
size_t fileSize = info.size;
if (fileSize == 0) {
ServiceManager::safeAfcFileClose(device, handle);
@@ -75,10 +72,8 @@ QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device,
return QByteArray();
}
- // Only append and free `chunkData` if `afc_file_read` was successful
buffer.append(reinterpret_cast(chunkData), bytesRead);
- afc_file_read_data_free(chunkData,
- bytesRead); // Free memory owned by Rust FFI
+ afc_file_read_data_free(chunkData, bytesRead);
ServiceManager::safeAfcFileClose(device, handle);
diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp
index 84c7f4f..b5827c2 100644
--- a/src/core/services/init_device.cpp
+++ b/src/core/services/init_device.cpp
@@ -21,9 +21,6 @@
#include "../../iDescriptor.h"
// #include "../../servicemanager.h"
#include "../../appcontext.h"
-#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-#include "libirecovery.h"
-#endif
#include "../../heartbeat.h"
#include
@@ -206,6 +203,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc,
d.firmwareVersion = safeGet("FirmwareVersion");
d.productVersion = safeGet("ProductVersion");
d.wifiMacAddress = safeGet("WiFiAddress");
+ d.UniqueDeviceID = safeGet("UniqueDeviceID");
QString q_version = QString::fromStdString(d.productVersion);
QStringList parts = q_version.split('.');
@@ -549,6 +547,9 @@ void init_idescriptor_device(const iDescriptor::Uniq &uniq,
if (val)
plist_print(val);
+ afc_client_set_timeout(afc_client,
+ 5000); // Set AFC client timeout to 5 seconds
+
result.provider = provider;
result.success = true;
result.afcClient = afc_client;
@@ -593,64 +594,62 @@ cleanup:
}
}
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// iDescriptorInitDeviceResultRecovery
-// init_idescriptor_recovery_device(uint64_t ecid)
-// {
-// qDebug() << "Initializing iDescriptor recovery device with ECID: " <<
-// ecid; iDescriptorInitDeviceResultRecovery result = {};
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+void init_idescriptor_recovery_device(
+ uint64_t ecid, iDescriptorInitDeviceResultRecovery &result)
+{
+ qDebug() << "Initializing iDescriptor recovery device with ECID: " << ecid;
+ result = {};
-// irecv_client_t client = nullptr;
-// const irecv_device_info *deviceInfo = nullptr;
-// irecv_device_t device = nullptr;
-// const DeviceDatabaseInfo *info = nullptr;
+ irecv_client_t client = nullptr;
+ const irecv_device_info *deviceInfo = nullptr;
+ irecv_device_t device = nullptr;
+ const DeviceDatabaseInfo *info = nullptr;
-// irecv_error_t ret = irecv_open_with_ecid_and_attempts(
-// &client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES);
+ irecv_error_t ret = irecv_open_with_ecid_and_attempts(
+ &client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES);
-// if (ret != IRECV_E_SUCCESS) {
-// qDebug() << "Failed to open recovery client with ECID:" << ecid
-// << "Error:" << ret;
-// result.error = ret;
-// goto cleanup;
-// }
+ if (ret != IRECV_E_SUCCESS) {
+ qDebug() << "Failed to open recovery client with ECID:" << ecid
+ << "Error:" << ret;
+ result.error = ret;
+ goto cleanup;
+ }
-// ret = irecv_get_mode(client, (int *)&result.mode);
-// if (ret != IRECV_E_SUCCESS) {
-// qDebug() << "Failed to get recovery mode. Error:" << ret;
-// result.error = ret;
-// goto cleanup;
-// }
+ ret = irecv_get_mode(client, (int *)&result.mode);
+ if (ret != IRECV_E_SUCCESS) {
+ qDebug() << "Failed to get recovery mode. Error:" << ret;
+ result.error = ret;
+ goto cleanup;
+ }
-// deviceInfo = irecv_get_device_info(client);
-// if (!deviceInfo) {
-// qDebug() << "Failed to get device info from recovery client";
-// result.error = IRECV_E_UNKNOWN_ERROR;
-// goto cleanup;
-// }
+ deviceInfo = irecv_get_device_info(client);
+ if (!deviceInfo) {
+ qDebug() << "Failed to get device info from recovery client";
+ result.error = IRECV_E_UNKNOWN_ERROR;
+ goto cleanup;
+ }
-// if (irecv_devices_get_device_by_client(client, &device) ==
-// IRECV_E_SUCCESS &&
-// device && device->hardware_model) {
-// qDebug() << "Recovery device hardware_model: "
-// << device->hardware_model;
-// info =
-// DeviceDatabase::findByHwModel(std::string(device->hardware_model));
-// } else {
-// qDebug() << "Could not resolve hardware_model from client.";
-// }
+ if (irecv_devices_get_device_by_client(client, &device) ==
+ IRECV_E_SUCCESS &&
+ device && device->hardware_model) {
+ qDebug() << "Recovery device hardware_model: "
+ << device->hardware_model;
+ info =
+ DeviceDatabase::findByHwModel(std::string(device->hardware_model));
+ } else {
+ qDebug() << "Could not resolve hardware_model from client.";
+ }
-// result.displayName =
-// info ? (info->displayName ? info->displayName : info->marketingName)
-// : "Unknown Device";
-// result.deviceInfo = *deviceInfo;
-// result.success = true;
+ result.displayName =
+ info ? (info->displayName ? info->displayName : info->marketingName)
+ : "Unknown Device";
+ result.deviceInfo = *deviceInfo;
+ result.success = true;
-// cleanup:
-// if (client) {
-// irecv_close(client);
-// }
-
-// return result;
-// }
-// #endif // ENABLE_RECOVERY_DEVICE_SUPPORT
+cleanup:
+ if (client) {
+ irecv_close(client);
+ }
+}
+#endif // ENABLE_RECOVERY_DEVICE_SUPPORT
diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp
index 95cab5b..1d7380a 100644
--- a/src/devdiskimagehelper.cpp
+++ b/src/devdiskimagehelper.cpp
@@ -29,7 +29,7 @@
#include
#include
-DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device,
+DevDiskImageHelper::DevDiskImageHelper(const iDescriptorDevice *device,
QWidget *parent)
: QDialog(parent), m_device(device)
{
@@ -119,7 +119,10 @@ void DevDiskImageHelper::start()
QString::number(deviceMajorVersion) + ".");
}
} else {
- finishWithSuccess();
+ showStatus("Developer disk image is not available for iOS version " +
+ QString::number(deviceMajorVersion) +
+ ". Please use a device with iOS 6 or above.",
+ true);
return;
}
}
@@ -224,7 +227,7 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version,
paths.second.toStdString().c_str());
if (err == nullptr) {
- return finishWithSuccess();
+ return finishWithSuccess(true);
}
qDebug() << "onImageDownloadFinished:" << err->code
@@ -265,10 +268,21 @@ void DevDiskImageHelper::showStatus(const QString &message, bool isError)
show();
}
-void DevDiskImageHelper::finishWithSuccess()
+/*
+ waiting is sometimes required because services
+ may not become available
+ as soon as the img is mounted
+*/
+void DevDiskImageHelper::finishWithSuccess(bool wait)
{
- m_loadingIndicator->stop();
- accept();
+ auto handler = [this]() {
+ m_loadingIndicator->stop();
+ accept();
+ };
+ if (wait) {
+ return QTimer::singleShot(3000, handler);
+ }
+ handler();
}
void DevDiskImageHelper::finishWithError(const QString &errorMessage)
diff --git a/src/devdiskimagehelper.h b/src/devdiskimagehelper.h
index a589448..2c1b9ec 100644
--- a/src/devdiskimagehelper.h
+++ b/src/devdiskimagehelper.h
@@ -32,7 +32,7 @@ class DevDiskImageHelper : public QDialog
{
Q_OBJECT
public:
- explicit DevDiskImageHelper(iDescriptorDevice *device,
+ explicit DevDiskImageHelper(const iDescriptorDevice *device,
QWidget *parent = nullptr);
// Start the mounting process
@@ -55,10 +55,10 @@ private:
void showStatus(const QString &message, bool isError = false);
void showMountUI();
void showRetryUI(const QString &errorMessage);
- void finishWithSuccess();
+ void finishWithSuccess(bool wait = false);
void finishWithError(const QString &errorMessage);
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
QLabel *m_statusLabel;
QProcessIndicator *m_loadingIndicator;
diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp
index 3bdf20a..5175008 100644
--- a/src/devdiskmanager.cpp
+++ b/src/devdiskmanager.cpp
@@ -322,7 +322,7 @@ bool DevDiskManager::isImageDownloaded(const QString &version,
return QFile::exists(dmgPath) && QFile::exists(sigPath);
}
-bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device,
+bool DevDiskManager::downloadCompatibleImage(const iDescriptorDevice *device,
std::function callback)
{
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
@@ -403,7 +403,7 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device,
}
// FIXME:DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED
-bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
+bool DevDiskManager::mountCompatibleImage(const iDescriptorDevice *device)
{
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
@@ -493,7 +493,7 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
}
bool DevDiskManager::mountImage(const QString &version,
- iDescriptorDevice *device)
+ const iDescriptorDevice *device)
{
const QString downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
diff --git a/src/devdiskmanager.h b/src/devdiskmanager.h
index 6d49071..f9d27ae 100644
--- a/src/devdiskmanager.h
+++ b/src/devdiskmanager.h
@@ -49,7 +49,7 @@ public:
// Mount operations
- bool mountImage(const QString &version, iDescriptorDevice *device);
+ bool mountImage(const QString &version, const iDescriptorDevice *device);
bool unmountImage();
std::pair getPathsForVersion(const QString &version);
@@ -58,8 +58,8 @@ public:
const char *mounted_sig, uint64_t mounted_sig_len);
QByteArray getImageListData() const { return m_imageListJsonData; }
- bool mountCompatibleImage(iDescriptorDevice *device);
- bool downloadCompatibleImage(iDescriptorDevice *device,
+ bool mountCompatibleImage(const iDescriptorDevice *device);
+ bool downloadCompatibleImage(const iDescriptorDevice *device,
std::function callback);
signals:
diff --git a/src/deviceimagewidget.cpp b/src/deviceimagewidget.cpp
index fab01cb..fa45a27 100644
--- a/src/deviceimagewidget.cpp
+++ b/src/deviceimagewidget.cpp
@@ -26,7 +26,8 @@
#include
#include
-DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent)
+DeviceImageWidget::DeviceImageWidget(const iDescriptorDevice *device,
+ QWidget *parent)
: QWidget(parent), m_device(device)
{
QVBoxLayout *layout = new QVBoxLayout(this);
diff --git a/src/deviceimagewidget.h b/src/deviceimagewidget.h
index c4e4b8a..eb898b6 100644
--- a/src/deviceimagewidget.h
+++ b/src/deviceimagewidget.h
@@ -30,7 +30,7 @@ class DeviceImageWidget : public QWidget
Q_OBJECT
public:
- explicit DeviceImageWidget(iDescriptorDevice *device,
+ explicit DeviceImageWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
~DeviceImageWidget();
@@ -47,7 +47,7 @@ private:
QPixmap createCompositeImage() const;
QRect findScreenArea(const QPixmap &mockup) const;
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
ResponsiveQLabel *m_imageLabel;
QTimer *m_timeUpdateTimer;
diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp
index bb58bf8..c72dac6 100644
--- a/src/deviceinfowidget.cpp
+++ b/src/deviceinfowidget.cpp
@@ -25,6 +25,7 @@
#include "iDescriptor.h"
#include "infolabel.h"
#include "privateinfolabel.h"
+#include "toolboxwidget.h"
#include
#include
#include
@@ -44,7 +45,8 @@
#include
#include
-DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
+DeviceInfoWidget::DeviceInfoWidget(const iDescriptorDevice *device,
+ QWidget *parent)
: QWidget(parent), m_device(device)
{
// Main layout with horizontal orientation
@@ -77,22 +79,22 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown",
1.0, this);
shutdownBtn->setIconSize(QSize(20, 20));
- // connect(shutdownBtn, &ZIconWidget::clicked, this,
- // [device]() { ToolboxWidget::shutdownDevice(device); });
+ connect(shutdownBtn, &ZIconWidget::clicked, this,
+ [device]() { ToolboxWidget::shutdownDevice(device); });
ZIconWidget *restartBtn =
new ZIconWidget(QIcon(":/resources/icons/IcTwotoneRestartAlt.png"),
"Restart", 1.0, this);
restartBtn->setIconSize(QSize(20, 20));
- // connect(restartBtn, &ZIconWidget::clicked, this,
- // [device]() { ToolboxWidget::restartDevice(device); });
+ connect(restartBtn, &ZIconWidget::clicked, this,
+ [device]() { ToolboxWidget::restartDevice(device); });
ZIconWidget *recoveryBtn =
new ZIconWidget(QIcon(":/resources/icons/HugeiconsWrench01.png"),
"Recovery", 1.0, this);
recoveryBtn->setIconSize(QSize(20, 20));
- // connect(recoveryBtn, &ZIconWidget::clicked, this,
- // [device]() { ToolboxWidget::_enterRecoveryMode(device); });
+ connect(recoveryBtn, &ZIconWidget::clicked, this,
+ [device]() { ToolboxWidget::enterRecoveryMode(device); });
actionsLayout->addWidget(shutdownBtn);
actionsLayout->addWidget(restartBtn);
diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h
index 4d6e0be..0f7d7e4 100644
--- a/src/deviceinfowidget.h
+++ b/src/deviceinfowidget.h
@@ -31,7 +31,7 @@ class DeviceInfoWidget : public QWidget
{
Q_OBJECT
public:
- explicit DeviceInfoWidget(iDescriptorDevice *device,
+ explicit DeviceInfoWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
~DeviceInfoWidget(); // added destructor
@@ -39,7 +39,7 @@ private slots:
void onBatteryMoreClicked();
private:
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
QTimer *m_updateTimer;
void updateBatteryInfo();
void updateChargingStatusIcon();
diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp
index d80ffac..662fd0b 100644
--- a/src/devicemanagerwidget.cpp
+++ b/src/devicemanagerwidget.cpp
@@ -20,10 +20,10 @@
#include "devicemanagerwidget.h"
#include "appcontext.h"
#include "devicemenuwidget.h"
-// #include "devicependingwidget.h"
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// #include "recoverydeviceinfowidget.h"
-// #endif
+#include "devicependingwidget.h"
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+#include "recoverydeviceinfowidget.h"
+#endif
#include "settingsmanager.h"
#include
@@ -33,7 +33,7 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
setupUI();
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
- [this](iDescriptorDevice *device) {
+ [this](const iDescriptorDevice *device) {
addDevice(device);
// Apply settings-based behavior for switching to new device
@@ -70,7 +70,7 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
});
connect(AppContext::sharedInstance(), &AppContext::devicePaired, this,
- [this](iDescriptorDevice *device) {
+ [this](const iDescriptorDevice *device) {
addPairedDevice(device);
// SettingsManager::sharedInstance()->doIfEnabled(
// SettingsManager::Setting::SwitchToNewDevice,
@@ -82,22 +82,19 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
emit updateNoDevicesConnected();
});
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // connect(AppContext::sharedInstance(),
- // &AppContext::recoveryDeviceAdded,
- // this, [this](const iDescriptorRecoveryDevice
- // *recoveryDeviceInfo) {
- // addRecoveryDevice(recoveryDeviceInfo);
- // emit updateNoDevicesConnected();
- // });
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceAdded,
+ this, [this](const iDescriptorRecoveryDevice *recoveryDeviceInfo) {
+ addRecoveryDevice(recoveryDeviceInfo);
+ emit updateNoDevicesConnected();
+ });
- // connect(AppContext::sharedInstance(),
- // &AppContext::recoveryDeviceRemoved,
- // this, [this](uint64_t ecid) {
- // removeRecoveryDevice(ecid);
- // emit updateNoDevicesConnected();
- // });
- // #endif
+ connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved,
+ this, [this](uint64_t ecid) {
+ removeRecoveryDevice(ecid);
+ emit updateNoDevicesConnected();
+ });
+#endif
connect(AppContext::sharedInstance(), &AppContext::devicePairingExpired,
this, [this](const QString &udid) {
@@ -130,7 +127,7 @@ void DeviceManagerWidget::setupUI()
&DeviceManagerWidget::onDeviceSelectionChanged);
}
-void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
+void DeviceManagerWidget::addDevice(const iDescriptorDevice *device)
{
if (m_deviceWidgets.contains(device->udid)) {
qWarning() << "Device already exists:"
@@ -150,52 +147,51 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
device->deviceInfo.isWireless)};
}
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// void DeviceManagerWidget::addRecoveryDevice(
-// const iDescriptorRecoveryDevice *device)
-// {
-// try {
-// // Create device info widget
-// RecoveryDeviceInfoWidget *recoveryDeviceInfoWidget =
-// new RecoveryDeviceInfoWidget(device);
-// m_recoveryDeviceWidgets.insert(
-// device->ecid,
-// std::pair{recoveryDeviceInfoWidget,
-// m_sidebar->addRecoveryDevice(device->ecid)});
-// m_stackedWidget->addWidget(recoveryDeviceInfoWidget);
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+void DeviceManagerWidget::addRecoveryDevice(
+ const iDescriptorRecoveryDevice *device)
+{
+ try {
+ // Create device info widget
+ RecoveryDeviceInfoWidget *recoveryDeviceInfoWidget =
+ new RecoveryDeviceInfoWidget(device);
+ m_recoveryDeviceWidgets.insert(
+ device->ecid,
+ std::pair{recoveryDeviceInfoWidget,
+ m_sidebar->addRecoveryDevice(device->ecid)});
+ m_stackedWidget->addWidget(recoveryDeviceInfoWidget);
-// } catch (...) {
-// qDebug() << "Error initializing recovery device";
-// }
-// }
+ } catch (...) {
+ qDebug() << "Error initializing recovery device";
+ }
+}
-// void DeviceManagerWidget::removeRecoveryDevice(uint64_t ecid)
-// {
-// qDebug() << "Removing recovery device with ECID:" << ecid;
-// if (!m_recoveryDeviceWidgets.contains(ecid)) {
-// qDebug() << "Recovery device with ECID" + QString::number(ecid) +
-// " not found. Please report this issue.";
-// return;
-// }
+void DeviceManagerWidget::removeRecoveryDevice(uint64_t ecid)
+{
+ qDebug() << "Removing recovery device with ECID:" << ecid;
+ if (!m_recoveryDeviceWidgets.contains(ecid)) {
+ qDebug() << "Recovery device with ECID" + QString::number(ecid) +
+ " not found. Please report this issue.";
+ return;
+ }
-// RecoveryDeviceInfoWidget *deviceWidget =
-// m_recoveryDeviceWidgets[ecid].first;
-// RecoveryDeviceSidebarItem *sidebarItem =
-// m_recoveryDeviceWidgets[ecid].second;
+ RecoveryDeviceInfoWidget *deviceWidget =
+ m_recoveryDeviceWidgets[ecid].first;
+ RecoveryDeviceSidebarItem *sidebarItem =
+ m_recoveryDeviceWidgets[ecid].second;
-// if (deviceWidget != nullptr && sidebarItem != nullptr) {
-// qDebug() << "Recovery device exists removing:" <<
-// QString::number(ecid);
+ if (deviceWidget != nullptr && sidebarItem != nullptr) {
+ qDebug() << "Recovery device exists removing:" << QString::number(ecid);
-// m_recoveryDeviceWidgets.remove(ecid);
-// m_stackedWidget->removeWidget(deviceWidget);
-// m_sidebar->removeRecoveryDevice(ecid);
-// deviceWidget->deleteLater();
+ m_recoveryDeviceWidgets.remove(ecid);
+ m_stackedWidget->removeWidget(deviceWidget);
+ m_sidebar->removeRecoveryDevice(ecid);
+ deviceWidget->deleteLater();
-// emit updateNoDevicesConnected();
-// }
-// }
-// #endif
+ emit updateNoDevicesConnected();
+ }
+}
+#endif
void DeviceManagerWidget::addPendingDevice(const QString &uniq, bool locked)
{
@@ -222,27 +218,26 @@ void DeviceManagerWidget::addPendingDevice(const QString &uniq, bool locked)
void DeviceManagerWidget::removePendingDevice(const QString &udid)
{
- // qDebug() << "Removing pending device:" << udid;
- // if (!m_pendingDeviceWidgets.contains(udid.toStdString())) {
- // qDebug() << "Pending device not found:" << udid;
- // return;
- // }
- // std::string udidStr = udid.toStdString();
- // DevicePendingWidget *deviceWidget =
- // m_pendingDeviceWidgets[udidStr].first; DevicePendingSidebarItem
- // *sidebarItem =
- // m_pendingDeviceWidgets[udidStr].second;
+ qDebug() << "Removing pending device:" << udid;
+ if (!m_pendingDeviceWidgets.contains(udid.toStdString())) {
+ qDebug() << "Pending device not found:" << udid;
+ return;
+ }
+ std::string udidStr = udid.toStdString();
+ DevicePendingWidget *deviceWidget = m_pendingDeviceWidgets[udidStr].first;
+ DevicePendingSidebarItem *sidebarItem =
+ m_pendingDeviceWidgets[udidStr].second;
- // if (deviceWidget != nullptr && sidebarItem != nullptr) {
- // qDebug() << "Pending device exists removing:" << udid;
- // m_pendingDeviceWidgets.remove(udidStr);
- // m_stackedWidget->removeWidget(deviceWidget);
- // m_sidebar->removePendingDevice(udidStr);
- // deviceWidget->deleteLater();
- // }
+ if (deviceWidget != nullptr && sidebarItem != nullptr) {
+ qDebug() << "Pending device exists removing:" << udid;
+ m_pendingDeviceWidgets.remove(udidStr);
+ m_stackedWidget->removeWidget(deviceWidget);
+ m_sidebar->removePendingDevice(udidStr);
+ deviceWidget->deleteLater();
+ }
}
-void DeviceManagerWidget::addPairedDevice(iDescriptorDevice *device)
+void DeviceManagerWidget::addPairedDevice(const iDescriptorDevice *device)
{
qDebug() << "Device paired:" << QString::fromStdString(device->udid);
@@ -336,19 +331,18 @@ void DeviceManagerWidget::onDeviceSelectionChanged(
}
break;
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // case DeviceSelection::Recovery:
- // if (m_recoveryDeviceWidgets.contains(selection.ecid)) {
- // QWidget *tabWidget =
- // m_recoveryDeviceWidgets[selection.ecid].first; if
- // (tabWidget) {
- // m_stackedWidget->setCurrentWidget(tabWidget);
- // // Clear current device since we're viewing recovery
- // device m_currentDeviceUuid = "";
- // }
- // }
- // break;
- // #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ case DeviceSelection::Recovery:
+ if (m_recoveryDeviceWidgets.contains(selection.ecid)) {
+ QWidget *tabWidget = m_recoveryDeviceWidgets[selection.ecid].first;
+ if (tabWidget) {
+ m_stackedWidget->setCurrentWidget(tabWidget);
+ // Clear current device since we're viewing recovery device
+ m_currentDeviceUuid = "";
+ }
+ }
+ break;
+#endif
case DeviceSelection::Pending:
if (m_pendingDeviceWidgets.contains(selection.udid)) {
diff --git a/src/devicemanagerwidget.h b/src/devicemanagerwidget.h
index deb8a83..d926bb9 100644
--- a/src/devicemanagerwidget.h
+++ b/src/devicemanagerwidget.h
@@ -24,9 +24,9 @@
#include "devicependingwidget.h"
#include "devicesidebarwidget.h"
#include "iDescriptor.h"
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// #include "recoverydeviceinfowidget.h"
-// #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+#include "recoverydeviceinfowidget.h"
+#endif
#include
#include
#include
@@ -51,14 +51,14 @@ private slots:
private:
void setupUI();
- void addDevice(iDescriptorDevice *device);
+ void addDevice(const iDescriptorDevice *device);
void removeDevice(const std::string &uuid);
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // void addRecoveryDevice(const iDescriptorRecoveryDevice *device);
- // void removeRecoveryDevice(uint64_t ecid);
- // #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ void addRecoveryDevice(const iDescriptorRecoveryDevice *device);
+ void removeRecoveryDevice(uint64_t ecid);
+#endif
void addPendingDevice(const QString &udid, bool locked);
- void addPairedDevice(iDescriptorDevice *device);
+ void addPairedDevice(const iDescriptorDevice *device);
void removePendingDevice(const QString &udid);
QHBoxLayout *m_mainLayout;
@@ -72,12 +72,11 @@ private:
std::pair>
m_pendingDeviceWidgets; // Map to store devices by UDID
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // QMap>
- // m_recoveryDeviceWidgets; // Map to store recovery devices by ECID
- // #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ QMap>
+ m_recoveryDeviceWidgets; // Map to store recovery devices by ECID
+#endif
std::string m_currentDeviceUuid;
};
diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp
index 1d4f1d6..1c180fa 100644
--- a/src/devicemenuwidget.cpp
+++ b/src/devicemenuwidget.cpp
@@ -31,7 +31,8 @@
#include
#include
-DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent)
+DeviceMenuWidget::DeviceMenuWidget(const iDescriptorDevice *device,
+ QWidget *parent)
: QWidget{parent}, m_device(device)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h
index 985c2d1..385481e 100644
--- a/src/devicemenuwidget.h
+++ b/src/devicemenuwidget.h
@@ -31,15 +31,15 @@ class DeviceMenuWidget : public QWidget
{
Q_OBJECT
public:
- explicit DeviceMenuWidget(iDescriptorDevice *device,
+ explicit DeviceMenuWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
void switchToTab(const QString &tabName);
void init();
~DeviceMenuWidget();
private:
- QStackedWidget *stackedWidget; // Pointer to the stacked widget
- iDescriptorDevice *m_device; // Pointer to the iDescriptor device
+ QStackedWidget *stackedWidget;
+ const iDescriptorDevice *m_device;
DeviceInfoWidget *m_deviceInfoWidget;
InstalledAppsWidget *m_installedAppsWidget;
GalleryWidget *m_galleryWidget;
diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp
index bf4face..cf5333a 100644
--- a/src/diskusagewidget.cpp
+++ b/src/diskusagewidget.cpp
@@ -33,7 +33,8 @@ extern "C" {
using namespace iDescriptor;
-DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent)
+DiskUsageWidget::DiskUsageWidget(const iDescriptorDevice *device,
+ QWidget *parent)
: QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0),
m_systemUsage(0), m_appsUsage(0), m_mediaUsage(0), m_othersUsage(0),
m_freeSpace(0)
@@ -501,11 +502,11 @@ void DiskUsageWidget::fetchData()
/*
on older devices if Photos.sqlite is high in size and the device is
connected wirelessly it takes ~5 minutes to read the entire file maybe
- skip on wireless connections on old devices (iPhone 6s in this case)?
+ skip on wireless connections on old devices than iPhone10,1 (iPhone 8)
*/
if (m_device->deviceInfo.is_iPhone && m_device->deviceInfo.isWireless &&
!iDescriptor::Utils::isProductTypeNewer(
- m_device->deviceInfo.rawProductType, "iPhone8,4")) {
+ m_device->deviceInfo.rawProductType, "iPhone10,1")) {
qDebug() << "Skipping gallery usage calculation on older "
"wireless device.";
result["galleryUsage"] = QVariant::fromValue(uint64_t(0));
diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h
index 1052bb0..b6a5c5e 100644
--- a/src/diskusagewidget.h
+++ b/src/diskusagewidget.h
@@ -35,7 +35,7 @@ class DiskUsageWidget : public QWidget
{
Q_OBJECT
public:
- explicit DiskUsageWidget(iDescriptorDevice *device,
+ explicit DiskUsageWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
private:
@@ -45,7 +45,7 @@ private:
enum State { Loading, Ready, Error };
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
State m_state;
QString m_errorMessage;
diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp
index 511d769..1d3843a 100644
--- a/src/exportmanager.cpp
+++ b/src/exportmanager.cpp
@@ -52,7 +52,7 @@ ExportManager::~ExportManager()
m_activeJobs.clear();
}
-QUuid ExportManager::startExport(iDescriptorDevice *device,
+QUuid ExportManager::startExport(const iDescriptorDevice *device,
const QList &items,
const QString &destinationPath,
std::optional altAfc)
diff --git a/src/exportmanager.h b/src/exportmanager.h
index 1da8c71..fb37373 100644
--- a/src/exportmanager.h
+++ b/src/exportmanager.h
@@ -48,7 +48,8 @@ public:
ExportManager(const ExportManager &) = delete;
ExportManager &operator=(const ExportManager &) = delete;
- QUuid startExport(iDescriptorDevice *device, const QList &items,
+ QUuid startExport(const iDescriptorDevice *device,
+ const QList &items,
const QString &destinationPath,
std::optional altAfc = std::nullopt);
diff --git a/src/fileexplorerwidget.cpp b/src/fileexplorerwidget.cpp
index 0b7c024..db7df22 100644
--- a/src/fileexplorerwidget.cpp
+++ b/src/fileexplorerwidget.cpp
@@ -42,7 +42,7 @@
#include
#include
-FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device,
+FileExplorerWidget::FileExplorerWidget(const iDescriptorDevice *device,
QWidget *parent)
: QWidget(parent), m_device(device)
{
diff --git a/src/fileexplorerwidget.h b/src/fileexplorerwidget.h
index 7aa68d8..2a0a898 100644
--- a/src/fileexplorerwidget.h
+++ b/src/fileexplorerwidget.h
@@ -39,7 +39,7 @@ class FileExplorerWidget : public QWidget
{
Q_OBJECT
public:
- explicit FileExplorerWidget(iDescriptorDevice *device,
+ explicit FileExplorerWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
private slots:
@@ -50,7 +50,7 @@ private:
QStackedWidget *m_stackedWidget;
AfcClientHandle *currentAfcClient;
QTreeWidget *m_sidebarTree;
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
// Tree items
QTreeWidgetItem *m_defaultAfcItem;
diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp
index b9d2359..2b23791 100644
--- a/src/gallerywidget.cpp
+++ b/src/gallerywidget.cpp
@@ -50,7 +50,7 @@
https://github.com/ScottKjr3347/iOS_Local_PL_Photos.sqlite_Queries
*/
-GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent)
+GalleryWidget::GalleryWidget(const iDescriptorDevice *device, QWidget *parent)
: QWidget{parent}, m_device(device), m_model(nullptr),
m_albumSelectionWidget(nullptr), m_albumListView(nullptr),
m_photoGalleryWidget(nullptr), m_listView(nullptr), m_backButton(nullptr)
@@ -479,6 +479,18 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath)
m_model = new PhotoModel(m_device, getCurrentFilterType(), this);
m_listView->setModel(m_model);
+ connect(m_model, &PhotoModel::albumPathSet, this, [this]() {
+ // Switch to photo gallery view once album is loaded
+ m_loadingWidget->switchToWidget(m_photoGalleryWidget);
+ // Enable controls and show back button
+ setControlsEnabled(true);
+ m_backButton->show();
+ });
+
+ connect(m_model, &PhotoModel::timedOut, this, [this]() {
+ m_loadingWidget->showError("Timed out loading album");
+ });
+
// Update export button states based on selection
connect(m_listView->selectionModel(),
&QItemSelectionModel::selectionChanged, this, [this]() {
@@ -488,25 +500,14 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath)
});
}
- // connect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model,
- // &PhotoModel::requestThumbnail, Qt::QueuedConnection);
// Set album path and load photos
m_model->setAlbumPath(albumPath);
- // Switch to photo gallery view
- m_loadingWidget->switchToWidget(m_photoGalleryWidget);
- // Enable controls and show back button
- setControlsEnabled(true);
- m_backButton->show();
+ m_loadingWidget->showLoading();
}
void GalleryWidget::onBackToAlbums()
{
- if (m_model) {
- // disconnect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model,
- // &PhotoModel::requestThumbnail);
- }
-
// Switch back to album selection view
m_loadingWidget->switchToWidget(m_albumSelectionWidget);
diff --git a/src/gallerywidget.h b/src/gallerywidget.h
index 2e152bb..096cc25 100644
--- a/src/gallerywidget.h
+++ b/src/gallerywidget.h
@@ -44,7 +44,7 @@ class GalleryWidget : public QWidget
Q_OBJECT
public:
- explicit GalleryWidget(iDescriptorDevice *device,
+ explicit GalleryWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
void load();
~GalleryWidget();
@@ -70,7 +70,7 @@ private:
void onPhotoContextMenu(const QPoint &pos);
PhotoModel::FilterType getCurrentFilterType() const;
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
bool m_loaded = false;
QString m_currentAlbumPath;
diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h
index eb30f9a..7092de8 100644
--- a/src/iDescriptor-ui.h
+++ b/src/iDescriptor-ui.h
@@ -57,6 +57,8 @@
#define COLOR_HYPERLINK QColor("#FF7FFFD4")
#endif
+#define THUMBNAIL_SIZE QSize(128, 128)
+
inline QString mergeStyles(QWidget *widget, const QString &newStyles)
{
if (!widget) {
diff --git a/src/iDescriptor-utils.h b/src/iDescriptor-utils.h
index 93b8cee..7a34a1a 100644
--- a/src/iDescriptor-utils.h
+++ b/src/iDescriptor-utils.h
@@ -59,8 +59,19 @@ public:
bool isMac() const { return m_isMac; };
bool isUdid() const { return !m_isMac; }
+ void set(const QString &uniq, bool isMac = false)
+ {
+ m_uniq = uniq;
+ m_isMac = isMac;
+ };
+ void set(const std::string &uniq, bool isMac = false)
+ {
+ m_uniq = QString::fromStdString(uniq);
+ m_isMac = isMac;
+ };
const QString &get() const { return m_uniq; }
operator QString() const { return m_uniq; }
+ operator std::string() const { return m_uniq.toStdString(); }
private:
QString m_uniq;
@@ -139,8 +150,21 @@ public:
static bool isVideoFile(const QString &fileName)
{
- /* known iPhone video file extensions */
+ /* known iPhone video file extensions (AVI and MKV is not common but it
+ * may be some from some app)*/
return fileName.endsWith(".MOV", Qt::CaseInsensitive) ||
+ fileName.endsWith(".MP4", Qt::CaseInsensitive) ||
+ fileName.endsWith(".M4V", Qt::CaseInsensitive) ||
+ fileName.endsWith(".AVI", Qt::CaseInsensitive) ||
+ fileName.endsWith(".MKV", Qt::CaseInsensitive);
+ }
+
+ static bool isGalleryFile(const QString &fileName)
+ {
+ return fileName.endsWith(".JPG", Qt::CaseInsensitive) ||
+ fileName.endsWith(".PNG", Qt::CaseInsensitive) ||
+ fileName.endsWith(".HEIC", Qt::CaseInsensitive) ||
+ fileName.endsWith(".MOV", Qt::CaseInsensitive) ||
fileName.endsWith(".MP4", Qt::CaseInsensitive) ||
fileName.endsWith(".M4V", Qt::CaseInsensitive);
}
diff --git a/src/iDescriptor.h b/src/iDescriptor.h
index 3a4c057..d4a935e 100644
--- a/src/iDescriptor.h
+++ b/src/iDescriptor.h
@@ -36,6 +36,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -68,11 +69,16 @@
((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF))
#include "devicemonitor.h"
#include "iDescriptor-utils.h"
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+#include
+#endif
#define DeviceLockedMountErrorCode -21
#define NotFoundErrorCode -14
#define ServiceNotFoundErrorCode -15
#define PairingDialogResponsePending -28
+#define InvalidServiceErrorCode -59
+#define TimeoutErrorCode -71
#define DISK_IMAGE_TYPE_DEVELOPER "Developer"
@@ -214,6 +220,8 @@ struct DeviceInfo {
bool isWireless = false;
// empty on USB devices
std::string ipAddress;
+ /* same as udid on iDescriptorDevice */
+ std::string UniqueDeviceID;
};
struct iDescriptorDevice {
@@ -242,34 +250,35 @@ struct iDescriptorInitDeviceResult {
std::shared_ptr diagRelay;
QThread *heartbeatThread;
};
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// struct iDescriptorRecoveryDevice {
-// uint64_t ecid;
-// irecv_mode mode;
-// uint32_t cpid;
-// uint32_t bdid;
-// std::string displayName;
-// std::recursive_mutex *mutex;
-// };
-// #endif
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+struct iDescriptorRecoveryDevice {
+ uint64_t ecid;
+ irecv_mode mode;
+ uint32_t cpid;
+ uint32_t bdid;
+ std::string displayName;
+ std::recursive_mutex mutex;
+};
+struct iDescriptorInitDeviceResultRecovery {
+ irecv_client_t client = nullptr;
+ irecv_device_info deviceInfo;
+ irecv_error_t error;
+ bool success = false;
+ irecv_mode mode = IRECV_K_RECOVERY_MODE_1;
+ const char *displayName = nullptr;
+};
+
+std::string parse_recovery_mode(irecv_mode productType);
+
+void init_idescriptor_recovery_device(uint64_t ecid,
+ iDescriptorInitDeviceResultRecovery &res);
+#endif
struct TakeScreenshotResult {
bool success = false;
QImage img;
};
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// struct iDescriptorInitDeviceResultRecovery {
-// irecv_client_t client = nullptr;
-// irecv_device_info deviceInfo;
-// irecv_error_t error;
-// bool success = false;
-// irecv_mode mode = IRECV_K_RECOVERY_MODE_1;
-// const char *displayName = nullptr;
-// };
-
-// #endif
-
void warn(const QString &message, const QString &title = "Warning",
QWidget *parent = nullptr);
@@ -402,15 +411,8 @@ public:
}
};
-// afc_error_t safe_afc_read_directory(afc_client_t afcClient, idevice_t device,
-// const char *path, char ***dirs);
-
std::string parse_product_type(const std::string &productType);
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// std::string parse_recovery_mode(irecv_mode productType);
-// #endif
-
struct MediaEntry {
std::string name;
bool isDir;
@@ -440,14 +442,6 @@ void init_idescriptor_device(const iDescriptor::Uniq &uniq,
iDescriptorInitDeviceResult &result,
const WirelessInitArgs &wirelessArgs = {"", ""});
-// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
-// iDescriptorInitDeviceResultRecovery
-// init_idescriptor_recovery_device(uint64_t ecid);
-// #endif
-// bool set_location(idevice_t device, char *lat, char *lon);
-
-// bool shutdown(idevice_t device);
-
IdeviceFfiError *mount_dev_image(const iDescriptorDevice *device,
const char *image_file,
const char *signature_file);
@@ -467,8 +461,6 @@ MountedImageInfo _get_mounted_image(const iDescriptorDevice *device);
void mounted_image_info_free(MountedImageInfo &info);
-// bool restart(std::string udid);
-
enum class ImageCompatibility {
Compatible, // Exact match or known compatible version
MaybeCompatible, // Major version matches but minor doesn't
diff --git a/src/imageloader.cpp b/src/imageloader.cpp
index c8fd5da..edf75cf 100644
--- a/src/imageloader.cpp
+++ b/src/imageloader.cpp
@@ -15,6 +15,7 @@ extern "C" {
ImageLoader::ImageLoader(QObject *parent) : QObject(parent)
{
+ // TODO: maybe finetune to hardware ?
m_pool.setMaxThreadCount(10);
// 350 MB cache for thumbnails
m_cache.setMaxCost(350 * 1024 * 1024);
@@ -22,51 +23,144 @@ ImageLoader::ImageLoader(QObject *parent) : QObject(parent)
bool ImageLoader::isLoading(const QString &path)
{
- return m_pending.contains(path);
+ QMutexLocker locker(&m_mutex);
+ return m_pendingTasks.contains(path);
}
void ImageLoader::requestThumbnail(const iDescriptorDevice *device,
- const QString &path, int priority,
- unsigned int row)
+ const QString &path, unsigned int row)
{
if (auto *cached = m_cache.object(path)) {
emit thumbnailReady(path, *cached, row);
return;
}
- if (m_pending.contains(path))
- return;
+ {
+ QMutexLocker locker(&m_mutex);
+ if (m_pendingTasks.contains(path))
+ return;
+ }
- m_pending.insert(path);
+ auto *task = new ImageTask(device, path, row);
- // FIXME: qsize
- auto *task = new ImageTask(device, path, QSize(128, 128), row);
+ {
+ QMutexLocker locker(&m_mutex);
+ m_pendingTasks[path] = task;
+ }
connect(task, &ImageTask::finished, this, &ImageLoader::onTaskFinished,
Qt::QueuedConnection);
+ // Use row as priority
+ m_pool.start(task, row);
+}
+
+/*
+ this method should not load from cache
+ because cached images are already scaled down
+ we need the original image
+*/
+void ImageLoader::requestImageWithCallback(
+ const iDescriptorDevice *device, const QString &path, int priority,
+ std::function callback)
+{
+
+ /*
+ FIXME: priority is passed as row
+ nothing dangerous but a bit hacky, should be handled better
+ */ //scale=false
+ auto *task = new ImageTask(device, path, priority, false);
+
+ /*
+ TODO: should we do this ?
+ this function is meant for the media preview dialog,
+ which only loads a image at a time
+ and not really related to the thumbnails in the photomodel
+ */
+ // m_pendingTasks[path] = task;
+
+ connect(
+ task, &ImageTask::finished, this,
+ [this, path, callback](const QString &, const QPixmap &pixmap,
+ unsigned int row) { callback(pixmap); },
+ Qt::QueuedConnection);
+
m_pool.start(task, priority);
}
void ImageLoader::cancelThumbnail(const QString &path)
{
- m_pending.remove(path);
+ qDebug() << "Attempting to cancel thumbnail loading for" << path;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_pendingTasks.contains(path)) {
+ return;
+ }
+
+ ImageTask *task = m_pendingTasks.value(path);
+ if (task && m_pool.tryTake(task)) {
+ qDebug() << "Cancelled thumbnail loading for" << path;
+ m_pendingTasks.remove(path);
+ delete task;
+ } else {
+ m_pendingTasks.remove(path);
+ }
}
void ImageLoader::clear()
{
- m_pending.clear();
+ qDebug() << "Clearing ImageLoader cache and pending tasks";
+
+ m_pool.clear();
+
+ {
+ QMutexLocker locker(&m_mutex);
+
+ for (auto it = m_pendingTasks.begin(); it != m_pendingTasks.end();) {
+ ImageTask *task = it.value();
+ if (task && m_pool.tryTake(task)) {
+ qDebug() << "Cancelled pending task";
+ delete task;
+ }
+ it = m_pendingTasks.erase(it);
+ }
+ }
+
+ /*
+ TODO: This could make the UI unresponsive but
+ maybe a good approch to handle
+ async cancellation properly(wireless)
+ Wait for any running tasks to complete
+ */
+ // m_pool.waitForDone();
+
+ {
+ QMutexLocker locker(&m_mutex);
+ m_pendingTasks.clear();
+ }
+
m_cache.clear();
}
void ImageLoader::onTaskFinished(const QString &path, const QPixmap &pixmap,
unsigned int row)
{
- // Stale?
- if (!m_pending.contains(path))
- return;
+ ImageTask *task = nullptr;
- m_pending.remove(path);
+ {
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_pendingTasks.contains(path)) {
+ return;
+ }
+
+ task = m_pendingTasks.take(path);
+ }
+
+ if (task) {
+ delete task;
+ }
// Cache
m_cache.insert(path, new QPixmap(pixmap));
@@ -74,18 +168,56 @@ void ImageLoader::onTaskFinished(const QString &path, const QPixmap &pixmap,
emit thumbnailReady(path, pixmap, row);
}
-// Static function that runs in worker thread
-QPixmap ImageLoader::loadThumbnailFromDevice(const iDescriptorDevice *device,
- const QString &filePath,
- const QSize &size)
+// almost a copy of loadThumbnailFromDevice but without any scaling logic
+QPixmap ImageLoader::loadImage(const iDescriptorDevice *device,
+ const QString &filePath)
{
- // Load from device using ServiceManager
QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray(
device, filePath.toUtf8().constData());
if (imageData.isEmpty()) {
qDebug() << "Could not read from device:" << filePath;
- return {}; // Return empty pixmap on error
+ return {};
+ }
+
+ if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) {
+ QPixmap img = load_heic(imageData);
+ return img.isNull() ? QPixmap() : img;
+ }
+
+ QBuffer buffer(&imageData);
+ buffer.open(QIODevice::ReadOnly);
+
+ QImageReader reader(&buffer);
+ if (reader.canRead()) {
+ QImage image = reader.read();
+ if (!image.isNull()) {
+ return QPixmap::fromImage(image);
+ }
+ qDebug() << "QImageReader failed to decode" << filePath
+ << "Error:" << reader.errorString();
+ }
+
+ // Fallback for formats QImageReader might struggle with
+ QPixmap pixmap;
+ if (pixmap.loadFromData(imageData)) {
+ return pixmap;
+ }
+
+ qDebug() << "Could not decode image data for:" << filePath;
+ return {};
+}
+
+QPixmap ImageLoader::loadThumbnailFromDevice(const iDescriptorDevice *device,
+ const QString &filePath,
+ const QSize &size)
+{
+ QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray(
+ device, filePath.toUtf8().constData());
+
+ if (imageData.isEmpty()) {
+ qDebug() << "Could not read from device:" << filePath;
+ return {};
}
if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) {
@@ -95,14 +227,11 @@ QPixmap ImageLoader::loadThumbnailFromDevice(const iDescriptorDevice *device,
Qt::SmoothTransformation);
}
- // Use QImageReader for efficient, low-memory scaled loading
QBuffer buffer(&imageData);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
if (reader.canRead()) {
- // This is the key optimization: it decodes a smaller image directly,
- // saving a massive amount of memory.
reader.setScaledSize(size);
QImage image = reader.read();
if (!image.isNull()) {
@@ -146,10 +275,8 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
// Get file size
AfcFileInfo info = {};
- IdeviceFfiError
- *err_info = // Use distinct variable name for the error from GetFileInfo
- ServiceManager::safeAfcGetFileInfo(
- device, filePath.toUtf8().constData(), &info);
+ IdeviceFfiError *err_info = ServiceManager::safeAfcGetFileInfo(
+ device, filePath.toUtf8().constData(), &info);
uint64_t fileSize = 0;
if (err_info) {
@@ -157,12 +284,11 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
<< "Error:" << err_info->message;
idevice_error_free(err_info);
ServiceManager::safeAfcFileClose(device, fileHandle);
- afc_file_info_free(&info); // Free internal strings of info
return {};
}
fileSize = info.size;
- afc_file_info_free(&info); // Free internal strings of info after use
+ afc_file_info_free(&info);
if (fileSize == 0) {
ServiceManager::safeAfcFileClose(device, fileHandle);
@@ -189,7 +315,7 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
StreamContext *streamCtx =
new StreamContext{device, fileHandle, fileSize, 0};
- // Custom read function that reads from device on-demand
+ // Custom read function
auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int {
StreamContext *ctx = static_cast(opaque);
@@ -201,10 +327,8 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
std::min(static_cast(bufSize),
static_cast(ctx->fileSize - ctx->currentPos));
size_t bytesRead = 0;
- uint8_t *read_data_ptr =
- nullptr; // Pointer to store the data allocated by safeAfcFileRead
+ uint8_t *read_data_ptr = nullptr;
- // Call safeAfcFileRead to get the data into a newly allocated buffer
IdeviceFfiError *err = ServiceManager::safeAfcFileRead(
ctx->device, ctx->fileHandle, &read_data_ptr, toRead, &bytesRead);
@@ -238,9 +362,7 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
// `buf`
if (read_data_ptr) {
memcpy(buf, read_data_ptr, bytesRead);
- afc_file_read_data_free(
- read_data_ptr,
- bytesRead); // Free the memory allocated by safeAfcFileRead
+ afc_file_read_data_free(read_data_ptr, bytesRead);
} else {
qWarning() << "AFC readPacket: read_data_ptr was null but "
"bytesRead > 0. This is unexpected.";
@@ -285,7 +407,7 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
if (err) {
qDebug() << "AFC seek error:" << err->message
<< "code:" << err->code;
- // idevice_error_free(err);
+ idevice_error_free(err);
return -1;
}
@@ -444,6 +566,12 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
QImage imgCopy = img.copy();
// Scale to requested size
+ /*
+ TODO: scaling might become optional
+ if we ever needed the raw frame,
+ might need to abstract the main logic to get the frame
+ and handle scaling separately
+ */
thumbnail = QPixmap::fromImage(
imgCopy.scaled(requestedSize, Qt::KeepAspectRatio,
Qt::SmoothTransformation));
@@ -472,30 +600,3 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
return thumbnail;
}
-
-// todo make sure this is also a task used in mediapreviewdialog.cpp
-QPixmap ImageLoader::loadImage(const iDescriptorDevice *device,
- const QString &filePath)
-{
- QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray(
- device, filePath.toUtf8().constData());
-
- if (imageData.isEmpty()) {
- qDebug() << "Could not read from device:" << filePath;
- return QPixmap(); // Return empty pixmap on error
- }
-
- if (filePath.endsWith(".HEIC")) {
- qDebug() << "Loading HEIC image from data for:" << filePath;
- QPixmap img = load_heic(imageData);
- return img.isNull() ? QPixmap() : img;
- }
-
- QPixmap original;
- if (!original.loadFromData(imageData)) {
- qDebug() << "Could not decode image data for:" << filePath;
- return QPixmap();
- }
-
- return original;
-}
\ No newline at end of file
diff --git a/src/imageloader.h b/src/imageloader.h
index 55ada91..84cd25d 100644
--- a/src/imageloader.h
+++ b/src/imageloader.h
@@ -3,19 +3,20 @@
#include "iDescriptor.h"
#include
+#include
#include
+#include
#include
#include
#include
#include
+class ImageTask;
+
class ImageLoader : public QObject
{
Q_OBJECT
public:
- enum LoadPriority { Low = 0, Medium = 50, High = 100 };
- Q_ENUM(LoadPriority)
-
explicit ImageLoader(QObject *parent = nullptr);
static ImageLoader &sharedInstance()
{
@@ -23,7 +24,11 @@ public:
return instance;
}
void requestThumbnail(const iDescriptorDevice *device, const QString &path,
- int priority, unsigned int row = 0);
+ unsigned int row = 0);
+ void
+ requestImageWithCallback(const iDescriptorDevice *device,
+ const QString &path, int priority,
+ std::function callback);
void cancelThumbnail(const QString &path);
bool isLoading(const QString &path);
void clear();
@@ -46,7 +51,8 @@ private slots:
private:
QThreadPool m_pool;
- QSet m_pending;
+ QHash m_pendingTasks;
+ QMutex m_mutex;
};
#endif // IMAGELOADER_H
\ No newline at end of file
diff --git a/src/imagetask.h b/src/imagetask.h
index 8f723ea..a5cf50a 100644
--- a/src/imagetask.h
+++ b/src/imagetask.h
@@ -1,6 +1,7 @@
#ifndef IMAGETASK_H
#define IMAGETASK_H
+#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include
#include
@@ -15,11 +16,10 @@ class ImageTask : public QObject, public QRunnable
Q_OBJECT
public:
ImageTask(const iDescriptorDevice *device, const QString &path,
- const QSize &thumbnailSize, unsigned int row)
- : m_device(device), m_path(path), m_thumbnailSize(thumbnailSize),
- m_row(row)
+ unsigned int row, bool scale = true)
+ : m_device(device), m_path(path), m_isThumbnail(scale), m_row(row)
{
- setAutoDelete(true);
+ setAutoDelete(false);
}
signals:
@@ -32,20 +32,26 @@ protected:
if (isVideo) {
QPixmap thumbnail = ImageLoader::generateVideoThumbnailFFmpeg(
- m_device, m_path, m_thumbnailSize);
+ m_device, m_path, THUMBNAIL_SIZE);
emit finished(m_path, thumbnail, m_row);
} else {
- QPixmap image = ImageLoader::loadThumbnailFromDevice(
- m_device, m_path, m_thumbnailSize);
- emit finished(m_path, image, m_row);
+ if (m_isThumbnail) {
+ QPixmap image = ImageLoader::loadThumbnailFromDevice(
+ m_device, m_path, THUMBNAIL_SIZE);
+ emit finished(m_path, image, m_row);
+ } else {
+ qDebug() << "Loading full image for:" << m_path;
+ QPixmap image = ImageLoader::loadImage(m_device, m_path);
+ emit finished(m_path, image, m_row);
+ }
}
}
private:
const iDescriptorDevice *m_device;
QString m_path;
- QSize m_thumbnailSize;
+ bool m_isThumbnail;
unsigned int m_row;
};
diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp
index 24ccbbb..8898e9c 100644
--- a/src/installedappswidget.cpp
+++ b/src/installedappswidget.cpp
@@ -46,8 +46,7 @@ AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId,
setMinimumWidth(100);
setCursor(Qt::PointingHandCursor);
- setupUI();
- m_iconLabel->setPixmap(icon);
+ setupUI(icon);
}
void AppTabWidget::setSelected(bool selected)
@@ -56,21 +55,25 @@ void AppTabWidget::setSelected(bool selected)
updateStyles();
}
-void AppTabWidget::setupUI()
+void AppTabWidget::setupUI(const QPixmap &icon)
{
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(10, 8, 10, 8);
mainLayout->setSpacing(10);
- // m_defaultBg = this->palette().color(QPalette::Window);
+
// Icon label
m_iconLabel = new QLabel();
m_iconLabel->setFixedSize(32, 32);
m_iconLabel->setScaledContents(true);
- QPixmap placeholderIcon = QApplication::style()
- ->standardIcon(QStyle::SP_ComputerIcon)
- .pixmap(32, 32);
- m_iconLabel->setPixmap(placeholderIcon);
+ if (!icon.isNull()) {
+ m_iconLabel->setPixmap(icon);
+ } else {
+ QPixmap placeholderIcon = QApplication::style()
+ ->standardIcon(QStyle::SP_ComputerIcon)
+ .pixmap(32, 32);
+ m_iconLabel->setPixmap(placeholderIcon);
+ }
mainLayout->addWidget(m_iconLabel);
// Text container
@@ -133,7 +136,7 @@ void AppTabWidget::updateStyles()
}
}
-InstalledAppsWidget::InstalledAppsWidget(iDescriptorDevice *device,
+InstalledAppsWidget::InstalledAppsWidget(const iDescriptorDevice *device,
QWidget *parent)
: QWidget(parent), m_device(device)
{
@@ -149,7 +152,14 @@ InstalledAppsWidget::InstalledAppsWidget(iDescriptorDevice *device,
fetchInstalledApps();
}
-InstalledAppsWidget::~InstalledAppsWidget() { cleanupHouseArrestClients(); }
+InstalledAppsWidget::~InstalledAppsWidget()
+{
+ cleanupHouseArrestClients();
+ if (m_springboardClient) {
+ springboard_services_free(m_springboardClient);
+ m_springboardClient = nullptr;
+ }
+}
void InstalledAppsWidget::setupUI()
{
@@ -269,6 +279,17 @@ void InstalledAppsWidget::fetchInstalledApps()
QFuture future = QtConcurrent::run([this]() -> QVariantMap {
QVariantMap result;
QVariantList apps;
+ // fetch icon from springboard service
+ IdeviceFfiError *err = nullptr;
+ if (!m_springboardClient) {
+ err = springboard_services_connect(m_device->provider,
+ &m_springboardClient);
+ if (err) {
+ qDebug() << "Error connecting to SpringBoard services:"
+ << QString::fromUtf8(err->message);
+ idevice_error_free(err);
+ }
+ }
try {
InstallationProxyClientHandle *installationProxyClientHandle =
@@ -370,7 +391,36 @@ void InstalledAppsWidget::fetchInstalledApps()
appData["type"] = appType;
- if (!appData["bundleId"].toString().isEmpty()) {
+ QString bundleId = appData["bundleId"].toString();
+
+ if (m_springboardClient && !bundleId.isEmpty()) {
+ void *out_result;
+ size_t out_result_len;
+
+ // FIXME: free out_result
+ // there is no springboard_services_free_data or
+ // similar, create an issue
+ err = springboard_services_get_icon(
+ m_springboardClient,
+ bundleId.toUtf8().constData(), &out_result,
+ &out_result_len);
+ if (err != nullptr) {
+ qWarning() << "Error getting icon for"
+ << appData.value("bundleId") << ":"
+ << QString::fromUtf8(err->message);
+ idevice_error_free(err);
+ } else {
+ QByteArray byteArray(
+ reinterpret_cast(out_result),
+ static_cast(out_result_len));
+ QImage image;
+ image.loadFromData(byteArray);
+ QPixmap pixmap = QPixmap::fromImage(image);
+ appData["icon"] = pixmap;
+ }
+ }
+
+ if (!bundleId.isEmpty()) {
apps.append(appData);
}
}
@@ -424,22 +474,6 @@ void InstalledAppsWidget::onAppsDataReady()
m_appTabs.clear();
m_selectedTab = nullptr;
- // fetch icon from springboard service
- SpringBoardServicesClientHandle *springboardClient = nullptr;
- IdeviceFfiError *err = nullptr;
- err = springboard_services_connect(m_device->provider, &springboardClient);
-
- if (err != nullptr) {
- qDebug() << "Error connecting to SpringBoard services:"
- << QString::fromUtf8(err->message);
- } else {
- /*
- FIXME:springboard_services_connect takes time
- MOVE EVERYTHING INTO QTCONCURRENT SO IT DOESN'T BLOCK UI
- */
- qDebug() << "Successfully connected to SpringBoard services.";
- }
-
// Create tabs for each app
for (const QVariant &appVariant : apps) {
QVariantMap appData = appVariant.toMap();
@@ -447,6 +481,8 @@ void InstalledAppsWidget::onAppsDataReady()
QString bundleId = appData.value("bundleId").toString();
QString version = appData.value("version").toString();
QString appType = appData.value("type").toString();
+ QPixmap icon = appData.value("icon").value();
+
bool fileSharingEnabled =
appData.value("fileSharingEnabled", false).toBool();
@@ -465,26 +501,7 @@ void InstalledAppsWidget::onAppsDataReady()
tabName += " (System)";
}
- if (springboardClient) {
- void *out_result;
- size_t out_result_len;
-
- err = springboard_services_get_icon(springboardClient,
- bundleId.toUtf8().constData(),
- &out_result, &out_result_len);
- if (err != nullptr) {
- qWarning() << "Error getting icon for" << bundleId << ":"
- << QString::fromUtf8(err->message);
- createAppTab(tabName, bundleId, version);
- } else {
- QByteArray byteArray(reinterpret_cast(out_result),
- static_cast(out_result_len));
- QImage image;
- image.loadFromData(byteArray);
- QPixmap pixmap = QPixmap::fromImage(image);
- createAppTab(tabName, bundleId, version, pixmap);
- }
- }
+ createAppTab(tabName, bundleId, version, icon);
// Select first tab if available
if (!m_appTabs.isEmpty()) {
@@ -716,7 +733,8 @@ void InstalledAppsWidget::cleanupHouseArrestClients()
}
if (m_houseArrestClient) {
- house_arrest_client_free(m_houseArrestClient);
+ // FIXME: crash
+ // house_arrest_client_free(m_houseArrestClient);
m_houseArrestClient = nullptr;
}
}
@@ -743,6 +761,7 @@ void InstalledAppsWidget::createLeftPanel()
searchLayout->addWidget(m_searchEdit);
// File sharing filter checkbox
+ // FIXME: crash when toggled
m_fileSharingCheckBox = new QCheckBox("Show Only File Sharing Enabled");
m_fileSharingCheckBox->setChecked(true);
m_fileSharingCheckBox->setStyleSheet("QCheckBox { font-size: 10px; }");
diff --git a/src/installedappswidget.h b/src/installedappswidget.h
index 8aae751..2716601 100644
--- a/src/installedappswidget.h
+++ b/src/installedappswidget.h
@@ -43,7 +43,6 @@
#include
#include
-// Custom App Tab Widget
class AppTabWidget : public QGroupBox
{
Q_OBJECT
@@ -75,7 +74,7 @@ protected:
};
private:
- void setupUI();
+ void setupUI(const QPixmap &icon);
QString m_appName;
QString m_bundleId;
@@ -93,7 +92,7 @@ class InstalledAppsWidget : public QWidget
Q_OBJECT
public:
- explicit InstalledAppsWidget(iDescriptorDevice *device,
+ explicit InstalledAppsWidget(const iDescriptorDevice *device,
QWidget *parent = nullptr);
~InstalledAppsWidget();
@@ -120,7 +119,7 @@ private:
void loadAppContainer(const QString &bundleId);
void cleanupHouseArrestClients();
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
QHBoxLayout *m_mainLayout;
QStackedWidget *m_stackedWidget;
QWidget *m_loadingWidget;
@@ -144,6 +143,7 @@ private:
// App data storage
QList m_appTabs;
AppTabWidget *m_selectedTab = nullptr;
+ SpringBoardServicesClientHandle *m_springboardClient = nullptr;
};
#endif // INSTALLEDAPPSWIDGET_H
\ No newline at end of file
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 2130697..6e57631 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -36,11 +36,9 @@
#include
#include "appcontext.h"
-#include "settingsmanager.h"
-// #include "devicemonitor.h"
-// #include "Toast.h"
#include "networkdevicemanager.h"
#include "networkdeviceswidget.h"
+#include "settingsmanager.h"
#include "statusballoon.h"
#include
#include
@@ -51,7 +49,29 @@
#include "platform/windows/win_common.h"
#endif
-using namespace IdeviceFFI;
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+void handleCallbackRecovery(const irecv_device_event_t *event, void *userData)
+{
+
+ switch (event->type) {
+ case IRECV_DEVICE_ADD:
+ qDebug() << "Recovery device added: ";
+ QMetaObject::invokeMethod(AppContext::sharedInstance(),
+ "addRecoveryDevice", Qt::QueuedConnection,
+ Q_ARG(uint64_t, event->device_info->ecid));
+ break;
+ case IRECV_DEVICE_REMOVE:
+ qDebug() << "Recovery device removed: ";
+ QMetaObject::invokeMethod(AppContext::sharedInstance(),
+ "removeRecoveryDevice", Qt::QueuedConnection,
+ Q_ARG(uint64_t, event->device_info->ecid));
+ break;
+ default:
+ printf("Unhandled recovery event: %d\n", event->type);
+ }
+}
+irecv_device_event_context_t context;
+#endif
MainWindow *MainWindow::sharedInstance()
{
@@ -134,6 +154,20 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
StatusBalloon *statusBalloon = StatusBalloon::sharedInstance();
statusLayout->addWidget(statusBalloon->getButton());
+
+ ZIconWidget *welcomeMenu = new ZIconWidget(
+ QIcon(":/resources/icons/LetsIconsHorizontalDownLeftMainLight.png"),
+ "Switch to Welcome Menu");
+ connect(welcomeMenu, &ZIconWidget::clicked, this, [this, welcomeMenu]() {
+ if (m_mainStackedWidget->currentIndex() != 0) {
+ welcomeMenu->setToolTip("Switch to Connected Devices");
+ return m_mainStackedWidget->setCurrentIndex(0);
+ }
+ welcomeMenu->setToolTip("Switch to Welcome Menu");
+ m_mainStackedWidget->setCurrentIndex(1);
+ });
+
+ statusLayout->addWidget(welcomeMenu);
statusLayout->addStretch(1);
statusLayout->setContentsMargins(0, 0, 0, 0);
@@ -174,19 +208,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
}
#endif
- // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
- // irecv_error_t res_recovery =
- // irecv_device_event_subscribe(&context, handleCallbackRecovery,
- // nullptr);
+#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
+ irecv_error_t res_recovery =
+ irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr);
- // if (res_recovery != IRECV_E_SUCCESS) {
- // qDebug() << "ERROR: Unable to subscribe to recovery device
- // events. "
- // "Error code:"
- // << res_recovery;
- // }
- // qDebug() << "Subscribed to recovery device events successfully.";
- // #endif
+ if (res_recovery != IRECV_E_SUCCESS) {
+ qDebug() << "ERROR: Unable to subscribe to recovery device events. "
+ "Error code:"
+ << res_recovery;
+ }
+ qDebug() << "Subscribed to recovery device events successfully.";
+#endif
// idevice_error_t res = idevice_event_subscribe(handleCallback,
// nullptr); if (res != IDEVICE_E_SUCCESS) {
@@ -195,7 +227,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
// << res;
// }
// qDebug() << "Subscribed to device events successfully.";
- // createMenus();
+ createMenus();
// UpdateProcedure updateProcedure;
// bool packageManagerManaged = false;
@@ -285,7 +317,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
// m_updater->checkForUpdates();
// });
- // Usage in main thread:
m_deviceMonitor = new DeviceMonitorThread(this);
connect(
m_deviceMonitor, &DeviceMonitorThread::deviceEvent, this,
diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp
index edb0ce9..5c075c2 100644
--- a/src/mediapreviewdialog.cpp
+++ b/src/mediapreviewdialog.cpp
@@ -47,20 +47,12 @@
#include
#include
-MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device,
+MediaPreviewDialog::MediaPreviewDialog(const iDescriptorDevice *device,
AfcClientHandle *afcClient,
const QString &filePath, QWidget *parent)
: QDialog(parent), m_device(device), m_filePath(filePath),
- m_isVideo(isVideoFile(filePath)), m_mainLayout(nullptr),
- m_controlsLayout(nullptr), m_imageView(nullptr), m_imageScene(nullptr),
- m_pixmapItem(nullptr), m_videoWidget(nullptr), m_mediaPlayer(nullptr),
- m_videoControlsLayout(nullptr), m_playPauseBtn(nullptr),
- m_stopBtn(nullptr), m_repeatBtn(nullptr), m_timelineSlider(nullptr),
- m_timeLabel(nullptr), m_volumeSlider(nullptr), m_volumeLabel(nullptr),
- m_progressTimer(nullptr), m_loadingLabel(nullptr), m_statusLabel(nullptr),
- m_zoomInBtn(nullptr), m_zoomOutBtn(nullptr), m_zoomResetBtn(nullptr),
- m_fitToWindowBtn(nullptr), m_zoomFactor(1.0), m_isRepeatEnabled(true),
- m_isDraggingTimeline(false), m_videoDuration(0), m_afcClient(afcClient)
+ m_isVideo(iDescriptor::Utils::isVideoFile(filePath)),
+ m_afcClient(afcClient)
{
setWindowTitle(QFileInfo(filePath).fileName() + " - iDescriptor");
@@ -97,28 +89,14 @@ void MediaPreviewDialog::setupUI()
m_mainLayout->setContentsMargins(0, 0, 0, 0);
m_mainLayout->setSpacing(0);
- // Loading label
- m_loadingLabel = new QLabel("Loading...", this);
- m_loadingLabel->setAlignment(Qt::AlignCenter);
- m_loadingLabel->setStyleSheet(
- "QLabel { font-size: 16px; color: #666; padding: 20px; }");
- m_mainLayout->addWidget(m_loadingLabel);
+ m_loadingWidget = new ZLoadingWidget();
+ m_mainLayout->addWidget(m_loadingWidget);
if (m_isVideo) {
setupVideoView();
} else {
setupImageView();
}
-
- // Status bar
- // more margin because of border radius on macOS
- m_statusLabel = new QLabel(this);
-#ifdef Q_OS_MAC
- m_statusLabel->setStyleSheet("QLabel { margin-left: 15px; }");
-#else
- m_statusLabel->setStyleSheet("QLabel { margin-left: 5px; }");
-#endif
- m_mainLayout->addWidget(m_statusLabel);
}
void MediaPreviewDialog::setupImageView()
@@ -129,7 +107,7 @@ void MediaPreviewDialog::setupImageView()
m_imageView->setDragMode(QGraphicsView::ScrollHandDrag);
m_imageView->setRenderHint(QPainter::Antialiasing);
m_imageView->setVisible(false);
- m_mainLayout->addWidget(m_imageView);
+ m_loadingWidget->setupContentWidget(m_imageView);
// Controls layout
m_controlsLayout = new QHBoxLayout();
@@ -166,7 +144,7 @@ void MediaPreviewDialog::setupVideoView()
m_videoWidget->setVisible(false);
m_videoWidget->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
- m_mainLayout->addWidget(m_videoWidget, 1); // Give it stretch factor 1
+ m_loadingWidget->setupContentWidget(m_videoWidget);
// Media player
m_mediaPlayer = new QMediaPlayer(this);
@@ -189,13 +167,10 @@ void MediaPreviewDialog::setupVideoView()
&MediaPreviewDialog::onMediaPlayerStateChanged);
connect(m_mediaPlayer, &QMediaPlayer::errorOccurred, this,
[this](QMediaPlayer::Error error, const QString &errorString) {
- qDebug() << "MediaPlayer Error:" << error << errorString;
- m_statusLabel->setText("Error: " + errorString);
- m_loadingLabel->setText("Error: " + errorString);
- m_loadingLabel->show();
- m_videoWidget->hide();
+ m_loadingWidget->showError("Error playing video: " +
+ errorString);
});
- // Setup progress timer for smooth updates
+
m_progressTimer = new QTimer(this);
connect(m_progressTimer, &QTimer::timeout, this,
&MediaPreviewDialog::updateVideoProgress);
@@ -211,22 +186,17 @@ void MediaPreviewDialog::loadMedia()
void MediaPreviewDialog::loadImage()
{
- auto future = QtConcurrent::run(
- [this]() { return ImageLoader::loadImage(m_device, m_filePath); });
-
- auto *watcher = new QFutureWatcher(this);
- connect(watcher, &QFutureWatcher::finished, this,
- [this, watcher]() {
- QPixmap pixmap = watcher->result();
- if (!pixmap.isNull()) {
- m_originalPixmap = pixmap;
- onImageLoaded();
- } else {
- onImageLoadFailed();
- }
- watcher->deleteLater();
- });
- watcher->setFuture(future);
+ auto callback = [this](const QPixmap &pixmap) {
+ if (!pixmap.isNull()) {
+ onImageLoaded(pixmap);
+ } else {
+ onImageLoadFailed();
+ }
+ };
+ // 99999 is so that it gets the highest priority in the queue
+ unsigned int priority = 99999;
+ ImageLoader::sharedInstance().requestImageWithCallback(m_device, m_filePath,
+ priority, callback);
}
void MediaPreviewDialog::loadVideo()
@@ -238,20 +208,22 @@ void MediaPreviewDialog::loadVideo()
m_device, m_afcClient, m_filePath);
qDebug() << "Streaming video from URL:" << streamUrl;
if (streamUrl.isEmpty()) {
- m_statusLabel->setText("Failed to start video stream");
+ // TODO: connect to retry signal to attempt restarting the stream
+ m_loadingWidget->showError("Failed to start video stream");
return;
}
m_mediaPlayer->setSource(streamUrl);
m_mediaPlayer->play();
- m_loadingLabel->hide();
- m_statusLabel->setText(
- QString("Playing: %1").arg(QFileInfo(m_filePath).fileName()));
+ m_loadingWidget->stop();
+ // m_statusLabel->setText(
+ // QString("Playing: %1").arg(QFileInfo(m_filePath).fileName()));
}
-void MediaPreviewDialog::onImageLoaded()
+void MediaPreviewDialog::onImageLoaded(const QPixmap &pixmap)
{
- m_loadingLabel->hide();
+ m_originalPixmap = pixmap;
+
m_imageView->setVisible(true);
// Add pixmap to scene
@@ -259,19 +231,22 @@ void MediaPreviewDialog::onImageLoaded()
m_imageScene->setSceneRect(m_originalPixmap.rect());
// Fit to window initially
- fitToWindow();
-
+ // TODO:why QTimer::singleShot is required here ?
+ // fitToWindow();
+ QTimer::singleShot(0, this, &MediaPreviewDialog::fitToWindow);
+ m_loadingWidget->stop();
// Update status
- m_statusLabel->setText(QString("Image: %1 (%2x%3)")
- .arg(QFileInfo(m_filePath).fileName())
- .arg(m_originalPixmap.width())
- .arg(m_originalPixmap.height()));
+ // m_statusLabel->setText(QString("Image: %1 (%2x%3)")
+ // .arg(QFileInfo(m_filePath).fileName())
+ // .arg(m_originalPixmap.width())
+ // .arg(m_originalPixmap.height()));
}
void MediaPreviewDialog::onImageLoadFailed()
{
- m_loadingLabel->setText("Failed to load image");
- m_statusLabel->setText("Error loading image");
+ // TODO: connect to retry signal to attempt reloading the image
+ m_loadingWidget->showError("Failed to load image");
+ // m_statusLabel->setText("Error loading image");
}
void MediaPreviewDialog::wheelEvent(QWheelEvent *event)
@@ -425,35 +400,17 @@ void MediaPreviewDialog::zoom(double factor)
updateZoomStatus();
}
-// void MediaPreviewDialog::updateZoomStatus()
-// {
-// if (!m_isVideo && !m_originalPixmap.isNull()) {
-// m_statusLabel->setText(QString("Image: %1 (%2x%3) - Zoom: %4%")
-// .arg(QFileInfo(m_filePath).fileName())
-// .arg(m_originalPixmap.width())
-// .arg(m_originalPixmap.height())
-// .arg(qRound(m_zoomFactor * 100)));
-// }
-// }
-
void MediaPreviewDialog::updateZoomStatus()
{
if (!m_isVideo && !m_originalPixmap.isNull()) {
- m_statusLabel->setText(QString("Image: %1 (%2x%3) - Zoom: %4%")
- .arg(QFileInfo(m_filePath).fileName())
- .arg(m_originalPixmap.width())
- .arg(m_originalPixmap.height())
- .arg(qRound(m_zoomFactor * 100)));
+ // m_statusLabel->setText(QString("Image: %1 (%2x%3) - Zoom: %4%")
+ // .arg(QFileInfo(m_filePath).fileName())
+ // .arg(m_originalPixmap.width())
+ // .arg(m_originalPixmap.height())
+ // .arg(qRound(m_zoomFactor * 100)));
}
}
-bool MediaPreviewDialog::isVideoFile(const QString &filePath) const
-{
- const QString lower = filePath.toLower();
- return lower.endsWith(".mov") || lower.endsWith(".mp4") ||
- lower.endsWith(".avi") || lower.endsWith(".m4v");
-}
-
void MediaPreviewDialog::setupVideoControls()
{
// Create video controls layout
@@ -525,7 +482,7 @@ void MediaPreviewDialog::setupVideoControls()
m_videoControlsLayout->addWidget(m_playPauseBtn);
m_videoControlsLayout->addWidget(m_stopBtn);
m_videoControlsLayout->addWidget(m_repeatBtn);
- m_videoControlsLayout->addWidget(m_timelineSlider, 1); // Stretch factor 1
+ m_videoControlsLayout->addWidget(m_timelineSlider, 1);
m_videoControlsLayout->addWidget(m_timeLabel);
m_videoControlsLayout->addWidget(m_volumeLabel);
m_videoControlsLayout->addWidget(m_volumeSlider);
@@ -563,8 +520,6 @@ void MediaPreviewDialog::onRepeatToggled(bool enabled)
m_repeatBtn->setStyleSheet(
enabled ? "QPushButton { background-color: #4CAF50; color: white; }"
: "");
-
- qDebug() << "Repeat mode:" << (enabled ? "ON" : "OFF");
}
void MediaPreviewDialog::onTimelinePressed()
@@ -665,9 +620,9 @@ void MediaPreviewDialog::onMediaPlayerDurationChanged(qint64 duration)
if (duration > 0) {
QString durationStr;
formatTime(duration, durationStr);
- m_statusLabel->setText(QString("Video: %1 - Duration: %2")
- .arg(QFileInfo(m_filePath).fileName())
- .arg(durationStr));
+ // m_statusLabel->setText(QString("Video: %1 - Duration: %2")
+ // .arg(QFileInfo(m_filePath).fileName())
+ // .arg(durationStr));
}
}
@@ -742,10 +697,11 @@ void MediaPreviewDialog::onVolumeChanged(int value)
}
}
+#ifdef __APPLE__
bool MediaPreviewDialog::event(QEvent *event)
{
- // FIXME: lets implement this on all dialogs
- // catch platform Close (Cmd+W on macOS)
+ // TODO: implement this on all dialogs
+ // catch platform close (Cmd+W on macOS)
if (event->type() == QEvent::ShortcutOverride) {
if (auto *ke = dynamic_cast(event)) {
const Qt::KeyboardModifiers mods = ke->modifiers();
@@ -755,12 +711,8 @@ bool MediaPreviewDialog::event(QEvent *event)
close();
return true;
}
- if (ke->key() == Qt::Key_Escape) {
- ke->accept();
- close();
- return true;
- }
}
}
return QDialog::event(event);
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mediapreviewdialog.h b/src/mediapreviewdialog.h
index b80771b..792b88c 100644
--- a/src/mediapreviewdialog.h
+++ b/src/mediapreviewdialog.h
@@ -22,6 +22,7 @@
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
+#include "zloadingwidget.h"
#include
#include
#include
@@ -37,21 +38,12 @@
#include
#include
-/**
- * @brief A dialog for previewing images and videos from iOS devices
- *
- * Features:
- * - Image viewing with zoom and pan using QGraphicsView
- * - Video streaming with timeline scrubbing support
- * - Asynchronous loading from device
- * - Proper memory management
- */
class MediaPreviewDialog : public QDialog
{
Q_OBJECT
public:
- explicit MediaPreviewDialog(iDescriptorDevice *device,
+ explicit MediaPreviewDialog(const iDescriptorDevice *device,
AfcClientHandle *afcClient,
const QString &filePath,
QWidget *parent = nullptr);
@@ -61,10 +53,11 @@ protected:
void wheelEvent(QWheelEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
- bool event(QEvent *event) override; // handle ShortcutOverride
-
+#ifdef __APPLE__
+ bool event(QEvent *event) override;
+#endif
private slots:
- void onImageLoaded();
+ void onImageLoaded(const QPixmap &pixmap);
void onImageLoadFailed();
void zoomIn();
void zoomOut();
@@ -96,10 +89,9 @@ private:
void updateZoomStatus();
void updateVideoTimeDisplay();
void formatTime(qint64 milliseconds, QString &timeString);
- bool isVideoFile(const QString &filePath) const;
// Core data
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
QString m_filePath;
bool m_isVideo;
@@ -127,10 +119,6 @@ private:
QLabel *m_volumeLabel;
QTimer *m_progressTimer;
- // Common components
- QLabel *m_loadingLabel;
- QLabel *m_statusLabel;
-
// Control buttons
QPushButton *m_zoomInBtn;
QPushButton *m_zoomOutBtn;
@@ -138,15 +126,16 @@ private:
QPushButton *m_fitToWindowBtn;
// State
- double m_zoomFactor;
+ double m_zoomFactor = 1.0;
QPixmap m_originalPixmap;
// Video state
- bool m_isRepeatEnabled;
- bool m_isDraggingTimeline;
- qint64 m_videoDuration;
+ bool m_isRepeatEnabled = true;
+ bool m_isDraggingTimeline = false;
+ qint64 m_videoDuration = 0;
AfcClientHandle *m_afcClient;
+ ZLoadingWidget *m_loadingWidget;
};
#endif // MEDIAPREVIEWDIALOG_H
diff --git a/src/mediastreamer.cpp b/src/mediastreamer.cpp
index 5e07248..22b1526 100644
--- a/src/mediastreamer.cpp
+++ b/src/mediastreamer.cpp
@@ -30,7 +30,7 @@
#include
#include
-MediaStreamer::MediaStreamer(iDescriptorDevice *device,
+MediaStreamer::MediaStreamer(const iDescriptorDevice *device,
AfcClientHandle *afcClient,
const QString &filePath, QObject *parent)
: QTcpServer(parent), m_device(device), m_afcClient(afcClient),
@@ -63,10 +63,10 @@ QUrl MediaStreamer::getUrl() const
if (!isListening()) {
return QUrl();
}
- // todo pass folder/filename
+
return QUrl(QString("http://127.0.0.1:%1/%2")
.arg(serverPort())
- .arg(QFileInfo(m_filePath).fileName()));
+ .arg(QUrl::toPercentEncoding(m_filePath)));
}
bool MediaStreamer::isListening() const { return QTcpServer::isListening(); }
@@ -235,7 +235,6 @@ void MediaStreamer::handleRequest(QTcpSocket *socket,
response += "\r\n";
socket->write(response);
- // Remove blocking call - let Qt handle when bytes are actually written
// For HEAD requests, don't send body
if (request.method == "HEAD") {
@@ -259,14 +258,12 @@ void MediaStreamer::sendErrorResponse(QTcpSocket *socket, int statusCode,
.toUtf8();
socket->write(response);
- // Remove blocking call
socket->disconnectFromHost();
}
void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
qint64 endByte)
{
- // Create a new streaming context for this request
StreamingContext *context = new StreamingContext();
context->socket = socket;
context->device = m_device;
@@ -276,12 +273,10 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
context->bytesRemaining = endByte - startByte + 1;
context->afcHandle = nullptr;
- qDebug() << "m_filepath" << m_filePath;
const QByteArray pathBytes = m_filePath.toUtf8();
- IdeviceFfiError *err_open = // Use distinct variable name
- ServiceManager::safeAfcFileOpen(m_device, pathBytes.constData(),
- AfcRdOnly, &context->afcHandle);
+ IdeviceFfiError *err_open = ServiceManager::safeAfcFileOpen(
+ m_device, pathBytes.constData(), AfcRdOnly, &context->afcHandle);
if (err_open || context->afcHandle == 0) {
qWarning() << "Failed to open file on device:" << m_filePath;
@@ -290,15 +285,16 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
return;
}
- // Seek to start position if needed
+ /* TODO: can it be optimized by doing
+ SEEK_END 2 /* Seek from end of file. ???
+ */
if (startByte > 0) {
- // FIXME: m_afcClient must be passed as alt afc
IdeviceFfiError *seek_err = ServiceManager::safeAfcFileSeek(
m_device, context->afcHandle, startByte, SEEK_SET);
if (seek_err) {
qWarning() << "Failed to seek in file:" << m_filePath;
- // FIXME: m_afcClient must be passed as alt afc
+
IdeviceFfiError *err =
ServiceManager::safeAfcFileClose(m_device, context->afcHandle);
if (err) {
@@ -310,9 +306,6 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
}
}
- qDebug() << "Starting non-blocking stream for range" << startByte << "-"
- << endByte << "(" << context->bytesRemaining << "bytes)";
-
// Store context as socket property for cleanup
socket->setProperty("streamingContext",
QVariant::fromValue(static_cast(context)));
@@ -321,15 +314,16 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
connect(socket, &QTcpSocket::bytesWritten, this,
[this, context](qint64 bytes) {
Q_UNUSED(bytes)
+
// Check if context is still valid
QTcpSocket *senderSocket = qobject_cast(sender());
if (!senderSocket ||
senderSocket->property("streamingContext").isNull()) {
- return; // Context already cleaned up
+ return;
}
+
// Continue streaming when socket buffer has space
- if (context->socket->bytesToWrite() <
- 32768) { // Keep buffer below 32KB
+ if (context->socket->bytesToWrite() < 50000) {
streamNextChunk(context);
}
});
@@ -339,7 +333,7 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte,
QTcpSocket *senderSocket = qobject_cast(sender());
if (!senderSocket ||
senderSocket->property("streamingContext").isNull()) {
- return; // Already cleaned up
+ return;
}
cleanupStreamingContext(context);
});
@@ -421,7 +415,7 @@ void MediaStreamer::streamNextChunk(StreamingContext *context)
return;
}
- const int CHUNK_SIZE = 256 * 1024;
+ const int CHUNK_SIZE = 64 * 1024;
const uint32_t bytesToRead = static_cast(
qMin(static_cast(CHUNK_SIZE), context->bytesRemaining));
@@ -441,9 +435,10 @@ void MediaStreamer::streamNextChunk(StreamingContext *context)
if (bytesRead == 0 && bytesToRead > 0) {
qWarning() << "AFC read returned 0 bytes but expected to read:"
<< bytesToRead;
- if (chunkData) {
- afc_file_read_data_free(chunkData, 0);
- }
+ // FIXME: in such situation, freeing shouldn't be safe ?
+ // if (chunkData) {
+ // afc_file_read_data_free(chunkData, 0);
+ // }
cleanupStreamingContext(context);
return;
}
@@ -475,7 +470,7 @@ void MediaStreamer::streamNextChunk(StreamingContext *context)
return;
}
- if (context->socket->bytesToWrite() >= 1024 * 1024) {
+ if (context->socket->bytesToWrite() >= 50000) {
// Wait for bytesWritten signal
return;
} else {
@@ -505,9 +500,7 @@ void MediaStreamer::cleanupStreamingContext(StreamingContext *context)
}
if (context->afcHandle != 0) {
- // FIXME: m_afcClient must be passed as alt afc
- ServiceManager::safeAfcFileClose(context->device, context->afcHandle
- /*m_afcClient*/);
+ ServiceManager::safeAfcFileClose(context->device, context->afcHandle);
context->afcHandle = 0;
}
@@ -517,7 +510,7 @@ void MediaStreamer::cleanupStreamingContext(StreamingContext *context)
disconnect(context->socket, &QTcpSocket::disconnected, this, nullptr);
context->socket->disconnectFromHost();
- context->socket = nullptr; // Prevent further access
+ context->socket = nullptr;
}
qDebug() << "Streaming context cleaned up for"
diff --git a/src/mediastreamer.h b/src/mediastreamer.h
index 80f6169..50cf259 100644
--- a/src/mediastreamer.h
+++ b/src/mediastreamer.h
@@ -30,38 +30,17 @@ QT_BEGIN_NAMESPACE
class QTcpSocket;
QT_END_NAMESPACE
-/**
- * @brief A lightweight HTTP server for streaming media files from iOS devices
- *
- * This class implements an HTTP server that supports:
- * - Basic HTTP GET requests
- * - HTTP Range requests for video scrubbing
- * - Streaming from AFC (Apple File Conduit) without loading entire file into
- * memory
- * - Thread-safe operation
- *
- * The server automatically shuts down when the client disconnects.
- */
class MediaStreamer : public QTcpServer
{
Q_OBJECT
public:
- explicit MediaStreamer(iDescriptorDevice *device,
+ explicit MediaStreamer(const iDescriptorDevice *device,
AfcClientHandle *afcClient, const QString &filePath,
QObject *parent = nullptr);
~MediaStreamer();
- /**
- * @brief Get the URL that clients should use to connect to this server
- * @return URL in format http://127.0.0.1:port
- */
QUrl getUrl() const;
-
- /**
- * @brief Check if the server started successfully
- * @return true if server is listening, false otherwise
- */
bool isListening() const;
protected:
@@ -83,7 +62,7 @@ private:
struct StreamingContext {
QTcpSocket *socket;
- iDescriptorDevice *device;
+ const iDescriptorDevice *device;
QString filePath;
qint64 startByte;
qint64 endByte;
@@ -102,7 +81,7 @@ private:
QString getMimeType() const;
// Core data
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
QString m_filePath;
// File info cache
diff --git a/src/mediastreamermanager.cpp b/src/mediastreamermanager.cpp
index b2e96c2..7200a38 100644
--- a/src/mediastreamermanager.cpp
+++ b/src/mediastreamermanager.cpp
@@ -30,7 +30,7 @@ MediaStreamerManager *MediaStreamerManager::sharedInstance()
return &instance;
}
-QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device,
+QUrl MediaStreamerManager::getStreamUrl(const iDescriptorDevice *device,
AfcClientHandle *afcClient,
const QString &filePath)
{
@@ -65,7 +65,7 @@ QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device,
return QUrl();
}
- // Store the streamer info
+ // FIXME: device pointer can become dangling if device is disconnected
StreamerInfo info;
info.streamer = streamer;
info.device = device;
diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h
index 6170bc7..191e4f4 100644
--- a/src/mediastreamermanager.h
+++ b/src/mediastreamermanager.h
@@ -27,40 +27,16 @@
#include
#include
-/**
- * @brief Singleton manager for MediaStreamer instances
- *
- * This class manages MediaStreamer instances to avoid creating multiple
- * streamers for the same file. It automatically cleans up unused streamers
- * and provides thread-safe access.
- */
class MediaStreamerManager
{
public:
- /**
- * @brief Get the singleton instance
- * @return The MediaStreamerManager instance
- */
static MediaStreamerManager *sharedInstance();
- /**
- * @brief Get or create a streamer for the specified file
- * @param device The iOS device
- * @param filePath The file path on the device
- * @return URL to stream the file, or empty URL if failed
- */
- QUrl getStreamUrl(iDescriptorDevice *device, AfcClientHandle *afcClient,
- const QString &filePath);
+ QUrl getStreamUrl(const iDescriptorDevice *device,
+ AfcClientHandle *afcClient, const QString &filePath);
- /**
- * @brief Release a streamer for the specified file
- * @param filePath The file path to release
- */
void releaseStreamer(const QString &filePath);
- /**
- * @brief Clean up all inactive streamers
- */
void cleanup();
private:
@@ -69,7 +45,7 @@ private:
private:
struct StreamerInfo {
MediaStreamer *streamer;
- iDescriptorDevice *device;
+ const iDescriptorDevice *device;
int refCount;
};
diff --git a/src/networkdevicestoconnectwidget.cpp b/src/networkdevicestoconnectwidget.cpp
index 6c42e07..b1ef6b1 100644
--- a/src/networkdevicestoconnectwidget.cpp
+++ b/src/networkdevicestoconnectwidget.cpp
@@ -130,6 +130,17 @@ void NetworkDeviceCard::noPairingFile()
});
}
+void NetworkDeviceCard::connected()
+{
+ m_connectButton->setText("Connected");
+ m_connectButton->setEnabled(false);
+
+ QTimer::singleShot(10000, this, [this]() {
+ m_connectButton->setText("Connect");
+ m_connectButton->setEnabled(true);
+ });
+}
+
void NetworkDeviceCard::initStarted()
{
m_connectButton->setText("Connecting...");
@@ -168,6 +179,10 @@ NetworkDevicesToConnectWidget::NetworkDevicesToConnectWidget(QWidget *parent)
&NetworkDevicesToConnectWidget::onDeviceInitFailed);
connect(AppContext::sharedInstance(), &AppContext::initStarted, this,
&NetworkDevicesToConnectWidget::onDeviceInitStarted);
+ connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
+ &NetworkDevicesToConnectWidget::onDeviceAdded);
+ connect(AppContext::sharedInstance(), &AppContext::deviceAlreadyExists,
+ this, &NetworkDevicesToConnectWidget::onDeviceAlreadyExists);
}
NetworkDevicesToConnectWidget::~NetworkDevicesToConnectWidget()
@@ -313,20 +328,47 @@ void NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice(
}
// udid or mac address
-void NetworkDevicesToConnectWidget::onDeviceInitFailed(const QString &udid)
+void NetworkDevicesToConnectWidget::onDeviceInitFailed(const QString &uniq)
{
- NetworkDeviceCard *deviceCard = m_deviceCards[udid];
+ NetworkDeviceCard *deviceCard = m_deviceCards[uniq];
if (deviceCard) {
- qDebug() << "Calling failed() on device card for" << udid;
+ qDebug() << "Calling failed() on device card for" << uniq;
deviceCard->failed();
}
}
-void NetworkDevicesToConnectWidget::onDeviceInitStarted(const QString &udid)
+void NetworkDevicesToConnectWidget::onDeviceInitStarted(const QString &uniq)
{
- NetworkDeviceCard *deviceCard = m_deviceCards[udid];
+ NetworkDeviceCard *deviceCard = m_deviceCards[uniq];
if (deviceCard) {
- qDebug() << "Calling initStarted() on device card for" << udid;
+ qDebug() << "Calling initStarted() on device card for" << uniq;
deviceCard->initStarted();
}
+}
+
+void NetworkDevicesToConnectWidget::onDeviceAdded(
+ const iDescriptorDevice *device)
+{
+ NetworkDeviceCard *deviceCard = m_deviceCards[QString::fromStdString(
+ device->deviceInfo.wifiMacAddress)];
+ if (deviceCard) {
+ qDebug() << "Calling connected() on device card for"
+ << QString::fromStdString(device->deviceInfo.wifiMacAddress);
+ deviceCard->connected();
+ return;
+ }
+ qDebug() << "No device card found for"
+ << QString::fromStdString(device->deviceInfo.wifiMacAddress);
+}
+
+void NetworkDevicesToConnectWidget::onDeviceAlreadyExists(
+ const iDescriptor::Uniq &uniq)
+{
+ NetworkDeviceCard *deviceCard = m_deviceCards[QString(uniq.get())];
+ if (deviceCard) {
+ qDebug() << "Calling connected() on device card for" << uniq.get();
+ deviceCard->connected();
+ return;
+ }
+ qDebug() << "No device card found for" << uniq.get();
}
\ No newline at end of file
diff --git a/src/networkdevicestoconnectwidget.h b/src/networkdevicestoconnectwidget.h
index 90959c9..7faec08 100644
--- a/src/networkdevicestoconnectwidget.h
+++ b/src/networkdevicestoconnectwidget.h
@@ -49,6 +49,7 @@ public:
void failed();
void noPairingFile();
void initStarted();
+ void connected();
};
class NetworkDevicesToConnectWidget : public QWidget
@@ -65,6 +66,8 @@ private slots:
void onNoPairingFileForWirelessDevice(const QString &macAddress);
void onDeviceInitFailed(const QString &udid);
void onDeviceInitStarted(const QString &udid);
+ void onDeviceAdded(const iDescriptorDevice *device);
+ void onDeviceAlreadyExists(const iDescriptor::Uniq &uniq);
private:
void setupUI();
diff --git a/src/opensshterminalwidget.cpp b/src/opensshterminalwidget.cpp
index 39dbeb6..10654fe 100644
--- a/src/opensshterminalwidget.cpp
+++ b/src/opensshterminalwidget.cpp
@@ -184,7 +184,7 @@ void OpenSSHTerminalWidget::clearDeviceButtons()
}
}
-void OpenSSHTerminalWidget::addWiredDevice(iDescriptorDevice *device)
+void OpenSSHTerminalWidget::addWiredDevice(const iDescriptorDevice *device)
{
QString deviceName = QString::fromStdString(device->deviceInfo.deviceName);
QString udid = QString::fromStdString(device->udid);
@@ -192,8 +192,9 @@ void OpenSSHTerminalWidget::addWiredDevice(iDescriptorDevice *device)
QRadioButton *radioButton = new QRadioButton(displayText);
radioButton->setProperty("deviceType", "wired");
- radioButton->setProperty("devicePointer",
- QVariant::fromValue(static_cast(device)));
+ radioButton->setProperty(
+ "devicePointer",
+ QVariant::fromValue(static_cast(device)));
radioButton->setProperty("udid", udid);
m_deviceButtonGroup->addButton(radioButton);
@@ -214,7 +215,7 @@ void OpenSSHTerminalWidget::addWirelessDevice(const NetworkDevice &device)
m_wirelessDevicesLayout->addWidget(radioButton);
}
-void OpenSSHTerminalWidget::onWiredDeviceAdded(iDescriptorDevice *device)
+void OpenSSHTerminalWidget::onWiredDeviceAdded(const iDescriptorDevice *device)
{
addWiredDevice(device);
}
diff --git a/src/opensshterminalwidget.h b/src/opensshterminalwidget.h
index 7a04b21..02565d1 100644
--- a/src/opensshterminalwidget.h
+++ b/src/opensshterminalwidget.h
@@ -47,7 +47,7 @@ public:
~OpenSSHTerminalWidget();
private slots:
void onOpenSSHTerminal();
- void onWiredDeviceAdded(iDescriptorDevice *device);
+ void onWiredDeviceAdded(const iDescriptorDevice *device);
void onWiredDeviceRemoved(const std::string &udid);
void onWirelessDeviceAdded(const NetworkDevice &device);
void onWirelessDeviceRemoved(const QString &deviceName);
@@ -57,7 +57,7 @@ private:
void setupDeviceSelectionUI(QVBoxLayout *layout);
void updateDeviceList();
void clearDeviceButtons();
- void addWiredDevice(iDescriptorDevice *device);
+ void addWiredDevice(const iDescriptorDevice *device);
void addWirelessDevice(const NetworkDevice &device);
void resetSelection();
@@ -82,8 +82,7 @@ private:
iDescriptorDevice *m_selectedWiredDevice = nullptr;
NetworkDevice m_selectedNetworkDevice;
- // Legacy device pointer (kept for compatibility)
- iDescriptorDevice *m_device = nullptr;
+ const iDescriptorDevice *m_device = nullptr;
// SSH components
ssh_session m_sshSession;
diff --git a/src/photoimportdialog.cpp b/src/photoimportdialog.cpp
index aaf2541..d2b71db 100644
--- a/src/photoimportdialog.cpp
+++ b/src/photoimportdialog.cpp
@@ -31,11 +31,8 @@
#include
#include
-PhotoImportDialog::PhotoImportDialog(const QStringList &files,
- bool hasDirectories, QWidget *parent)
- : QDialog(parent), selectedFiles(files),
- containsDirectories(hasDirectories), m_httpServer(nullptr),
- m_mediaPlayer(nullptr)
+PhotoImportDialog::PhotoImportDialog(const QStringList &files, QWidget *parent)
+ : QDialog(parent), selectedFiles(files)
{
setupUI();
setModal(true);
@@ -59,16 +56,6 @@ void PhotoImportDialog::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
- // Warning label for directories
- if (containsDirectories) {
- warningLabel =
- new QLabel("⚠️ Warning: Selected items contain directories. All "
- "gallery-compatible files will be included.",
- this);
- warningLabel->setWordWrap(true);
- mainLayout->addWidget(warningLabel);
- }
-
// File list
QLabel *listLabel = new QLabel(
QString("Files to be served (%1 items):").arg(selectedFiles.size()),
@@ -230,6 +217,7 @@ void PhotoImportDialog::onServerStarted()
void PhotoImportDialog::onDownloadProgress(const QString &fileName,
int bytesDownloaded, int totalBytes)
{
+ // TODO: bring in a progress bar each item
m_progressLabel->setText(QString("Downloaded: %1 (%2 KB)")
.arg(fileName)
.arg(bytesDownloaded / 1024));
diff --git a/src/photoimportdialog.h b/src/photoimportdialog.h
index a8ca23a..566d3fb 100644
--- a/src/photoimportdialog.h
+++ b/src/photoimportdialog.h
@@ -25,20 +25,20 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
-#include
#include
-#include
class PhotoImportDialog : public QDialog
{
Q_OBJECT
public:
- explicit PhotoImportDialog(const QStringList &files, bool hasDirectories,
+ explicit PhotoImportDialog(const QStringList &files,
QWidget *parent = nullptr);
~PhotoImportDialog();
@@ -52,10 +52,8 @@ private slots:
private:
QStringList selectedFiles;
- bool containsDirectories;
QListWidget *fileList;
- QLabel *warningLabel;
QLabel *qrCodeLabel;
QStackedWidget *m_instructionStack;
QLabel *m_instructionLabel;
diff --git a/src/photomodel.cpp b/src/photomodel.cpp
index 05eca32..024e05c 100644
--- a/src/photomodel.cpp
+++ b/src/photomodel.cpp
@@ -34,40 +34,27 @@
#include
#include
#include
-#include
-PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType,
+PhotoModel::PhotoModel(const iDescriptorDevice *device, FilterType filterType,
QObject *parent)
- : QAbstractListModel(parent), m_device(device), m_thumbnailSize(120, 120),
- m_sortOrder(NewestFirst), m_filterType(filterType)
+ : QAbstractListModel(parent), m_device(device), m_sortOrder(NewestFirst),
+ m_filterType(filterType)
{
- connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this,
- &PhotoModel::onThumbnailReady);
}
void PhotoModel::clear()
{
- blockSignals(true);
-
- // // Clean up any active watchers
- // for (auto *watcher : m_activeLoaders.values()) {
- // if (watcher) {
- // watcher->disconnect();
- // watcher->cancel();
- // // watcher->waitForFinished();
- // watcher->deleteLater();
- // }
- // }
- // m_activeLoaders.clear();
- // m_loadingPaths.clear();
- // m_thumbnailCache.clear();
+ QMutexLocker locker(&m_mutex);
+ disconnect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady,
+ this, &PhotoModel::onThumbnailReady);
beginResetModel();
m_photos.clear();
m_allPhotos.clear();
endResetModel();
- blockSignals(false);
+ qDebug() << "Cleared PhotoModel data";
+ ImageLoader::sharedInstance().clear();
}
PhotoModel::~PhotoModel()
@@ -112,8 +99,7 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const
}
}
- imgloader.requestThumbnail(m_device, info.filePath, index.row(),
- index.row());
+ imgloader.requestThumbnail(m_device, info.filePath, index.row());
if (iDescriptor::Utils::isVideoFile(info.fileName)) {
return QIcon(":/resources/icons/video-x-generic.png");
@@ -134,19 +120,33 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const
void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap,
unsigned int row)
{
- if (m_photos[row].filePath == path) {
- QModelIndex idx = createIndex(row, 0);
- emit dataChanged(idx, idx, {Qt::DecorationRole});
+ // check bounds
+ if (row < m_photos.size()) {
+ const PhotoInfo &photo = m_photos.at(row);
+ if (photo.filePath == path) {
+ QModelIndex idx = createIndex(row, 0);
+ emit dataChanged(idx, idx, {Qt::DecorationRole});
+ }
+ } else {
+ // FIXME: happens when we filter down to videos only
+ qDebug() << "Out of bounds in PhotoModel::onThumbnailReady";
}
}
-void PhotoModel::populatePhotoPaths()
+bool isTimeoutError(IdeviceFfiError *err)
{
- // TODO:beginResetModel called on PhotoModel(0x600002d12a40) without calling
- // endResetModel first
+ return err && err->code == TimeoutErrorCode;
+}
+
+bool PhotoModel::populatePhotoPaths()
+{
+ // FIXME:DEADLOCK?
+ // QMutexLocker locker(&m_mutex);
+ connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this,
+ &PhotoModel::onThumbnailReady);
if (m_albumPath.isEmpty()) {
qDebug() << "No album path set, skipping population";
- return;
+ return false;
}
m_allPhotos.clear();
@@ -160,9 +160,13 @@ void PhotoModel::populatePhotoPaths()
ServiceManager::safeAfcGetFileInfo(m_device, albumPathCStr, &albumInfo);
if (err) {
qDebug() << "Album path does not exist or cannot be accessed:"
- << m_albumPath << "Error:" << err->message;
+ << m_albumPath << "Error:" << err->message
+ << "Code:" << err->code;
+ if (isTimeoutError(err)) {
+ emit timedOut();
+ }
idevice_error_free(err);
- return;
+ return false;
}
// FIXME: should we continue if albumInfo is null?
if (albumInfo.size) {
@@ -182,27 +186,23 @@ void PhotoModel::populatePhotoPaths()
if (err) {
qDebug() << "Failed to read photo directory:" << photoDir
<< "Error:" << err->message;
+ if (isTimeoutError(err)) {
+ emit timedOut();
+ }
idevice_error_free(err);
- return;
+ return false;
}
if (files) {
for (int i = 0; files[i]; i++) {
QString fileName = QString::fromUtf8(files[i]);
- if (fileName.endsWith(".JPG", Qt::CaseInsensitive) ||
- fileName.endsWith(".PNG", Qt::CaseInsensitive) ||
- fileName.endsWith(".HEIC", Qt::CaseInsensitive) ||
- fileName.endsWith(".MOV", Qt::CaseInsensitive) ||
- fileName.endsWith(".MP4", Qt::CaseInsensitive) ||
- fileName.endsWith(".M4V", Qt::CaseInsensitive)) {
-
+ if (iDescriptor::Utils::isGalleryFile(fileName)) {
PhotoInfo info;
info.filePath = m_albumPath + "/" + fileName;
info.fileName = fileName;
info.thumbnailRequested = false;
info.fileType = determineFileType(fileName);
info.dateTime = extractDateTimeFromFile(info.filePath);
-
m_allPhotos.append(info);
}
}
@@ -214,6 +214,7 @@ void PhotoModel::populatePhotoPaths()
qDebug() << "Loaded" << m_allPhotos.size() << "media files from device";
qDebug() << "After filtering:" << m_photos.size() << "items shown";
+ return true;
}
// Sorting and filtering methods
@@ -235,6 +236,7 @@ void PhotoModel::setFilterType(FilterType filter)
void PhotoModel::applyFilterAndSort()
{
+ QMutexLocker locker(&m_mutex);
beginResetModel();
// Filter photos
@@ -330,15 +332,15 @@ QStringList PhotoModel::getFilteredFilePaths() const
// Helper methods
QDateTime PhotoModel::extractDateTimeFromFile(const QString &filePath) const
{
- AfcFileInfo *info = nullptr;
+ AfcFileInfo info = {};
IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo(
- m_device, filePath.toUtf8().constData(), info);
- if (!err && info) {
- uint64_t creation_seconds = info->creation;
+ m_device, filePath.toUtf8().constData(), &info);
+ if (!err && info.creation) {
+ uint64_t creation_seconds = info.creation;
QDateTime dateTime =
QDateTime::fromSecsSinceEpoch(creation_seconds, Qt::UTC);
- // afc_file_info_free(info);
+ afc_file_info_free(&info);
if (dateTime.isValid()) {
return dateTime;
}
@@ -362,7 +364,24 @@ void PhotoModel::setAlbumPath(const QString &albumPath)
clear();
m_albumPath = albumPath;
- populatePhotoPaths();
+ QFutureWatcher *futureWatcher = new QFutureWatcher(this);
+ QFuture future =
+ QtConcurrent::run([this]() { return populatePhotoPaths(); });
+ futureWatcher->setFuture(future);
+ connect(futureWatcher, &QFutureWatcher::finished, this,
+ [this, futureWatcher]() {
+ futureWatcher->deleteLater();
+ bool success = futureWatcher->result();
+ if (success) {
+ qDebug() << "Finished populating photo paths for album:"
+ << m_albumPath;
+ emit albumPathSet();
+ } else {
+ // qDebug() << "Failed to populate photo paths for album:"
+ // << m_albumPath;
+ // emit albumPathFailed();
+ }
+ });
}
-
+// TODO:REMOVE
void PhotoModel::refreshPhotos() { populatePhotoPaths(); }
diff --git a/src/photomodel.h b/src/photomodel.h
index c0aaaaf..a9adfb5 100644
--- a/src/photomodel.h
+++ b/src/photomodel.h
@@ -26,10 +26,12 @@
#include
#include
#include
+#include
+#include
#include
-#include
#include
#include
+#include
struct PhotoInfo {
QString filePath;
@@ -50,7 +52,7 @@ public:
enum FilterType { All, ImagesOnly, VideosOnly };
- explicit PhotoModel(iDescriptorDevice *device, FilterType filterType,
+ explicit PhotoModel(const iDescriptorDevice *device, FilterType filterType,
QObject *parent = nullptr);
~PhotoModel();
@@ -83,20 +85,19 @@ public:
private:
// Data members
- iDescriptorDevice *m_device;
+ const iDescriptorDevice *m_device;
QString m_albumPath;
QList m_allPhotos; // All photos from device
QList m_photos; // Currently filtered/sorted photos
- // Thumbnail management
- QSize m_thumbnailSize;
-
// Sorting and filtering
SortOrder m_sortOrder;
FilterType m_filterType;
+ QMutex m_mutex;
+
// Helper methods
- void populatePhotoPaths();
+ bool populatePhotoPaths();
void applyFilterAndSort();
void sortPhotos(QList &photos) const;
bool matchesFilter(const PhotoInfo &info) const;
@@ -107,6 +108,10 @@ private:
private slots:
void onThumbnailReady(const QString &path, const QPixmap &pixmap,
unsigned int row);
+
+signals:
+ void albumPathSet();
+ void timedOut();
};
#endif // PHOTOMODEL_H
\ No newline at end of file
diff --git a/src/recoverydeviceinfowidget.cpp b/src/recoverydeviceinfowidget.cpp
index 56243cb..b4ce007 100644
--- a/src/recoverydeviceinfowidget.cpp
+++ b/src/recoverydeviceinfowidget.cpp
@@ -28,143 +28,157 @@
#include
#include
-RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(const void *info,
- QWidget *parent)
+std::string parse_recovery_mode(irecv_mode productType)
+{
+ switch (productType) {
+ case irecv_mode::IRECV_K_RECOVERY_MODE_1:
+ case irecv_mode::IRECV_K_RECOVERY_MODE_2:
+ case irecv_mode::IRECV_K_RECOVERY_MODE_3:
+ case irecv_mode::IRECV_K_RECOVERY_MODE_4:
+ return "Recovery Mode";
+ case irecv_mode::IRECV_K_WTF_MODE:
+ return "WTF Mode";
+ case irecv_mode::IRECV_K_DFU_MODE:
+ case irecv_mode::IRECV_K_PORT_DFU_MODE:
+ return "DFU Mode";
+ default:
+ return "Unknown Mode";
+ }
+}
+
+RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(
+ const iDescriptorRecoveryDevice *info, QWidget *parent)
: QWidget{parent}
{
- // ecid = info->ecid; // Assuming ecid is unique for each device
+ ecid = info->ecid; // Assuming ecid is unique for each device
- // QVBoxLayout *mainLayout = new QVBoxLayout(this);
- // mainLayout->setContentsMargins(20, 20, 20, 20);
- // mainLayout->setSpacing(16);
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+ mainLayout->setContentsMargins(20, 20, 20, 20);
+ mainLayout->setSpacing(16);
- // // Device Information Group
- // QGroupBox *deviceInfoGroup = new QGroupBox("Device Information");
- // QVBoxLayout *infoLayout = new QVBoxLayout(deviceInfoGroup);
- // infoLayout->setSpacing(8);
- // infoLayout->setContentsMargins(16, 16, 16, 16);
+ // Device Information Group
+ QGroupBox *deviceInfoGroup = new QGroupBox("Device Information");
+ QVBoxLayout *infoLayout = new QVBoxLayout(deviceInfoGroup);
+ infoLayout->setSpacing(8);
+ infoLayout->setContentsMargins(16, 16, 16, 16);
- // // Device name with larger font
- // QLabel *deviceNameLabel =
- // new QLabel(QString::fromStdString(info->displayName));
- // QFont nameFont = deviceNameLabel->font();
- // nameFont.setPointSize(nameFont.pointSize() + 2);
- // nameFont.setWeight(QFont::DemiBold);
- // deviceNameLabel->setFont(nameFont);
- // infoLayout->addWidget(deviceNameLabel);
+ // Device name with larger font
+ QLabel *deviceNameLabel =
+ new QLabel(QString::fromStdString(info->displayName));
+ QFont nameFont = deviceNameLabel->font();
+ nameFont.setPointSize(nameFont.pointSize() + 2);
+ nameFont.setWeight(QFont::DemiBold);
+ deviceNameLabel->setFont(nameFont);
+ infoLayout->addWidget(deviceNameLabel);
- // // Add spacing
- // infoLayout->addSpacing(8);
+ // Add spacing
+ infoLayout->addSpacing(8);
- // // Mode info
- // QString modeText =
- // QString::fromStdString(parse_recovery_mode(info->mode)); QLabel
- // *modeLabel = new QLabel("Mode: " + modeText);
- // infoLayout->addWidget(modeLabel);
+ // Mode info
+ QString modeText = QString::fromStdString(parse_recovery_mode(info->mode));
+ QLabel *modeLabel = new QLabel("Mode: " + modeText);
+ infoLayout->addWidget(modeLabel);
- // // ECID info
- // QLabel *ecidLabel = new QLabel("ECID: " + QString::number(info->ecid));
- // infoLayout->addWidget(ecidLabel);
+ // ECID info
+ QLabel *ecidLabel = new QLabel("ECID: " + QString::number(info->ecid));
+ infoLayout->addWidget(ecidLabel);
- // // CPID info
- // QLabel *cpidLabel = new QLabel("CPID: " + QString::number(info->cpid));
- // infoLayout->addWidget(cpidLabel);
+ // CPID info
+ QLabel *cpidLabel = new QLabel("CPID: " + QString::number(info->cpid));
+ infoLayout->addWidget(cpidLabel);
- // mainLayout->addWidget(deviceInfoGroup);
+ mainLayout->addWidget(deviceInfoGroup);
- // // Actions Group
- // QGroupBox *actionsGroup = new QGroupBox("Actions");
- // QVBoxLayout *actionsLayout = new QVBoxLayout(actionsGroup);
- // actionsLayout->setSpacing(12);
- // actionsLayout->setContentsMargins(16, 16, 16, 16);
+ // Actions Group
+ QGroupBox *actionsGroup = new QGroupBox("Actions");
+ QVBoxLayout *actionsLayout = new QVBoxLayout(actionsGroup);
+ actionsLayout->setSpacing(12);
+ actionsLayout->setContentsMargins(16, 16, 16, 16);
- // // Info label
- // QLabel *infoLabel = new QLabel(
- // "Exit recovery mode and restart the device into normal mode.");
- // infoLabel->setWordWrap(true);
- // QFont infoFont = infoLabel->font();
- // infoFont.setPointSize(infoFont.pointSize() - 1);
- // infoLabel->setFont(infoFont);
- // QPalette infoPalette = infoLabel->palette();
- // infoPalette.setColor(QPalette::WindowText,
- // infoPalette.color(QPalette::WindowText).lighter(120));
- // infoLabel->setPalette(infoPalette);
- // actionsLayout->addWidget(infoLabel);
+ // Info label
+ QLabel *infoLabel = new QLabel(
+ "Exit recovery mode and restart the device into normal mode.");
+ infoLabel->setWordWrap(true);
+ QFont infoFont = infoLabel->font();
+ infoFont.setPointSize(infoFont.pointSize() - 1);
+ infoLabel->setFont(infoFont);
+ QPalette infoPalette = infoLabel->palette();
+ infoPalette.setColor(QPalette::WindowText,
+ infoPalette.color(QPalette::WindowText).lighter(120));
+ infoLabel->setPalette(infoPalette);
+ actionsLayout->addWidget(infoLabel);
- // // Button layout
- // QHBoxLayout *buttonLayout = new QHBoxLayout();
- // buttonLayout->addStretch();
+ // Button layout
+ QHBoxLayout *buttonLayout = new QHBoxLayout();
+ buttonLayout->addStretch();
- // QPushButton *exitRecoveryMode = new QPushButton("Exit Recovery Mode");
- // exitRecoveryMode->setMinimumWidth(160);
- // exitRecoveryMode->setMinimumHeight(32);
+ QPushButton *exitRecoveryMode = new QPushButton("Exit Recovery Mode");
+ exitRecoveryMode->setMinimumWidth(160);
+ exitRecoveryMode->setMinimumHeight(32);
- // connect(exitRecoveryMode, &QPushButton::clicked, this, [this, info]() {
- // irecv_client_t client = NULL;
- // irecv_error_t ierr = irecv_open_with_ecid_and_attempts(
- // &client, info->ecid, RECOVERY_CLIENT_CONNECTION_TRIES);
- // irecv_error_t error = IRECV_E_SUCCESS;
- // if (ierr != IRECV_E_SUCCESS) {
- // QMessageBox::critical(
- // this, "Connection Error",
- // QString("Failed to open device with ECID %1:\n%2")
- // .arg(info->ecid)
- // .arg(irecv_strerror(ierr)));
- // return;
- // }
+ connect(exitRecoveryMode, &QPushButton::clicked, this, [this, info]() {
+ irecv_client_t client = NULL;
+ irecv_error_t ierr = irecv_open_with_ecid_and_attempts(
+ &client, info->ecid, RECOVERY_CLIENT_CONNECTION_TRIES);
+ irecv_error_t error = IRECV_E_SUCCESS;
+ if (ierr != IRECV_E_SUCCESS) {
+ QMessageBox::critical(
+ this, "Connection Error",
+ QString("Failed to open device with ECID %1:\n%2")
+ .arg(info->ecid)
+ .arg(irecv_strerror(ierr)));
+ return;
+ }
- // if (client == NULL) {
- // QMessageBox::critical(this, "Error",
- // "Client is NULL after successful open");
- // return;
- // }
+ if (client == NULL) {
+ QMessageBox::critical(this, "Error",
+ "Client is NULL after successful open");
+ return;
+ }
- // error = irecv_setenv(client, "auto-boot", "true");
- // if (error != IRECV_E_SUCCESS) {
- // QMessageBox::critical(
- // this, "Error",
- // QString("Failed to set environment variable
- // 'auto-boot':\n%1")
- // .arg(irecv_strerror(error)));
- // irecv_close(client);
- // return;
- // }
+ error = irecv_setenv(client, "auto-boot", "true");
+ if (error != IRECV_E_SUCCESS) {
+ QMessageBox::critical(
+ this, "Error",
+ QString("Failed to set environment variable 'auto-boot':\n%1")
+ .arg(irecv_strerror(error)));
+ irecv_close(client);
+ return;
+ }
- // error = irecv_saveenv(client);
- // if (error != IRECV_E_SUCCESS) {
- // QMessageBox::critical(
- // this, "Error",
- // QString("Failed to save environment variables:\n%1")
- // .arg(irecv_strerror(error)));
- // irecv_close(client);
- // return;
- // }
+ error = irecv_saveenv(client);
+ if (error != IRECV_E_SUCCESS) {
+ QMessageBox::critical(
+ this, "Error",
+ QString("Failed to save environment variables:\n%1")
+ .arg(irecv_strerror(error)));
+ irecv_close(client);
+ return;
+ }
- // error = irecv_reboot(client);
- // if (error != IRECV_E_SUCCESS) {
- // QMessageBox::critical(this, "Error",
- // QString("Failed to send reboot
- // command:\n%1")
- // .arg(irecv_strerror(error)));
- // irecv_close(client);
- // return;
- // }
+ error = irecv_reboot(client);
+ if (error != IRECV_E_SUCCESS) {
+ QMessageBox::critical(this, "Error",
+ QString("Failed to send reboot command:\n%1")
+ .arg(irecv_strerror(error)));
+ irecv_close(client);
+ return;
+ }
- // irecv_close(client);
+ irecv_close(client);
- // auto *msgBox =
- // new QMessageBox(QMessageBox::Information, "Success",
- // "Device is exiting recovery mode and
- // restarting...", QMessageBox::Ok,
- // QApplication::activeWindow());
- // msgBox->setAttribute(Qt::WA_DeleteOnClose);
- // msgBox->open();
- // });
+ auto *msgBox = new QMessageBox(
+ QMessageBox::Information, "Success",
+ "Device is exiting recovery mode and restarting... ",
+ QMessageBox::Ok, QApplication::activeWindow());
+ msgBox->setAttribute(Qt::WA_DeleteOnClose);
+ msgBox->open();
+ });
- // buttonLayout->addWidget(exitRecoveryMode);
- // buttonLayout->addStretch();
- // actionsLayout->addLayout(buttonLayout);
+ buttonLayout->addWidget(exitRecoveryMode);
+ buttonLayout->addStretch();
+ actionsLayout->addLayout(buttonLayout);
- // mainLayout->addWidget(actionsGroup);
- // mainLayout->addStretch();
+ mainLayout->addWidget(actionsGroup);
+ mainLayout->addStretch();
}
diff --git a/src/recoverydeviceinfowidget.h b/src/recoverydeviceinfowidget.h
index 1d96e3d..67920ce 100644
--- a/src/recoverydeviceinfowidget.h
+++ b/src/recoverydeviceinfowidget.h
@@ -27,7 +27,7 @@ class RecoveryDeviceInfoWidget : public QWidget
Q_OBJECT
public:
- explicit RecoveryDeviceInfoWidget(const void *info,
+ explicit RecoveryDeviceInfoWidget(const iDescriptorRecoveryDevice *info,
QWidget *parent = nullptr);
uint64_t ecid; // Assuming ecid is unique for each device
signals:
diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp
index 9f48934..9f79b56 100644
--- a/src/servicemanager.cpp
+++ b/src/servicemanager.cpp
@@ -223,96 +223,102 @@ IdeviceFfiError *ServiceManager::exportFileToPath(
std::atomic *cancelRequested)
{
qDebug()
- << "[serviceManager::exportFileToPath] Exporting file from device path:"
+ << "[ServiceManager::exportFileToPath] Exporting file from device path:"
<< device_path << "to local path:" << local_path;
+
+ // FIXME : use execute afc op
return executeOperation(
device,
[device, device_path, local_path, progressCallback,
cancelRequested]() -> IdeviceFfiError * {
AfcFileHandle *afcHandle = nullptr;
- qDebug() << "Opening file on device:" << device_path;
- IdeviceFfiError *err_open = safeAfcFileOpen(
- device, device_path, AfcFopenMode::AfcRdOnly, &afcHandle);
+ IdeviceFfiError *err =
+ afc_file_open(device->afcClient, device_path,
+ AfcFopenMode::AfcRdOnly, &afcHandle);
- if (err_open != nullptr) {
+ if (err != nullptr) {
qDebug() << "Failed to open file on device:" << device_path
- << "Error Code:" << err_open->code
- << "Message:" << err_open->message;
- return err_open;
+ << "Error Code:" << err->code
+ << "Message:" << err->message;
+ return err;
}
qDebug() << "File opened on device successfully";
FILE *out = fopen(local_path, "wb");
if (!out) {
qDebug() << "Failed to open local file:" << local_path;
- IdeviceFfiError *err_close =
- safeAfcFileClose(device, afcHandle);
+ IdeviceFfiError *err_close = afc_file_close(afcHandle);
if (err_close != nullptr) {
- // idevice_error_free(err_close);
+ idevice_error_free(err_close);
}
- return new IdeviceFfiError{1, "FAILED_TO_OPEN_LOCAL_FILE"};
+ return new IdeviceFfiError{1, "Failed to open local file"};
}
- qDebug() << "Local file opened successfully";
- const size_t CHUNK_SIZE = 256 * 1024; // 256KB chunks
+ // 256KB chunks
+ const size_t CHUNK_SIZE = 256 * 1024;
uint8_t *chunkData = nullptr;
size_t bytesRead = 0;
qint64 totalBytesRead = 0;
// Get file size for progress
AfcFileInfo fileInfo;
- IdeviceFfiError *info_err =
- safeAfcGetFileInfo(device, device_path, &fileInfo);
+ err = afc_get_file_info(device->afcClient, device_path, &fileInfo);
qint64 totalFileSize = 0;
- if (info_err == nullptr) {
- totalFileSize = fileInfo.size;
- // afc_file_info_free(&fileInfo);
- } else {
- // idevice_error_free(info_err);
- }
+ if (err != nullptr)
+ return err;
+
+ totalFileSize = fileInfo.size;
+ afc_file_info_free(&fileInfo);
- IdeviceFfiError *read_err = nullptr;
- // Read file in chunks
while (true) {
// Check for cancellation
if (cancelRequested && cancelRequested->load()) {
fclose(out);
- safeAfcFileClose(device, afcHandle);
- return new IdeviceFfiError{1, "OPERATION_CANCELLED"};
+ err = afc_file_close(afcHandle);
+ if (err != nullptr) {
+ idevice_error_free(err);
+ }
+ // FIXME: we need error codes
+ return new IdeviceFfiError{1, "Transfer cancelled"};
}
- read_err = safeAfcFileRead(device, afcHandle, &chunkData,
- CHUNK_SIZE, &bytesRead);
+ err = afc_file_read(afcHandle, &chunkData, CHUNK_SIZE,
+ &bytesRead);
- if (read_err != nullptr) {
- qDebug() << "Error reading file:" << read_err->message;
+ if (err != nullptr) {
+ qDebug() << "Error reading file:" << err->message;
fclose(out);
- safeAfcFileClose(device, afcHandle);
- return read_err;
+ IdeviceFfiError *err_close = afc_file_close(afcHandle);
+ if (err_close != nullptr) {
+ idevice_error_free(err_close);
+ }
+ return err;
}
if (bytesRead == 0) {
- // End of file reached
+ // End of file
break;
}
- // Write chunk to local file
+ // Write chunk
size_t written = fwrite(chunkData, 1, bytesRead, out);
- // Free the memory allocated by afc_file_read
afc_file_read_data_free(chunkData, bytesRead);
chunkData = nullptr;
if (written != bytesRead) {
qDebug() << "Failed to write all bytes to local file";
fclose(out);
- safeAfcFileClose(device, afcHandle);
- return new IdeviceFfiError{1, "WRITE_ERROR"};
+ IdeviceFfiError *err_close = afc_file_close(afcHandle);
+ if (err_close != nullptr) {
+ idevice_error_free(err_close);
+ }
+ return new IdeviceFfiError{1,
+ "Failed to write to local file"};
}
totalBytesRead += bytesRead;
- // Report progress
if (progressCallback) {
progressCallback(totalBytesRead, totalFileSize);
}
@@ -320,13 +326,13 @@ IdeviceFfiError *ServiceManager::exportFileToPath(
fclose(out);
- IdeviceFfiError *err_close = safeAfcFileClose(device, afcHandle);
+ IdeviceFfiError *err_close = afc_file_close(afcHandle);
if (err_close != nullptr) {
qDebug() << "Failed to close AFC file:" << err_close->message;
return err_close;
}
- return nullptr; // Success
+ return nullptr;
});
}
diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp
index bc06876..14e9104 100644
--- a/src/toolboxwidget.cpp
+++ b/src/toolboxwidget.cpp
@@ -456,16 +456,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice)
liveScreen->show();
} break;
case iDescriptorTool::RecoveryMode: {
- // Handle entering recovery mode
- // bool success = _enterRecoveryMode(device);
- // QMessageBox msgBox;
- // msgBox.setWindowTitle("Recovery Mode");
- // if (success) {
- // msgBox.setText("Successfully entered recovery mode.");
- // } else {
- // msgBox.setText("Failed to enter recovery mode.");
- // }
- // msgBox.exec();
+ enterRecoveryMode(device);
} break;
case iDescriptorTool::MountDevImage: {
DevDiskImageHelper *devDiskImageHelper =
@@ -547,8 +538,6 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice)
#endif
case iDescriptorTool::CableInfoWidget: {
CableInfoWidget *cableInfoWidget = new CableInfoWidget(device);
- cableInfoWidget->setAttribute(Qt::WA_DeleteOnClose);
- cableInfoWidget->resize(600, 400);
cableInfoWidget->show();
} break;
case iDescriptorTool::NetworkDevices: {
@@ -569,7 +558,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice)
}
}
-void ToolboxWidget::restartDevice(iDescriptorDevice *device)
+void ToolboxWidget::restartDevice(const iDescriptorDevice *device)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Restart Device");
@@ -598,7 +587,7 @@ void ToolboxWidget::restartDevice(iDescriptorDevice *device)
}
}
-void ToolboxWidget::shutdownDevice(iDescriptorDevice *device)
+void ToolboxWidget::shutdownDevice(const iDescriptorDevice *device)
{
QMessageBox msgBox;
@@ -629,7 +618,7 @@ void ToolboxWidget::shutdownDevice(iDescriptorDevice *device)
}
}
-void ToolboxWidget::_enterRecoveryMode(iDescriptorDevice *device)
+void ToolboxWidget::enterRecoveryMode(const iDescriptorDevice *device)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Enter Recovery Mode");
@@ -645,18 +634,16 @@ void ToolboxWidget::_enterRecoveryMode(iDescriptorDevice *device)
return;
}
- // auto res = device->diagRelay->enterRecovery();
- // if (res.is_err()) {
- // QMessageBox::warning(
- // nullptr, "Enter Recovery Mode Failed",
- // "Failed to enter recovery mode: " +
- // QString::fromStdString(res.unwrap_err().message));
- // } else {
- // QMessageBox::information(nullptr, "Enter Recovery Mode Initiated",
- // "Device will enter recovery mode once
- // unplugged.");
- // qDebug() << "Entering recovery mode";
- // }
+ IdeviceFfiError *error = lockdownd_enter_recovery(device->lockdown);
+ if (error != nullptr) {
+ QMessageBox::warning(nullptr, "Enter Recovery Mode Failed",
+ "Failed to enter recovery mode: " +
+ QString::fromStdString(error->message));
+ idevice_error_free(error);
+ } else {
+ QMessageBox::information(nullptr, "Enter Recovery Mode Initiated",
+ "Device will enter recovery mode.");
+ }
}
void ToolboxWidget::restartAirPlayWidget()
diff --git a/src/toolboxwidget.h b/src/toolboxwidget.h
index 03d1111..52bf8f5 100644
--- a/src/toolboxwidget.h
+++ b/src/toolboxwidget.h
@@ -44,9 +44,9 @@ class ToolboxWidget : public QWidget
Q_OBJECT
public:
explicit ToolboxWidget(QWidget *parent = nullptr);
- static void restartDevice(iDescriptorDevice *device);
- static void shutdownDevice(iDescriptorDevice *device);
- static void _enterRecoveryMode(iDescriptorDevice *device);
+ static void restartDevice(const iDescriptorDevice *device);
+ static void shutdownDevice(const iDescriptorDevice *device);
+ static void enterRecoveryMode(const iDescriptorDevice *device);
static ToolboxWidget *sharedInstance();
void restartAirPlayWidget();
private slots:
diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp
index 3a20cfc..ab82626 100644
--- a/src/virtuallocationwidget.cpp
+++ b/src/virtuallocationwidget.cpp
@@ -152,8 +152,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
this->deleteLater();
}
});
-
- // DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
}
void VirtualLocation::onQuickWidgetStatusChanged(QQuickWidget::Status status)
@@ -170,8 +168,7 @@ void VirtualLocation::onQuickWidgetStatusChanged(QQuickWidget::Status status)
void VirtualLocation::onInputChanged()
{
- // Update map when input changes (with slight delay to avoid too frequent
- // updates)
+ // Update map when input changes
m_updateTimer.setSingleShot(true);
m_updateTimer.setInterval(500); // 500ms delay
@@ -301,186 +298,251 @@ void VirtualLocation::onApplyClicked()
m_applyButton->setEnabled(true);
return;
}
- // DevDiskImageHelper *devDiskImageHelper =
- // new DevDiskImageHelper(m_device, this);
- // connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, this,
- // [this, latitude, longitude, devDiskImageHelper](bool success) {
- // if (devDiskImageHelper) {
- // devDiskImageHelper->deleteLater();
- // }
-
- // if (!success) {
- // return;
- // }
-
- // // Apply location
- // emit locationChanged(latitude, longitude);
- // updateMapFromInputs();
-
- // // Visual feedback
- // m_applyButton->setText("Applied!");
-
- // bool locationSuccess = set_location(
- // m_device->device,
- // const_cast(
- // m_latitudeEdit->text().toStdString().c_str()),
- // const_cast(
- // m_longitudeEdit->text().toStdString().c_str()));
-
- // if (!locationSuccess) {
- // QMessageBox::warning(this, "Error",
- // "Failed to set location on device");
- // } else {
- // SettingsManager::sharedInstance()->saveRecentLocation(
- // latitude, longitude);
-
- // QMessageBox::information(this, "Success",
- // "Location applied
- // successfully!");
- // }
- // });
- // connect(
- // devDiskImageHelper, &DevDiskImageHelper::destroyed, this,
- // [this]() {
- // QTimer::singleShot(1000, this, [this]() {
- // m_applyButton->setText("Apply Location");
- // m_applyButton->setEnabled(true);
- // });
- // },
- // Qt::SingleShotConnection);
- // devDiskImageHelper->start();
int major = m_device->deviceInfo.parsedDeviceVersion.major;
if (major < 17) {
- QMessageBox::warning(this, "TODO", "TODO");
+ LocationSimulationServiceHandle *locationSim = nullptr;
+ IdeviceFfiError *err = nullptr;
+ err = lockdown_location_simulation_connect(m_device->provider,
+ &locationSim);
+ if (err != NULL) {
+ qDebug() << "Failed to connect to location simulation service:"
+ << err->code << err->message;
+ if (err->code != InvalidServiceErrorCode) {
+ QMessageBox::warning(
+ this, "Error",
+ QString(
+ "Failed to connect to location simulation service: %1")
+ .arg(err->message));
+ idevice_error_free(err);
+ m_applyButton->setEnabled(true);
+ return;
+ }
+
+ idevice_error_free(err);
+ DevDiskImageHelper *devDiskImageHelper =
+ new DevDiskImageHelper(m_device, this);
+ connect(
+ devDiskImageHelper, &DevDiskImageHelper::mountingCompleted,
+ this,
+ [this, latitude, longitude, devDiskImageHelper](bool success) {
+ if (devDiskImageHelper) {
+ devDiskImageHelper->deleteLater();
+ }
+
+ if (!success) {
+ // mounter will show its own error message, just
+ // re-enable the button here
+ m_applyButton->setEnabled(true);
+ return;
+ }
+ IdeviceFfiError *err = nullptr;
+ LocationSimulationServiceHandle *locationSim = nullptr;
+ err = lockdown_location_simulation_connect(
+ m_device->provider, &locationSim);
+
+ if (err != NULL) {
+ qDebug() << "Failed to connect to location simulation "
+ "service after mounting disk image:"
+ << err->code << err->message;
+ QMessageBox::warning(
+ this, "Error",
+ QString("Failed to connect to location simulation "
+ "service after mounting disk image: %1")
+ .arg(err->message));
+ idevice_error_free(err);
+ m_applyButton->setEnabled(true);
+ return;
+ }
+
+ err = lockdown_location_simulation_set(
+ locationSim,
+ m_latitudeEdit->text().toStdString().c_str(),
+ m_longitudeEdit->text().toStdString().c_str());
+ if (err != NULL) {
+ qDebug()
+ << "Failed to set location simulation:" << err->code
+ << err->message;
+ QMessageBox::warning(
+ this, "Error",
+ QString("Failed to set location simulation: %1")
+ .arg(err->message));
+ idevice_error_free(err);
+ }
+
+ lockdown_location_simulation_free(locationSim);
+ QMessageBox::information(this, "Success",
+ "Location applied successfully!");
+
+ SettingsManager::sharedInstance()->saveRecentLocation(
+ latitude, longitude);
+ m_applyButton->setEnabled(true);
+ return;
+ });
+ connect(
+ devDiskImageHelper, &DevDiskImageHelper::destroyed, this,
+ [this]() {
+ QTimer::singleShot(1000, this, [this]() {
+ m_applyButton->setText("Apply Location");
+ m_applyButton->setEnabled(true);
+ });
+ },
+ Qt::SingleShotConnection);
+ return devDiskImageHelper->start();
+ }
+
+ err = lockdown_location_simulation_set(
+ locationSim, m_latitudeEdit->text().toStdString().c_str(),
+ m_longitudeEdit->text().toStdString().c_str());
+ if (err != NULL) {
+ qDebug() << "Failed to set location simulation:" << err->code
+ << err->message;
+ QMessageBox::warning(
+ this, "Error",
+ QString("Failed to set location simulation: %1")
+ .arg(err->message));
+ idevice_error_free(err);
+ }
+
+ lockdown_location_simulation_free(locationSim);
+ QMessageBox::information(this, "Success",
+ "Location applied successfully!");
m_applyButton->setEnabled(true);
return;
}
+ /* iOS 17 and above */
IdeviceFfiError *err = nullptr;
- // Connect to CoreDeviceProxy
+ IdeviceFfiError *err_reveal = nullptr;
CoreDeviceProxyHandle *core_device = NULL;
+ uint16_t rsd_port;
+ AdapterHandle *adapter = NULL;
+ ReadWriteOpaque *stream = NULL;
+ RsdHandshakeHandle *handshake = NULL;
+ RemoteServerHandle *remote_server = NULL;
+ LocationSimulationHandle *location_sim = NULL;
+
err = core_device_proxy_connect(m_device->provider, &core_device);
if (err != NULL) {
- fprintf(stderr, "Failed to connect to CoreDeviceProxy: [%d] %s",
- err->code, err->message);
- idevice_error_free(err);
+ qDebug() << "Failed to connect to CoreDeviceProxy:" << err->code
+ << err->message;
+ goto cleanup;
}
- // Get server RSD port
- uint16_t rsd_port;
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
if (err != NULL) {
- fprintf(stderr, "Failed to get server RSD port: [%d] %s", err->code,
- err->message);
- idevice_error_free(err);
- core_device_proxy_free(core_device);
+ qDebug() << "Failed to get server RSD port:" << err->code
+ << err->message;
+ goto cleanup;
}
- // Create TCP adapter and connect to RSD port
- AdapterHandle *adapter = NULL;
err = core_device_proxy_create_tcp_adapter(core_device, &adapter);
if (err != NULL) {
- fprintf(stderr, "Failed to create TCP adapter: [%d] %s", err->code,
- err->message);
- idevice_error_free(err);
+ qDebug() << "Failed to create TCP adapter:" << err->code
+ << err->message;
+ goto cleanup;
}
- // Connect to RSD port
- ReadWriteOpaque *stream = NULL;
err = adapter_connect(adapter, rsd_port, &stream);
if (err != NULL) {
- fprintf(stderr, "Failed to connect to RSD port: [%d] %s", err->code,
- err->message);
- idevice_error_free(err);
- adapter_free(adapter);
+ qDebug() << "Failed to connect to RSD port:" << err->code
+ << err->message;
+ goto cleanup;
}
- RsdHandshakeHandle *handshake = NULL;
err = rsd_handshake_new(stream, &handshake);
if (err != NULL) {
- fprintf(stderr, "Failed to perform RSD handshake: [%d] %s", err->code,
- err->message);
- idevice_error_free(err);
- // adapter_close(stream);
- idevice_stream_free(stream);
- adapter_free(adapter);
+ qDebug() << "Failed to perform RSD handshake:" << err->code
+ << err->message;
+ goto cleanup;
}
- // Create RemoteServerClient
- RemoteServerHandle *remote_server = NULL;
err = remote_server_connect_rsd(adapter, handshake, &remote_server);
if (err != NULL) {
- // needs dev mode
- fprintf(stderr, "Failed to create remote server: [%d] %s", err->code,
- err->message);
if (err->code == ServiceNotFoundErrorCode) {
- auto res = QMessageBox::question(
- this, "Enable Developer Mode?",
- "Location Simulation service is not found. Enable Developer "
- "Mode on the device?",
- QMessageBox::Yes | QMessageBox::No);
- if (res == QMessageBox::Yes) {
- IdeviceFfiError *devmodeErr =
- ServiceManager::enableDevMode(m_device);
- if (devmodeErr != NULL) {
- QMessageBox::warning(
- this, "Error",
- QString("Failed to enable Developer Mode:\n%1")
- .arg(devmodeErr->message));
- idevice_error_free(devmodeErr);
- } else {
- QMessageBox::information(
- this, "Success",
- "Developer Mode enabled successfully. Please try "
- "applying the location again.");
- }
+ err_reveal =
+ ServiceManager::revealDeveloperModeOptionInUI(m_device);
+ if (err_reveal != NULL) {
+ qDebug() << "Failed to reveal developer mode option in UI:"
+ << err_reveal->code << err_reveal->message;
+ QMessageBox::warning(
+ this, "Error",
+ "Developer Mode Not Enabled and failed to "
+ "reveal developer mode option in UI:\n" +
+ QString::fromStdString(err_reveal->message));
+ goto cleanup;
}
-
- idevice_error_free(err);
- adapter_free(adapter);
- rsd_handshake_free(handshake);
- }
-
- // Create LocationSimulationClient
- LocationSimulationHandle *location_sim = NULL;
- err = location_simulation_new(remote_server, &location_sim);
- if (err != NULL) {
- fprintf(stderr,
- "Failed to create location simulation client: [%d] %s",
- err->code, err->message);
- idevice_error_free(err);
- remote_server_free(remote_server);
- }
-
- // Set location
- err = location_simulation_set(location_sim, latitude, longitude);
- if (err != NULL) {
- fprintf(stderr, "Failed to set location: [%d] %s", err->code,
- err->message);
- idevice_error_free(err);
+ // TODO: create a widget showing instructions on how to enable dev
+ // mode,
+ QMessageBox::information(this, "Developer Mode Not Enabled",
+ "Please enable Developer "
+ "Mode on the device to use this "
+ "feature.");
+ goto cleanup;
} else {
- printf("Successfully set location to %.6f, %.6f\n", latitude,
- longitude);
+ qDebug() << "Failed to connect to remote server:" << err->code
+ << err->message;
+ QMessageBox::warning(this, "Connection Failed",
+ "Failed to connect to device's remote "
+ "service:\n" +
+ QString::fromStdString(err->message));
+ goto cleanup;
}
}
- // // FIXME: create issue for c bindings
- // IdeviceFfiError *err =
- // location_simulation_set(m_device->locationSimulation,
- // latitude, longitude);
- // if (err != nullptr) {
- // QMessageBox::warning(this, "Error",
- // "Failed to set location on device:\n" +
- // QString::fromStdString(err->message));
- // // idevice_ffi_error_free(err);
- // } else {
- // // SettingsManager::sharedInstance()->saveRecentLocation(
- // // latitude, longitude);
- // QMessageBox::information(this, "Success",
- // "Location applied successfully!");
+ err = location_simulation_new(remote_server, &location_sim);
+ if (err != NULL) {
+ qDebug() << "Failed to create location simulation client:" << err->code
+ << err->message;
+ QMessageBox::warning(this, "Error",
+ "Failed to create location simulation client:\n" +
+ QString::fromStdString(err->message));
+ goto cleanup;
+ }
+
+ err = location_simulation_set(location_sim, latitude, longitude);
+ if (err != NULL) {
+ qDebug() << "Failed to set location:" << err->code << err->message;
+ QMessageBox::warning(this, "Error",
+ "Failed to set location on device:\n" +
+ QString::fromStdString(err->message));
+ } else {
+ qDebug() << "Successfully set location to" << latitude << longitude;
+ QMessageBox::information(this, "Success",
+ "Location applied successfully!");
+ SettingsManager::sharedInstance()->saveRecentLocation(latitude,
+ longitude);
+ }
+
+cleanup:
+ if (location_sim) {
+ location_simulation_free(location_sim);
+ }
+ if (remote_server) {
+ remote_server_free(remote_server);
+ }
+ if (handshake) {
+ rsd_handshake_free(handshake);
+ }
+ if (adapter) {
+ adapter_close(adapter);
+ adapter_free(adapter);
+ }
+ // FIXME: seg faults
+ // if (stream) {
+ // idevice_stream_free(stream);
// }
+ if (core_device) {
+ core_device_proxy_free(core_device);
+ }
+ if (err) {
+ idevice_error_free(err);
+ }
+ if (err_reveal) {
+ idevice_error_free(err_reveal);
+ }
+ m_applyButton->setEnabled(true);
}
void VirtualLocation::loadRecentLocations(QVBoxLayout *layout)
diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp
index 2d41a27..0204394 100644
--- a/src/welcomewidget.cpp
+++ b/src/welcomewidget.cpp
@@ -87,8 +87,7 @@ void WelcomeWidget::setupUI()
[]() { QDesktopServices::openUrl(QUrl(REPO_URL)); });
QPalette githubPalette = m_githubLabel->palette();
- githubPalette.setColor(QPalette::WindowText,
- COLOR_HYPERLINK); // Apple blue
+ githubPalette.setColor(QPalette::WindowText, COLOR_HYPERLINK);
m_githubLabel->setPalette(githubPalette);
m_mainLayout->addWidget(m_githubLabel, 0, Qt::AlignCenter);
diff --git a/src/wirelessgalleryimportwidget.cpp b/src/wirelessgalleryimportwidget.cpp
index 8a2c4f3..5fc3cb0 100644
--- a/src/wirelessgalleryimportwidget.cpp
+++ b/src/wirelessgalleryimportwidget.cpp
@@ -190,7 +190,7 @@ void WirelessGalleryImportWidget::onImportPhotos()
return;
}
- PhotoImportDialog dialog(m_selectedFiles, false, this);
+ PhotoImportDialog dialog(m_selectedFiles, this);
dialog.exec();
}
diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp
index 3925032..5266712 100644
--- a/src/zloadingwidget.cpp
+++ b/src/zloadingwidget.cpp
@@ -22,7 +22,13 @@ ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent)
loadingLayout->addWidget(m_loadingIndicator);
loadingLayout->addStretch();
- addWidget(loadingWidget); // Loading widget at index 0
+ m_errorWidget = new ZLoadingErrorWidget(this);
+ connect(static_cast(m_errorWidget),
+ &ZLoadingErrorWidget::retryClicked, this,
+ [this]() { emit retryClicked(); });
+
+ addWidget(loadingWidget);
+ addWidget(m_errorWidget);
}
void ZLoadingWidget::setupContentWidget(QWidget *contentWidget)
@@ -41,32 +47,24 @@ void ZLoadingWidget::setupContentWidget(QLayout *contentLayout)
void ZLoadingWidget::setupErrorWidget(QWidget *errorWidget)
{
+ if (m_errorWidget) {
+ m_errorWidget->deleteLater();
+ }
m_errorWidget = errorWidget;
addWidget(m_errorWidget);
}
void ZLoadingWidget::setupErrorWidget(QLayout *errorLayout)
{
+ if (m_errorWidget) {
+ m_errorWidget->deleteLater();
+ }
m_errorWidget = new QWidget();
m_errorWidget->setLayout(errorLayout);
addWidget(m_errorWidget);
}
-void ZLoadingWidget::setupErrorWidget(const QString &errorMessage)
-{
- m_errorWidget = new QWidget();
- QVBoxLayout *errorLayout = new QVBoxLayout(m_errorWidget);
- errorLayout->setAlignment(Qt::AlignCenter);
-
- QLabel *errorLabel = new QLabel(errorMessage);
- errorLabel->setAlignment(Qt::AlignCenter);
- errorLabel->setStyleSheet("QLabel { color: red; }");
- errorLayout->addWidget(errorLabel);
-
- addWidget(m_errorWidget);
-}
-
void ZLoadingWidget::setupAditionalWidget(QWidget *customWidget)
{
addWidget(customWidget);
@@ -76,6 +74,9 @@ void ZLoadingWidget::switchToWidget(QWidget *widget)
{
int index = indexOf(widget);
if (index != -1) {
+ if (m_loadingIndicator) {
+ m_loadingIndicator->stop();
+ }
setCurrentIndex(index);
}
}
@@ -85,9 +86,8 @@ void ZLoadingWidget::stop(bool showContent)
if (m_loadingIndicator) {
m_loadingIndicator->stop();
}
- if (showContent) {
- // FIXME: dont use hardcoded index
- setCurrentIndex(1);
+ if (showContent && m_contentWidget) {
+ switchToWidget(m_contentWidget);
}
}
@@ -99,13 +99,26 @@ void ZLoadingWidget::showError()
}
}
+void ZLoadingWidget::showError(const QString &errorMessage)
+{
+ m_loadingIndicator->stop();
+ if (m_errorWidget) {
+ // FIXME: can be handled better
+ // maybe subclass ZLoadingWidget for custom error widget?
+ if (auto errorWidget =
+ qobject_cast(m_errorWidget)) {
+ errorWidget->setText(errorMessage);
+ }
+ setCurrentWidget(m_errorWidget);
+ }
+}
+
void ZLoadingWidget::showLoading()
{
if (m_loadingIndicator) {
m_loadingIndicator->start();
}
- // FIXME: dont use hardcoded index
- setCurrentIndex(0);
+ setCurrentWidget(m_loadingIndicator->parentWidget());
}
ZLoadingWidget::~ZLoadingWidget()
diff --git a/src/zloadingwidget.h b/src/zloadingwidget.h
index f046715..5029bdd 100644
--- a/src/zloadingwidget.h
+++ b/src/zloadingwidget.h
@@ -1,8 +1,41 @@
#ifndef ZLOADINGWIDGET_H
#define ZLOADINGWIDGET_H
+#include
+#include
#include
#include
+#include
+
+class ZLoadingErrorWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ ZLoadingErrorWidget(QWidget *parent = nullptr)
+ {
+
+ QHBoxLayout *layout = new QHBoxLayout(this);
+ layout->addStretch();
+ m_errorLabel = new QLabel("An error occurred.", this);
+ m_errorLabel->setAlignment(Qt::AlignCenter);
+ m_errorLabel->setStyleSheet("QLabel { color: red; }");
+ layout->addWidget(m_errorLabel);
+ m_retryButton = new QPushButton("Retry", this);
+ m_retryButton->setMaximumWidth(m_retryButton->sizeHint().width());
+ layout->addWidget(m_retryButton);
+ layout->addStretch();
+ connect(m_retryButton, &QPushButton::clicked, this,
+ [this]() { emit retryClicked(); });
+ }
+
+ void setText(const QString &text) { m_errorLabel->setText(text); };
+
+private:
+ QLabel *m_errorLabel;
+ QPushButton *m_retryButton;
+signals:
+ void retryClicked();
+};
class ZLoadingWidget : public QStackedWidget
{
@@ -15,17 +48,18 @@ public:
void setupContentWidget(QWidget *contentWidget);
void setupContentWidget(QLayout *contentLayout);
void setupErrorWidget(QWidget *errorWidget);
- void setupErrorWidget(const QString &errorMessage);
void setupErrorWidget(QLayout *errorLayout);
void setupAditionalWidget(QWidget *customWidget);
void switchToWidget(QWidget *widget);
void showError();
+ void showError(const QString &errorMessage);
private:
class QProcessIndicator *m_loadingIndicator = nullptr;
QWidget *m_contentWidget = nullptr;
QWidget *m_errorWidget = nullptr;
signals:
+ void retryClicked();
};
#endif // ZLOADINGWIDGET_H