mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
refactor asset serving & fix typos
- Added necessary libraries for Linux build in build-linux.yml - Updated submodule reference for zupdater - Added new video resource for wireless gallery import - use UDID instead of UUID across multiple files - Improved user interface messages and layout in various widgets - Refactor http server
This commit is contained in:
@@ -67,6 +67,10 @@ jobs:
|
||||
libzip-dev \
|
||||
libssh-dev \
|
||||
libfuse3-dev \
|
||||
libavformat-dev \
|
||||
libavcodec-dev \
|
||||
libavutil-dev \
|
||||
libswscale-dev
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
|
||||
+1
-1
Submodule lib/zupdater updated: ba63a9f0dd...bd50e5f76d
@@ -64,5 +64,6 @@
|
||||
<file>resources/ipad-mockups/ipad.png</file>
|
||||
<file>resources/DeveloperDiskImages.json</file>
|
||||
<file>resources/keychain.mp4</file>
|
||||
<file>resources/wireless-gallery-import.mp4</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
+10
-10
@@ -143,9 +143,9 @@ int AppContext::getConnectedDeviceCount() const
|
||||
*/
|
||||
void AppContext::removeDevice(QString _udid)
|
||||
{
|
||||
const std::string uuid = _udid.toStdString();
|
||||
const std::string udid = _udid.toStdString();
|
||||
qDebug() << "AppContext::removeDevice device with UUID:"
|
||||
<< QString::fromStdString(uuid);
|
||||
<< QString::fromStdString(udid);
|
||||
|
||||
if (m_pendingDevices.contains(_udid)) {
|
||||
m_pendingDevices.removeAll(_udid);
|
||||
@@ -157,16 +157,16 @@ void AppContext::removeDevice(QString _udid)
|
||||
" not found in pending devices.";
|
||||
}
|
||||
|
||||
if (!m_devices.contains(uuid)) {
|
||||
if (!m_devices.contains(udid)) {
|
||||
qDebug() << "Device with UUID " + _udid +
|
||||
" not found in normal devices.";
|
||||
return;
|
||||
}
|
||||
|
||||
iDescriptorDevice *device = m_devices[uuid];
|
||||
m_devices.remove(uuid);
|
||||
iDescriptorDevice *device = m_devices[udid];
|
||||
m_devices.remove(udid);
|
||||
|
||||
emit deviceRemoved(uuid);
|
||||
emit deviceRemoved(udid);
|
||||
emit deviceChange();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
|
||||
@@ -202,9 +202,9 @@ void AppContext::removeRecoveryDevice(uint64_t ecid)
|
||||
}
|
||||
#endif
|
||||
|
||||
iDescriptorDevice *AppContext::getDevice(const std::string &uuid)
|
||||
iDescriptorDevice *AppContext::getDevice(const std::string &udid)
|
||||
{
|
||||
return m_devices.value(uuid, nullptr);
|
||||
return m_devices.value(udid, nullptr);
|
||||
}
|
||||
|
||||
QList<iDescriptorDevice *> AppContext::getAllDevices()
|
||||
@@ -283,9 +283,9 @@ void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection)
|
||||
{
|
||||
qDebug() << "New selection -"
|
||||
<< " Type:" << selection.type
|
||||
<< " UUID:" << QString::fromStdString(selection.uuid)
|
||||
<< " UDID:" << QString::fromStdString(selection.udid)
|
||||
<< " ECID:" << selection.ecid << " Section:" << selection.section;
|
||||
if (m_currentSelection.uuid == selection.uuid &&
|
||||
if (m_currentSelection.udid == selection.udid &&
|
||||
m_currentSelection.ecid == selection.ecid &&
|
||||
m_currentSelection.section == selection.section) {
|
||||
qDebug() << "setCurrentDeviceSelection: No change in selection";
|
||||
|
||||
@@ -318,13 +318,13 @@ void DeviceManagerWidget::onDeviceSelectionChanged(
|
||||
|
||||
switch (selection.type) {
|
||||
case DeviceSelection::Normal:
|
||||
if (m_deviceWidgets.contains(selection.uuid)) {
|
||||
if (m_currentDeviceUuid != selection.uuid) {
|
||||
setCurrentDevice(selection.uuid);
|
||||
if (m_deviceWidgets.contains(selection.udid)) {
|
||||
if (m_currentDeviceUuid != selection.udid) {
|
||||
setCurrentDevice(selection.udid);
|
||||
}
|
||||
|
||||
// Handle navigation section
|
||||
QWidget *tabWidget = m_deviceWidgets[selection.uuid].first;
|
||||
QWidget *tabWidget = m_deviceWidgets[selection.udid].first;
|
||||
DeviceMenuWidget *deviceMenuWidget =
|
||||
qobject_cast<DeviceMenuWidget *>(tabWidget);
|
||||
qDebug() << "Switching to tab:" << selection.section
|
||||
@@ -349,8 +349,8 @@ void DeviceManagerWidget::onDeviceSelectionChanged(
|
||||
#endif
|
||||
|
||||
case DeviceSelection::Pending:
|
||||
if (m_pendingDeviceWidgets.contains(selection.uuid)) {
|
||||
QWidget *tabWidget = m_pendingDeviceWidgets[selection.uuid].first;
|
||||
if (m_pendingDeviceWidgets.contains(selection.udid)) {
|
||||
QWidget *tabWidget = m_pendingDeviceWidgets[selection.udid].first;
|
||||
if (tabWidget) {
|
||||
m_stackedWidget->setCurrentWidget(tabWidget);
|
||||
// Clear current device since we're viewing pending device
|
||||
|
||||
@@ -28,11 +28,12 @@ DevicePendingWidget::DevicePendingWidget(bool locked, QWidget *parent)
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(5);
|
||||
|
||||
m_label = new QLabel(m_locked ? "Please unlock the screen"
|
||||
: "Please click on trust on the popup",
|
||||
this);
|
||||
|
||||
layout->addWidget(m_label);
|
||||
m_label = new QLabel(
|
||||
m_locked ? "Please unlock the screen and click on trust on the popup"
|
||||
: "Please click on trust on the popup",
|
||||
this);
|
||||
m_label->setWordWrap(true);
|
||||
layout->addWidget(m_label, 0, Qt::AlignCenter);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
|
||||
@@ -393,8 +393,8 @@ void DeviceSidebarWidget::updateSelection()
|
||||
|
||||
// Set selection based on current selection
|
||||
if (m_currentSelection.type == DeviceSelection::Normal &&
|
||||
m_deviceItems.contains(m_currentSelection.uuid)) {
|
||||
m_deviceItems[m_currentSelection.uuid]->setSelected(true);
|
||||
m_deviceItems.contains(m_currentSelection.udid)) {
|
||||
m_deviceItems[m_currentSelection.udid]->setSelected(true);
|
||||
} else if (m_currentSelection.type == DeviceSelection::Recovery &&
|
||||
m_recoveryItems.contains(m_currentSelection.ecid)) {
|
||||
m_recoveryItems[m_currentSelection.ecid]->setSelected(true);
|
||||
|
||||
@@ -113,12 +113,12 @@ signals:
|
||||
struct DeviceSelection {
|
||||
enum Type { Normal, Recovery, Pending };
|
||||
Type type;
|
||||
std::string uuid;
|
||||
std::string udid;
|
||||
uint64_t ecid = 0;
|
||||
QString section = "Info";
|
||||
|
||||
DeviceSelection(const std::string &deviceUuid, const QString &nav = "")
|
||||
: type(Normal), uuid(deviceUuid), section(nav)
|
||||
DeviceSelection(const std::string &deviceUdid, const QString &nav = "")
|
||||
: type(Normal), udid(deviceUdid), section(nav)
|
||||
{
|
||||
}
|
||||
DeviceSelection(uint64_t recoveryEcid) : type(Recovery), ecid(recoveryEcid)
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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 "httpserver.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QNetworkInterface>
|
||||
#include <QRandomGenerator>
|
||||
#include <QUrl>
|
||||
|
||||
HttpServer::HttpServer(QObject *parent)
|
||||
: QObject(parent), server(new QTcpServer(this)), port(8080)
|
||||
{
|
||||
connect(server, &QTcpServer::newConnection, this,
|
||||
&HttpServer::onNewConnection);
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer() { stop(); }
|
||||
|
||||
void HttpServer::start(const QStringList &files)
|
||||
{
|
||||
fileList = files;
|
||||
|
||||
// Generate unique JSON filename
|
||||
QString timestamp =
|
||||
QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");
|
||||
jsonFileName = QString("%1-idescriptor-import.json").arg(timestamp);
|
||||
|
||||
// Try to bind to port 8080, if fails try other ports
|
||||
for (int tryPort = 8080; tryPort <= 8090; ++tryPort) {
|
||||
if (server->listen(QHostAddress::Any, tryPort)) {
|
||||
port = tryPort;
|
||||
emit serverStarted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
emit serverError("Could not bind to any port between 8080-8090");
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
if (server->isListening()) {
|
||||
server->close();
|
||||
}
|
||||
}
|
||||
|
||||
int HttpServer::getPort() const { return port; }
|
||||
|
||||
void HttpServer::onNewConnection()
|
||||
{
|
||||
QTcpSocket *socket = server->nextPendingConnection();
|
||||
connect(socket, &QTcpSocket::readyRead, this, &HttpServer::onReadyRead);
|
||||
connect(socket, &QTcpSocket::disconnected, this,
|
||||
&HttpServer::onDisconnected);
|
||||
}
|
||||
|
||||
void HttpServer::onReadyRead()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
||||
if (!socket)
|
||||
return;
|
||||
|
||||
QByteArray data = socket->readAll();
|
||||
QString request = QString::fromUtf8(data);
|
||||
|
||||
// Parse HTTP request
|
||||
QStringList lines = request.split("\r\n");
|
||||
if (lines.isEmpty())
|
||||
return;
|
||||
|
||||
QString requestLine = lines.first();
|
||||
QStringList parts = requestLine.split(" ");
|
||||
if (parts.size() < 2)
|
||||
return;
|
||||
|
||||
QString method = parts[0];
|
||||
QString path = parts[1];
|
||||
|
||||
if (method == "GET") {
|
||||
handleRequest(socket, path);
|
||||
} else {
|
||||
sendResponse(socket, 405, "text/plain", "Method Not Allowed");
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::onDisconnected()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
||||
if (socket) {
|
||||
socket->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::handleRequest(QTcpSocket *socket, const QString &path)
|
||||
{
|
||||
// Serve JSON manifest
|
||||
if (path == QString("/%1").arg(jsonFileName)) {
|
||||
sendJsonManifest(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve files from /serve/ directory
|
||||
if (path.startsWith("/serve/")) {
|
||||
QString encodedFileName = path.mid(7); // Remove "/serve/"
|
||||
QString fileName = QUrl::fromPercentEncoding(encodedFileName.toUtf8());
|
||||
|
||||
// Find the file in our list
|
||||
QString targetFile;
|
||||
for (const QString &file : fileList) {
|
||||
QFileInfo info(file);
|
||||
if (info.fileName() == fileName) {
|
||||
targetFile = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFile.isEmpty()) {
|
||||
sendFile(socket, targetFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(socket, 404, "text/html",
|
||||
"<html><body><h1>404 Not Found</h1><p>The requested file was "
|
||||
"not found.</p></body></html>");
|
||||
}
|
||||
|
||||
void HttpServer::sendResponse(QTcpSocket *socket, int statusCode,
|
||||
const QString &contentType,
|
||||
const QByteArray &data)
|
||||
{
|
||||
QString statusText;
|
||||
switch (statusCode) {
|
||||
case 200:
|
||||
statusText = "OK";
|
||||
break;
|
||||
case 404:
|
||||
statusText = "Not Found";
|
||||
break;
|
||||
case 405:
|
||||
statusText = "Method Not Allowed";
|
||||
break;
|
||||
case 500:
|
||||
statusText = "Internal Server Error";
|
||||
break;
|
||||
default:
|
||||
statusText = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
QString response =
|
||||
QString("HTTP/1.1 %1 %2\r\n").arg(statusCode).arg(statusText);
|
||||
response += QString("Content-Type: %1\r\n").arg(contentType);
|
||||
response += QString("Content-Length: %1\r\n").arg(data.size());
|
||||
response += "Access-Control-Allow-Origin: *\r\n";
|
||||
response += "Connection: close\r\n";
|
||||
response += "\r\n";
|
||||
|
||||
socket->write(response.toUtf8());
|
||||
socket->write(data);
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
|
||||
void HttpServer::sendFile(QTcpSocket *socket, const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
sendResponse(socket, 404, "text/plain", "File not found");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
QString mimeType = getMimeType(filePath);
|
||||
|
||||
// Emit progress signal
|
||||
QFileInfo info(filePath);
|
||||
emit downloadProgress(info.fileName(), data.size(), data.size());
|
||||
|
||||
sendResponse(socket, 200, mimeType, data);
|
||||
}
|
||||
|
||||
void HttpServer::sendJsonManifest(QTcpSocket *socket)
|
||||
{
|
||||
QString jsonContent = generateJsonManifest();
|
||||
sendResponse(socket, 200, "application/json", jsonContent.toUtf8());
|
||||
}
|
||||
|
||||
QString HttpServer::generateJsonManifest() const
|
||||
{
|
||||
QString serverIP = getLocalIP();
|
||||
|
||||
QJsonObject manifest;
|
||||
QJsonArray items;
|
||||
|
||||
for (const QString &file : fileList) {
|
||||
QFileInfo info(file);
|
||||
QJsonObject item;
|
||||
item["path"] = QString("http://%1:%2/serve/%3")
|
||||
.arg(serverIP)
|
||||
.arg(port)
|
||||
.arg(QString::fromUtf8(
|
||||
QUrl::toPercentEncoding(info.fileName())));
|
||||
items.append(item);
|
||||
}
|
||||
|
||||
manifest["items"] = items;
|
||||
|
||||
QJsonDocument doc(manifest);
|
||||
return doc.toJson();
|
||||
}
|
||||
|
||||
QString HttpServer::getLocalIP() const
|
||||
{
|
||||
foreach (const QNetworkInterface &interface,
|
||||
QNetworkInterface::allInterfaces()) {
|
||||
if (interface.flags().testFlag(QNetworkInterface::IsUp) &&
|
||||
!interface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
|
||||
foreach (const QNetworkAddressEntry &entry,
|
||||
interface.addressEntries()) {
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return entry.ip().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
QString HttpServer::getMimeType(const QString &filePath) const
|
||||
{
|
||||
QMimeDatabase db;
|
||||
QMimeType type = db.mimeTypeForFile(filePath);
|
||||
return type.name();
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SIMPLEHTTPSERVER_H
|
||||
#define SIMPLEHTTPSERVER_H
|
||||
#ifndef HTTPSERVER_H
|
||||
#define HTTPSERVER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
@@ -27,13 +27,13 @@
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
|
||||
class SimpleHttpServer : public QObject
|
||||
class HttpServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SimpleHttpServer(QObject *parent = nullptr);
|
||||
~SimpleHttpServer();
|
||||
explicit HttpServer(QObject *parent = nullptr);
|
||||
~HttpServer();
|
||||
|
||||
void start(const QStringList &files);
|
||||
void stop();
|
||||
@@ -62,11 +62,9 @@ private:
|
||||
const QString &contentType, const QByteArray &data);
|
||||
void sendFile(QTcpSocket *socket, const QString &filePath);
|
||||
void sendJsonManifest(QTcpSocket *socket);
|
||||
void sendHtmlPage(QTcpSocket *socket);
|
||||
QString generateJsonManifest() const;
|
||||
QString generateHtmlPage() const;
|
||||
QString getMimeType(const QString &filePath) const;
|
||||
QString getLocalIP() const;
|
||||
};
|
||||
|
||||
#endif // SIMPLEHTTPSERVER_H
|
||||
#endif // HTTPSERVER_H
|
||||
@@ -25,9 +25,11 @@
|
||||
#include <QMainWindow>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QSlider>
|
||||
#include <QSplitter>
|
||||
#include <QSplitterHandle>
|
||||
#include <QStyleOption>
|
||||
#include <QWheelEvent>
|
||||
#include <QWidget>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
@@ -369,3 +371,28 @@ protected:
|
||||
QLabel::mouseReleaseEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
class ZSlider : public QSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ZSlider(QWidget *parent = nullptr) : QSlider(parent) {}
|
||||
explicit ZSlider(Qt::Orientation orientation, QWidget *parent = nullptr)
|
||||
: QSlider(orientation, parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
// Set the value to the position of the click
|
||||
int value = QStyle::sliderValueFromPosition(
|
||||
minimum(), maximum(), event->pos().x(), width());
|
||||
setValue(value);
|
||||
}
|
||||
// Let the base class handle the rest of the event
|
||||
QSlider::mousePressEvent(event);
|
||||
}
|
||||
};
|
||||
+33
-11
@@ -80,8 +80,9 @@ void handleCallback(const idevice_event_t *event, void *userData)
|
||||
|
||||
case IDEVICE_DEVICE_PAIRED: {
|
||||
if (event->conn_type == CONNECTION_NETWORK) {
|
||||
warn("Network devices are not supported but a network device was "
|
||||
"received in event listener. Please report this issue.");
|
||||
qDebug()
|
||||
<< "Network devices are not supported but a network device was "
|
||||
"received in event listener. Please report this issue.";
|
||||
return;
|
||||
}
|
||||
qDebug() << "Device paired: " << QString::fromUtf8(event->udid);
|
||||
@@ -241,7 +242,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
}
|
||||
qDebug() << "Subscribed to device events successfully.";
|
||||
createMenus();
|
||||
// Example usage with customization
|
||||
|
||||
UpdateProcedure updateProcedure;
|
||||
bool packageManagerManaged = false;
|
||||
@@ -256,15 +256,27 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
struct UpdateProcedure {
|
||||
bool openFile;
|
||||
bool openFileDir;
|
||||
bool quitApp;
|
||||
QString boxInformativeText;
|
||||
QString boxText;
|
||||
};
|
||||
*/
|
||||
switch (ZUpdater::detectPlatform()) {
|
||||
// todo: adjust for portable
|
||||
case Platform::Windows:
|
||||
updateProcedure = UpdateProcedure{
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
"The application will now quit to install the update.",
|
||||
"Do you want to install the downloaded update now?",
|
||||
!isPortable,
|
||||
isPortable,
|
||||
!isPortable,
|
||||
isPortable ? "New portable version downloaded, app location will "
|
||||
"be shown after this message"
|
||||
: "The application will now quit to install the update.",
|
||||
isPortable ? "New portable version downloaded"
|
||||
: "Do you want to install the downloaded update now?",
|
||||
};
|
||||
break;
|
||||
// todo: adjust for pkg managers
|
||||
@@ -296,13 +308,23 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: fix repo name
|
||||
m_updater =
|
||||
new ZUpdater("uncor3/libtest", APP_VERSION, "iDescriptor",
|
||||
updateProcedure, isPortable, packageManagerManaged, this);
|
||||
qDebug() << "Checking for updates...";
|
||||
#if defined(PACKAGE_MANAGER_MANAGED) && defined(__linux__)
|
||||
m_updater->setPackageManagerManagedMessage(
|
||||
QString(
|
||||
"You seem to have installed iDescriptor using a package manager. "
|
||||
"Please use %1 to update it.")
|
||||
.arg(PACKAGE_MANAGER_HINT));
|
||||
#endif
|
||||
|
||||
SettingsManager::sharedInstance()->doIfEnabled(
|
||||
SettingsManager::Setting::AutoCheckUpdates,
|
||||
[this]() { m_updater->checkForUpdates(); });
|
||||
SettingsManager::Setting::AutoCheckUpdates, [this]() {
|
||||
qDebug() << "Checking for updates...";
|
||||
m_updater->checkForUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::createMenus()
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
#include <QWheelEvent>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtGlobal>
|
||||
#include "appcontext.h"
|
||||
#include "iDescriptor-ui.h"
|
||||
|
||||
|
||||
|
||||
MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device,
|
||||
afc_client_t afcClient,
|
||||
@@ -72,6 +76,11 @@ MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device,
|
||||
|
||||
setupUI();
|
||||
loadMedia();
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [this](const std::string &udid) {
|
||||
if (udid == m_device->udid) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MediaPreviewDialog::~MediaPreviewDialog()
|
||||
@@ -481,16 +490,16 @@ void MediaPreviewDialog::setupVideoControls()
|
||||
&MediaPreviewDialog::onRepeatToggled);
|
||||
|
||||
// Timeline slider
|
||||
m_timelineSlider = new QSlider(Qt::Horizontal, this);
|
||||
m_timelineSlider = new ZSlider(Qt::Horizontal, this);
|
||||
m_timelineSlider->setMinimum(0);
|
||||
m_timelineSlider->setMaximum(1000);
|
||||
m_timelineSlider->setValue(0);
|
||||
m_timelineSlider->setToolTip("Seek timeline");
|
||||
connect(m_timelineSlider, &QSlider::valueChanged, this,
|
||||
connect(m_timelineSlider, &ZSlider::valueChanged, this,
|
||||
&MediaPreviewDialog::onTimelineValueChanged);
|
||||
connect(m_timelineSlider, &QSlider::sliderPressed, this,
|
||||
connect(m_timelineSlider, &ZSlider::sliderPressed, this,
|
||||
&MediaPreviewDialog::onTimelinePressed);
|
||||
connect(m_timelineSlider, &QSlider::sliderReleased, this,
|
||||
connect(m_timelineSlider, &ZSlider::sliderReleased, this,
|
||||
&MediaPreviewDialog::onTimelineReleased);
|
||||
|
||||
// Time label
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#ifndef MEDIAPREVIEWDIALOG_H
|
||||
#define MEDIAPREVIEWDIALOG_H
|
||||
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDialog>
|
||||
@@ -120,7 +121,7 @@ private:
|
||||
QPushButton *m_playPauseBtn;
|
||||
QPushButton *m_stopBtn;
|
||||
QPushButton *m_repeatBtn;
|
||||
QSlider *m_timelineSlider;
|
||||
ZSlider *m_timelineSlider;
|
||||
QLabel *m_timeLabel;
|
||||
QSlider *m_volumeSlider;
|
||||
QLabel *m_volumeLabel;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "photoimportdialog.h"
|
||||
#include "simplehttpserver.h"
|
||||
#include "httpserver.h"
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
@@ -117,12 +117,12 @@ void PhotoImportDialog::init()
|
||||
progressBar->setRange(0, 0); // Indeterminate progress
|
||||
|
||||
// Create and start HTTP server
|
||||
m_httpServer = new SimpleHttpServer(this);
|
||||
connect(m_httpServer, &SimpleHttpServer::serverStarted, this,
|
||||
m_httpServer = new HttpServer(this);
|
||||
connect(m_httpServer, &HttpServer::serverStarted, this,
|
||||
&PhotoImportDialog::onServerStarted);
|
||||
connect(m_httpServer, &SimpleHttpServer::serverError, this,
|
||||
connect(m_httpServer, &HttpServer::serverError, this,
|
||||
&PhotoImportDialog::onServerError);
|
||||
connect(m_httpServer, &SimpleHttpServer::downloadProgress, this,
|
||||
connect(m_httpServer, &HttpServer::downloadProgress, this,
|
||||
&PhotoImportDialog::onDownloadProgress);
|
||||
|
||||
m_httpServer->start(selectedFiles);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#ifndef PHOTOIMPORTDIALOG_H
|
||||
#define PHOTOIMPORTDIALOG_H
|
||||
|
||||
#include "httpserver.h"
|
||||
#include <QDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -29,8 +30,6 @@
|
||||
#include <QStringList>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class SimpleHttpServer;
|
||||
|
||||
class PhotoImportDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -59,7 +58,7 @@ private:
|
||||
QProgressBar *progressBar;
|
||||
QLabel *progressLabel;
|
||||
|
||||
SimpleHttpServer *m_httpServer;
|
||||
HttpServer *m_httpServer;
|
||||
|
||||
void setupUI();
|
||||
void generateQRCode(const QString &url);
|
||||
|
||||
@@ -88,12 +88,9 @@ void SettingsWidget::setupUI()
|
||||
themeLayout->addWidget(new QLabel("Theme:"));
|
||||
m_themeCombo = new QComboBox();
|
||||
|
||||
/* FIXME: Theme control on Linux needs to be implemented */
|
||||
#ifdef __linux__
|
||||
/* FIXME: Theme control needs to be implemented */
|
||||
m_themeCombo->addItems({"System Default"});
|
||||
#else
|
||||
m_themeCombo->addItems({"System Default", "Light", "Dark"});
|
||||
#endif
|
||||
// m_themeCombo->addItems({"System Default", "Light", "Dark"});
|
||||
|
||||
themeLayout->addWidget(m_themeCombo);
|
||||
themeLayout->addStretch();
|
||||
|
||||
@@ -1,437 +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 "simplehttpserver.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QNetworkInterface>
|
||||
#include <QRandomGenerator>
|
||||
#include <QUrl>
|
||||
|
||||
SimpleHttpServer::SimpleHttpServer(QObject *parent)
|
||||
: QObject(parent), server(new QTcpServer(this)), port(8080)
|
||||
{
|
||||
connect(server, &QTcpServer::newConnection, this,
|
||||
&SimpleHttpServer::onNewConnection);
|
||||
}
|
||||
|
||||
SimpleHttpServer::~SimpleHttpServer() { stop(); }
|
||||
|
||||
void SimpleHttpServer::start(const QStringList &files)
|
||||
{
|
||||
fileList = files;
|
||||
|
||||
// Generate unique JSON filename
|
||||
QString timestamp =
|
||||
QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");
|
||||
jsonFileName = QString("%1-idescriptor-import.json").arg(timestamp);
|
||||
|
||||
// Try to bind to port 8080, if fails try other ports
|
||||
for (int tryPort = 8080; tryPort <= 8090; ++tryPort) {
|
||||
if (server->listen(QHostAddress::Any, tryPort)) {
|
||||
port = tryPort;
|
||||
emit serverStarted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
emit serverError("Could not bind to any port between 8080-8090");
|
||||
}
|
||||
|
||||
void SimpleHttpServer::stop()
|
||||
{
|
||||
if (server->isListening()) {
|
||||
server->close();
|
||||
}
|
||||
}
|
||||
|
||||
int SimpleHttpServer::getPort() const { return port; }
|
||||
|
||||
void SimpleHttpServer::onNewConnection()
|
||||
{
|
||||
QTcpSocket *socket = server->nextPendingConnection();
|
||||
connect(socket, &QTcpSocket::readyRead, this,
|
||||
&SimpleHttpServer::onReadyRead);
|
||||
connect(socket, &QTcpSocket::disconnected, this,
|
||||
&SimpleHttpServer::onDisconnected);
|
||||
}
|
||||
|
||||
void SimpleHttpServer::onReadyRead()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
||||
if (!socket)
|
||||
return;
|
||||
|
||||
QByteArray data = socket->readAll();
|
||||
QString request = QString::fromUtf8(data);
|
||||
|
||||
// Parse HTTP request
|
||||
QStringList lines = request.split("\r\n");
|
||||
if (lines.isEmpty())
|
||||
return;
|
||||
|
||||
QString requestLine = lines.first();
|
||||
QStringList parts = requestLine.split(" ");
|
||||
if (parts.size() < 2)
|
||||
return;
|
||||
|
||||
QString method = parts[0];
|
||||
QString path = parts[1];
|
||||
|
||||
if (method == "GET") {
|
||||
handleRequest(socket, path);
|
||||
} else {
|
||||
sendResponse(socket, 405, "text/plain", "Method Not Allowed");
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleHttpServer::onDisconnected()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
||||
if (socket) {
|
||||
socket->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleHttpServer::handleRequest(QTcpSocket *socket, const QString &path)
|
||||
{
|
||||
// Serve HTML page at root
|
||||
if (path == "/" || path == "/index.html") {
|
||||
sendHtmlPage(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve JSON manifest
|
||||
if (path == QString("/%1").arg(jsonFileName)) {
|
||||
sendJsonManifest(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (path == "/import.shortcut") {
|
||||
// Generate import shortcut file
|
||||
QString shortcutPath =
|
||||
QString("%1/resources/import.shortcut").arg(SOURCE_DIR);
|
||||
QFile shortcutFile(shortcutPath);
|
||||
if (shortcutFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray data = shortcutFile.readAll();
|
||||
sendResponse(socket, 200, "application/octet-stream", data);
|
||||
return;
|
||||
} else {
|
||||
sendResponse(socket, 404, "text/plain", "Shortcut file not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Serve files from /serve/ directory
|
||||
if (path.startsWith("/serve/")) {
|
||||
QString fileName = path.mid(7); // Remove "/serve/"
|
||||
|
||||
// Find the file in our list
|
||||
QString targetFile;
|
||||
for (const QString &file : fileList) {
|
||||
QFileInfo info(file);
|
||||
if (info.fileName() == fileName) {
|
||||
targetFile = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFile.isEmpty()) {
|
||||
sendFile(socket, targetFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(socket, 404, "text/html",
|
||||
"<html><body><h1>404 Not Found</h1><p>The requested file was "
|
||||
"not found.</p></body></html>");
|
||||
}
|
||||
|
||||
void SimpleHttpServer::sendResponse(QTcpSocket *socket, int statusCode,
|
||||
const QString &contentType,
|
||||
const QByteArray &data)
|
||||
{
|
||||
QString statusText;
|
||||
switch (statusCode) {
|
||||
case 200:
|
||||
statusText = "OK";
|
||||
break;
|
||||
case 404:
|
||||
statusText = "Not Found";
|
||||
break;
|
||||
case 405:
|
||||
statusText = "Method Not Allowed";
|
||||
break;
|
||||
case 500:
|
||||
statusText = "Internal Server Error";
|
||||
break;
|
||||
default:
|
||||
statusText = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
QString response =
|
||||
QString("HTTP/1.1 %1 %2\r\n").arg(statusCode).arg(statusText);
|
||||
response += QString("Content-Type: %1\r\n").arg(contentType);
|
||||
response += QString("Content-Length: %1\r\n").arg(data.size());
|
||||
response += "Access-Control-Allow-Origin: *\r\n";
|
||||
response += "Connection: close\r\n";
|
||||
response += "\r\n";
|
||||
|
||||
socket->write(response.toUtf8());
|
||||
socket->write(data);
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
|
||||
void SimpleHttpServer::sendFile(QTcpSocket *socket, const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
sendResponse(socket, 404, "text/plain", "File not found");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
QString mimeType = getMimeType(filePath);
|
||||
|
||||
// Emit progress signal
|
||||
QFileInfo info(filePath);
|
||||
emit downloadProgress(info.fileName(), data.size(), data.size());
|
||||
|
||||
sendResponse(socket, 200, mimeType, data);
|
||||
}
|
||||
|
||||
void SimpleHttpServer::sendJsonManifest(QTcpSocket *socket)
|
||||
{
|
||||
QString jsonContent = generateJsonManifest();
|
||||
sendResponse(socket, 200, "application/json", jsonContent.toUtf8());
|
||||
}
|
||||
|
||||
void SimpleHttpServer::sendHtmlPage(QTcpSocket *socket)
|
||||
{
|
||||
QString htmlContent = generateHtmlPage();
|
||||
sendResponse(socket, 200, "text/html", htmlContent.toUtf8());
|
||||
}
|
||||
|
||||
QString SimpleHttpServer::generateJsonManifest() const
|
||||
{
|
||||
QString serverIP = getLocalIP();
|
||||
|
||||
QJsonObject manifest;
|
||||
QJsonArray items;
|
||||
|
||||
for (const QString &file : fileList) {
|
||||
QFileInfo info(file);
|
||||
QJsonObject item;
|
||||
item["path"] = QString("http://%1:%2/serve/%3")
|
||||
.arg(serverIP)
|
||||
.arg(port)
|
||||
.arg(info.fileName());
|
||||
items.append(item);
|
||||
}
|
||||
|
||||
manifest["items"] = items;
|
||||
|
||||
QJsonDocument doc(manifest);
|
||||
return doc.toJson();
|
||||
}
|
||||
|
||||
QString SimpleHttpServer::generateHtmlPage() const
|
||||
{
|
||||
QString serverIP = getLocalIP();
|
||||
QString shortcutPath =
|
||||
QString("shortcuts://import-shortcut?url=http://%1:%2/import.shortcut")
|
||||
.arg(serverIP)
|
||||
.arg(port);
|
||||
|
||||
QString html = QString(R"(
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>iDescriptor Photo Import</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f7;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #1d1d1f;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.server-info {
|
||||
background: #f6f6f6;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
background: #007AFF;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
margin: 10px 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.button:hover {
|
||||
background: #0056CC;
|
||||
}
|
||||
.button.secondary {
|
||||
background: #34C759;
|
||||
}
|
||||
.button.secondary:hover {
|
||||
background: #28A745;
|
||||
}
|
||||
.instructions {
|
||||
background: #e8f4fd;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.file-list {
|
||||
background: #f8f8f8;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.file-count {
|
||||
font-weight: bold;
|
||||
color: #007AFF;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📱 iDescriptor Photo Import</h1>
|
||||
|
||||
<div class="server-info">
|
||||
<strong>Server Address:</strong> %1:%2<br>
|
||||
<strong>JSON Manifest:</strong> %3
|
||||
</div>
|
||||
|
||||
<div class="file-list">
|
||||
<p class="file-count">Ready to serve %4 files</p>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>Instructions:</h3>
|
||||
<ol>
|
||||
<li>Copy the server address below</li>
|
||||
<li>Download the shortcut to your iOS device</li>
|
||||
<li>Run the shortcut and paste the server address when prompted</li>
|
||||
<li>The shortcut will automatically import all photos to your Gallery</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<a href="%5" class="button secondary">Download Shortcut</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// function copyAddress() {
|
||||
// const address = '%1:%2';
|
||||
// window.navigator.clipboard.writeText(address).then(function() {
|
||||
// alert('Server address copied to clipboard!');
|
||||
// }).catch(function(err) {
|
||||
// prompt('Copy this address:', address);
|
||||
// });
|
||||
// }
|
||||
|
||||
// function unsecuredCopyToClipboard(text) {
|
||||
// const textArea = document.createElement("textarea");
|
||||
// textArea.value = text;
|
||||
// document.body.appendChild(textArea);
|
||||
// textArea.focus();
|
||||
// textArea.select();
|
||||
// try {
|
||||
// document.execCommand('copy');
|
||||
// } catch (err) {
|
||||
// console.error('Unable to copy to clipboard', err);
|
||||
// }
|
||||
// document.body.removeChild(textArea);
|
||||
// }
|
||||
|
||||
// unsecuredCopyToClipboard('%1:%2');
|
||||
// document.querySelector('a').click();
|
||||
}
|
||||
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)")
|
||||
.arg(serverIP)
|
||||
.arg(port)
|
||||
.arg(jsonFileName)
|
||||
.arg(fileList.size())
|
||||
.arg(shortcutPath);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
QString SimpleHttpServer::getLocalIP() const
|
||||
{
|
||||
foreach (const QNetworkInterface &interface,
|
||||
QNetworkInterface::allInterfaces()) {
|
||||
if (interface.flags().testFlag(QNetworkInterface::IsUp) &&
|
||||
!interface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
|
||||
foreach (const QNetworkAddressEntry &entry,
|
||||
interface.addressEntries()) {
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return entry.ip().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
QString SimpleHttpServer::getMimeType(const QString &filePath) const
|
||||
{
|
||||
QMimeDatabase db;
|
||||
QMimeType type = db.mimeTypeForFile(filePath);
|
||||
return type.name();
|
||||
}
|
||||
+19
-33
@@ -387,16 +387,16 @@ void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection)
|
||||
{
|
||||
if (selection.type == DeviceSelection::Normal) {
|
||||
int index =
|
||||
m_deviceCombo->findData(QString::fromStdString(selection.uuid));
|
||||
m_deviceCombo->findData(QString::fromStdString(selection.udid));
|
||||
if (index != -1) {
|
||||
// Block signals to prevent recursive calls when we update the UI
|
||||
m_deviceCombo->blockSignals(true);
|
||||
m_deviceCombo->setCurrentIndex(index);
|
||||
m_deviceCombo->blockSignals(false);
|
||||
|
||||
m_uuid = selection.uuid;
|
||||
m_uuid = selection.udid;
|
||||
m_currentDevice =
|
||||
AppContext::sharedInstance()->getDevice(selection.uuid);
|
||||
AppContext::sharedInstance()->getDevice(selection.udid);
|
||||
}
|
||||
} else {
|
||||
// Handle recovery, pending, or no device selection
|
||||
@@ -442,37 +442,23 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool)
|
||||
msgBox.exec();
|
||||
} break;
|
||||
case iDescriptorTool::MountDevImage: {
|
||||
GetMountedImageResult result =
|
||||
DevDiskManager::sharedInstance()->getMountedImage(
|
||||
m_currentDevice->udid.c_str());
|
||||
|
||||
if (!result.success) {
|
||||
QMessageBox::warning(this, "Failure", result.message.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success && result.sig.empty()) {
|
||||
bool devImgSuccess =
|
||||
DevDiskManager::sharedInstance()->mountCompatibleImage(
|
||||
m_currentDevice);
|
||||
if (!devImgSuccess) {
|
||||
QMessageBox::warning(
|
||||
this, "Failure",
|
||||
"Failed to mount developer image on device. "
|
||||
"Try with a different cable.");
|
||||
qDebug()
|
||||
<< "Failed to mount developer image on device. Cannot set "
|
||||
"location.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QMessageBox::information(
|
||||
this, "Success",
|
||||
QString("There is already a developer image mounted on device %1.")
|
||||
.arg(QString::fromStdString(
|
||||
m_currentDevice->deviceInfo.productType)));
|
||||
DevDiskImageHelper *devDiskImageHelper =
|
||||
new DevDiskImageHelper(m_currentDevice, this);
|
||||
|
||||
connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted,
|
||||
this, [this, devDiskImageHelper](bool success) {
|
||||
devDiskImageHelper->deleteLater();
|
||||
if (success) {
|
||||
QMessageBox::information(
|
||||
this, "Success",
|
||||
"Developer image mounted successfully.");
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this, "Failure",
|
||||
"Failed to mount developer image.");
|
||||
}
|
||||
});
|
||||
devDiskImageHelper->start();
|
||||
} break;
|
||||
case iDescriptorTool::VirtualLocation: {
|
||||
// Handle virtual location functionality
|
||||
|
||||
@@ -38,6 +38,7 @@ WirelessGalleryImportWidget::WirelessGalleryImportWidget(QWidget *parent)
|
||||
{
|
||||
setupUI();
|
||||
setMinimumSize(800, 600);
|
||||
setWindowTitle("Wireless Gallery Import - iDescriptor");
|
||||
QTimer::singleShot(100, this,
|
||||
&WirelessGalleryImportWidget::setupTutorialVideo);
|
||||
}
|
||||
@@ -131,7 +132,8 @@ void WirelessGalleryImportWidget::setupTutorialVideo()
|
||||
QSizePolicy::Expanding);
|
||||
|
||||
m_tutorialPlayer->setVideoOutput(m_tutorialVideoWidget);
|
||||
m_tutorialPlayer->setSource(QUrl("qrc:/resources/airplayer-tutorial.mp4"));
|
||||
m_tutorialPlayer->setSource(
|
||||
QUrl("qrc:/resources/wireless-gallery-import.mp4"));
|
||||
m_tutorialVideoWidget->setAspectRatioMode(
|
||||
Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user