mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
detect recovery devices, cleanup code , use const pointers wherever possible, refactor img thumbnail loading, refactor gallery widget
This commit is contained in:
+44
-5
@@ -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)
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<file>resources/icons/MaterialSymbolsFolder.png</file>
|
||||
<file>resources/icons/QlementineIconsWireless116.png</file>
|
||||
<file>resources/icons/UimProcess.png</file>
|
||||
<file>resources/icons/LetsIconsHorizontalDownLeftMainLight.png</file>
|
||||
<file>qml/MapView.qml</file>
|
||||
<file>resources/iphone.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios4.png</file>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -44,7 +44,8 @@
|
||||
#include <QTreeWidget>
|
||||
#include <QVariant>
|
||||
|
||||
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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
+87
-62
@@ -25,6 +25,7 @@
|
||||
#include "networkdevicemanager.h"
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QThreadPool>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
#include <thread>
|
||||
@@ -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<iDescriptorInitDeviceResult>();
|
||||
|
||||
@@ -237,7 +246,7 @@ void AppContext::addDevice(iDescriptor::Uniq uniq,
|
||||
watcher->setFuture(future);
|
||||
connect(
|
||||
watcher, &QFutureWatcher<void>::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<std::recursive_mutex> 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<std::recursive_mutex> lock(*deviceInfo->mutex);
|
||||
// delete deviceInfo->mutex;
|
||||
// delete deviceInfo;
|
||||
std::lock_guard<std::recursive_mutex> 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<iDescriptorDevice *> AppContext::getAllDevices()
|
||||
@@ -565,12 +587,12 @@ QList<iDescriptorDevice *> AppContext::getAllDevices()
|
||||
return m_devices.values();
|
||||
}
|
||||
|
||||
// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
// QList<iDescriptorRecoveryDevice *> AppContext::getAllRecoveryDevices()
|
||||
// {
|
||||
// // return m_recoveryDevices.values();
|
||||
// }
|
||||
// #endif
|
||||
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
QList<iDescriptorRecoveryDevice *> 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<iDescriptorInitDeviceResultRecovery>();
|
||||
|
||||
// if (!res.success) {
|
||||
// qDebug() << "Failed to initialize recovery device with ECID: "
|
||||
// << QString::number(ecid);
|
||||
// qDebug() << "Error code: " << res.error;
|
||||
// return;
|
||||
// }
|
||||
QFuture<void> future = QtConcurrent::run(
|
||||
[this, ecid, res]() { init_idescriptor_recovery_device(ecid, *res); });
|
||||
QFutureWatcher<void> *watcher = new QFutureWatcher<void>();
|
||||
watcher->setFuture(future);
|
||||
connect(watcher, &QFutureWatcher<void>::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);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -34,14 +34,13 @@ public:
|
||||
QList<iDescriptorDevice *> getAllDevices();
|
||||
explicit AppContext(QObject *parent = nullptr);
|
||||
bool noDevicesConnected() const;
|
||||
// QMap<WiFiMACAddress, PairingFilePath>
|
||||
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<iDescriptorRecoveryDevice *> getAllRecoveryDevices();
|
||||
// #endif
|
||||
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
QList<iDescriptorRecoveryDevice *> getAllRecoveryDevices();
|
||||
#endif
|
||||
~AppContext();
|
||||
int getConnectedDeviceCount() const;
|
||||
|
||||
@@ -52,24 +51,25 @@ public:
|
||||
|
||||
private:
|
||||
QMap<std::string, iDescriptorDevice *> m_devices;
|
||||
// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
// QMap<uint64_t, iDescriptorRecoveryDevice *> m_recoveryDevices;
|
||||
// #endif
|
||||
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
QMap<uint64_t, iDescriptorRecoveryDevice *> m_recoveryDevices;
|
||||
#endif
|
||||
QStringList m_pendingDevices;
|
||||
DeviceSelection m_currentSelection = DeviceSelection("");
|
||||
QMap<QString, QString> 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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* iDescriptor: A free and open-source idevice management tool.
|
||||
*
|
||||
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "libirecovery.h"
|
||||
#include <string>
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -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<const char *>(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);
|
||||
|
||||
|
||||
@@ -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 <QDebug>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<void(bool)> 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();
|
||||
|
||||
@@ -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<QString, QString> 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<void(bool)> callback);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
#include <QPainterPath>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
DeviceImageWidget::DeviceImageWidget(const iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget(parent), m_device(device)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "iDescriptor.h"
|
||||
#include "infolabel.h"
|
||||
#include "privateinfolabel.h"
|
||||
#include "toolboxwidget.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
@@ -44,7 +45,8 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCore>
|
||||
|
||||
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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
+87
-93
@@ -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 <QDebug>
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
+14
-15
@@ -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 <QHBoxLayout>
|
||||
#include <QMap>
|
||||
#include <QStackedWidget>
|
||||
@@ -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<DevicePendingWidget *, DevicePendingSidebarItem *>>
|
||||
m_pendingDeviceWidgets; // Map to store devices by UDID
|
||||
|
||||
// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
// QMap<uint64_t,
|
||||
// std::pair<RecoveryDeviceInfoWidget *, RecoveryDeviceSidebarItem
|
||||
// *>>
|
||||
// m_recoveryDeviceWidgets; // Map to store recovery devices by ECID
|
||||
// #endif
|
||||
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
|
||||
QMap<uint64_t,
|
||||
std::pair<RecoveryDeviceInfoWidget *, RecoveryDeviceSidebarItem *>>
|
||||
m_recoveryDeviceWidgets; // Map to store recovery devices by ECID
|
||||
#endif
|
||||
|
||||
std::string m_currentDeviceUuid;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
DeviceMenuWidget::DeviceMenuWidget(const iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}, m_device(device)
|
||||
{
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ ExportManager::~ExportManager()
|
||||
m_activeJobs.clear();
|
||||
}
|
||||
|
||||
QUuid ExportManager::startExport(iDescriptorDevice *device,
|
||||
QUuid ExportManager::startExport(const iDescriptorDevice *device,
|
||||
const QList<ExportItem> &items,
|
||||
const QString &destinationPath,
|
||||
std::optional<AfcClientHandle *> altAfc)
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ public:
|
||||
ExportManager(const ExportManager &) = delete;
|
||||
ExportManager &operator=(const ExportManager &) = delete;
|
||||
|
||||
QUuid startExport(iDescriptorDevice *device, const QList<ExportItem> &items,
|
||||
QUuid startExport(const iDescriptorDevice *device,
|
||||
const QList<ExportItem> &items,
|
||||
const QString &destinationPath,
|
||||
std::optional<AfcClientHandle *> altAfc = std::nullopt);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#include <QTreeWidget>
|
||||
#include <QVariant>
|
||||
|
||||
FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device,
|
||||
FileExplorerWidget::FileExplorerWidget(const iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget(parent), m_device(device)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
+14
-13
@@ -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);
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+25
-1
@@ -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);
|
||||
}
|
||||
|
||||
+31
-39
@@ -36,6 +36,7 @@
|
||||
#include <idevice++/heartbeat.hpp>
|
||||
#include <idevice++/installation_proxy.hpp>
|
||||
#include <idevice++/lockdown.hpp>
|
||||
#include <idevice++/lockdown_location_simulation.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
#include <idevice++/readwrite.hpp>
|
||||
#include <idevice++/rsd.hpp>
|
||||
@@ -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 <libirecovery.h>
|
||||
#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<DiagnosticsRelay> 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
|
||||
|
||||
+165
-64
@@ -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<void(const QPixmap &)> 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<StreamContext *>(opaque);
|
||||
|
||||
@@ -201,10 +327,8 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device,
|
||||
std::min(static_cast<uint32_t>(bufSize),
|
||||
static_cast<uint32_t>(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;
|
||||
}
|
||||
+11
-5
@@ -3,19 +3,20 @@
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include <QCache>
|
||||
#include <QHash>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QThreadPool>
|
||||
|
||||
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<void(const QPixmap &)> callback);
|
||||
void cancelThumbnail(const QString &path);
|
||||
bool isLoading(const QString &path);
|
||||
void clear();
|
||||
@@ -46,7 +51,8 @@ private slots:
|
||||
|
||||
private:
|
||||
QThreadPool m_pool;
|
||||
QSet<QString> m_pending;
|
||||
QHash<QString, ImageTask *> m_pendingTasks;
|
||||
QMutex m_mutex;
|
||||
};
|
||||
|
||||
#endif // IMAGELOADER_H
|
||||
+15
-9
@@ -1,6 +1,7 @@
|
||||
#ifndef IMAGETASK_H
|
||||
#define IMAGETASK_H
|
||||
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
+67
-48
@@ -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<QVariantMap> 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<const char *>(out_result),
|
||||
static_cast<int>(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<QPixmap>();
|
||||
|
||||
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<const char *>(out_result),
|
||||
static_cast<int>(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; }");
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
// 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<AppTabWidget *> m_appTabs;
|
||||
AppTabWidget *m_selectedTab = nullptr;
|
||||
SpringBoardServicesClientHandle *m_springboardClient = nullptr;
|
||||
};
|
||||
|
||||
#endif // INSTALLEDAPPSWIDGET_H
|
||||
+49
-18
@@ -36,11 +36,9 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#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 <QApplication>
|
||||
#include <QDesktopServices>
|
||||
@@ -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,
|
||||
|
||||
+54
-102
@@ -47,20 +47,12 @@
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtGlobal>
|
||||
|
||||
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<QPixmap>(this);
|
||||
connect(watcher, &QFutureWatcher<QPixmap>::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<QKeyEvent *>(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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+12
-23
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "zloadingwidget.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDialog>
|
||||
#include <QGraphicsPixmapItem>
|
||||
@@ -37,21 +38,12 @@
|
||||
#include <QVideoWidget>
|
||||
#include <QtGlobal>
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
+22
-29
@@ -30,7 +30,7 @@
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
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<void *>(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<QTcpSocket *>(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<QTcpSocket *>(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<uint32_t>(
|
||||
qMin(static_cast<qint64>(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"
|
||||
|
||||
+3
-24
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -27,40 +27,16 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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<void *>(device)));
|
||||
radioButton->setProperty(
|
||||
"devicePointer",
|
||||
QVariant::fromValue(static_cast<const void *>(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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,11 +31,8 @@
|
||||
#include <QUrl>
|
||||
#include <qrencode.h>
|
||||
|
||||
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));
|
||||
|
||||
@@ -25,20 +25,20 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMediaPlayer>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QStringList>
|
||||
#include <QVBoxLayout>
|
||||
#include <QStackedWidget>
|
||||
#include <QVideoWidget>
|
||||
#include <QMediaPlayer>
|
||||
|
||||
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;
|
||||
|
||||
+67
-48
@@ -34,40 +34,27 @@
|
||||
#include <QTimer>
|
||||
#include <QVideoFrame>
|
||||
#include <QVideoSink>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
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<bool> *futureWatcher = new QFutureWatcher<bool>(this);
|
||||
QFuture<bool> future =
|
||||
QtConcurrent::run([this]() { return populatePhotoPaths(); });
|
||||
futureWatcher->setFuture(future);
|
||||
connect(futureWatcher, &QFutureWatcher<bool>::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(); }
|
||||
|
||||
+12
-7
@@ -26,10 +26,12 @@
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QFutureWatcher>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QPixmap>
|
||||
#include <QSemaphore>
|
||||
#include <QSize>
|
||||
#include <QStandardPaths>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
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<PhotoInfo> m_allPhotos; // All photos from device
|
||||
QList<PhotoInfo> 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<PhotoInfo> &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
|
||||
+130
-116
@@ -28,143 +28,157 @@
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
+46
-40
@@ -223,96 +223,102 @@ IdeviceFfiError *ServiceManager::exportFileToPath(
|
||||
std::atomic<bool> *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<IdeviceFfiError *>(
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+14
-27
@@ -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()
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+207
-145
@@ -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<char *>(
|
||||
// m_latitudeEdit->text().toStdString().c_str()),
|
||||
// const_cast<char *>(
|
||||
// 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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -190,7 +190,7 @@ void WirelessGalleryImportWidget::onImportPhotos()
|
||||
return;
|
||||
}
|
||||
|
||||
PhotoImportDialog dialog(m_selectedFiles, false, this);
|
||||
PhotoImportDialog dialog(m_selectedFiles, this);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
|
||||
+33
-20
@@ -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<ZLoadingErrorWidget *>(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<ZLoadingErrorWidget *>(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()
|
||||
|
||||
+35
-1
@@ -1,8 +1,41 @@
|
||||
#ifndef ZLOADINGWIDGET_H
|
||||
#define ZLOADINGWIDGET_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QWidget>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user