mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
433 lines
15 KiB
C++
433 lines
15 KiB
C++
/*
|
|
* 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 "sshterminaltool.h"
|
|
#include "appcontext.h"
|
|
#include "responsiveqlabel.h"
|
|
#include "sshterminalwidget.h"
|
|
|
|
SSHTerminalTool::SSHTerminalTool(QWidget *parent)
|
|
: Tool{parent}, m_selectedUniq(QString(""), false)
|
|
{
|
|
setWindowTitle("SSH Terminal - iDescriptor");
|
|
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
|
mainLayout->setContentsMargins(2, 2, 2, 2);
|
|
mainLayout->setSpacing(2);
|
|
|
|
// Create responsive image label
|
|
ResponsiveQLabel *deviceImageLabel = new ResponsiveQLabel(this);
|
|
deviceImageLabel->setPixmap(QPixmap(":/resources/iphone.png"));
|
|
deviceImageLabel->setMinimumWidth(200);
|
|
deviceImageLabel->setSizePolicy(QSizePolicy::Ignored,
|
|
QSizePolicy::Expanding);
|
|
deviceImageLabel->setStyleSheet("background: transparent; border: none;");
|
|
|
|
mainLayout->addWidget(deviceImageLabel, 1);
|
|
|
|
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
|
|
&SSHTerminalTool::On_iDeviceAdded);
|
|
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
|
|
&SSHTerminalTool::On_iDeviceRemoved);
|
|
|
|
connect(NetworkDeviceProvider::sharedInstance(),
|
|
&NetworkDeviceProvider::deviceAdded, this,
|
|
&SSHTerminalTool::onWirelessDeviceAdded);
|
|
connect(NetworkDeviceProvider::sharedInstance(),
|
|
&NetworkDeviceProvider::deviceRemoved, this,
|
|
&SSHTerminalTool::onWirelessDeviceRemoved);
|
|
|
|
QWidget *rightContainer = new QWidget();
|
|
rightContainer->setSizePolicy(QSizePolicy::Expanding,
|
|
QSizePolicy::Expanding);
|
|
rightContainer->setMinimumWidth(400);
|
|
QVBoxLayout *rightLayout = new QVBoxLayout(rightContainer);
|
|
rightLayout->setContentsMargins(15, 15, 15, 15);
|
|
rightLayout->setSpacing(10);
|
|
|
|
setupDeviceSelectionUI(rightLayout);
|
|
|
|
mainLayout->addWidget(rightContainer, 3);
|
|
|
|
updateDeviceList();
|
|
}
|
|
|
|
void SSHTerminalTool::setupDeviceSelectionUI(QVBoxLayout *layout)
|
|
{
|
|
// Create scroll area for device selection
|
|
QScrollArea *scrollArea = new QScrollArea();
|
|
scrollArea->setWidgetResizable(true);
|
|
scrollArea->setMinimumHeight(200);
|
|
scrollArea->setMaximumHeight(300);
|
|
scrollArea->setObjectName("devicescrollArea");
|
|
|
|
// scrollArea->setStyleSheet("QWidget#devicescrollArea {border: none;}");
|
|
QWidget *scrollContent = new QWidget();
|
|
m_deviceLayout = new QVBoxLayout(scrollContent);
|
|
m_deviceLayout->setContentsMargins(5, 5, 5, 5);
|
|
m_deviceLayout->setSpacing(10);
|
|
|
|
// Button group for device selection
|
|
m_deviceButtonGroup = new QButtonGroup(this);
|
|
connect(m_deviceButtonGroup,
|
|
QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked),
|
|
this, &SSHTerminalTool::onDeviceSelected);
|
|
|
|
// Wired devices group
|
|
m_connectedDevicesGroup = new QGroupBox("Connected Devices");
|
|
m_connectedDevicesLayout = new QVBoxLayout(m_connectedDevicesGroup);
|
|
m_deviceLayout->addWidget(m_connectedDevicesGroup);
|
|
|
|
// Wireless devices group
|
|
m_networkDevicesGroup = new QGroupBox("Network (All) Devices");
|
|
m_networkDevicesLayout = new QVBoxLayout(m_networkDevicesGroup);
|
|
m_deviceLayout->addWidget(m_networkDevicesGroup);
|
|
|
|
scrollArea->setWidget(scrollContent);
|
|
layout->addWidget(scrollArea);
|
|
|
|
// Info and connect button
|
|
m_infoLabel = new QLabel("Select a device to connect");
|
|
layout->addWidget(m_infoLabel);
|
|
|
|
// support
|
|
m_connectButton = new QPushButton("Choose a device");
|
|
m_connectButton->setMaximumWidth(m_connectButton->sizeHint().width());
|
|
m_connectButton->setEnabled(false);
|
|
connect(m_connectButton, &QPushButton::clicked, this,
|
|
&SSHTerminalTool::onOpenSSHTerminal);
|
|
layout->addWidget(m_connectButton, 0, Qt::AlignCenter);
|
|
|
|
// Manual IP connection group
|
|
m_manualConnectionGroup = new QGroupBox("Manual IP connection");
|
|
QVBoxLayout *manualLayout = new QVBoxLayout(m_manualConnectionGroup);
|
|
|
|
QHBoxLayout *ipLayout = new QHBoxLayout();
|
|
QLabel *ipLabel = new QLabel("Device IP:");
|
|
m_manualIpEdit = new QLineEdit();
|
|
m_manualIpEdit->setPlaceholderText("e.g. 192.168.1.10");
|
|
|
|
ipLayout->addWidget(ipLabel);
|
|
ipLayout->addWidget(m_manualIpEdit);
|
|
|
|
m_manualConnectButton = new QPushButton("Connect via IP");
|
|
connect(m_manualConnectButton, &QPushButton::clicked, this,
|
|
&SSHTerminalTool::onManualConnectClicked);
|
|
|
|
manualLayout->addLayout(ipLayout);
|
|
manualLayout->addWidget(m_manualConnectButton, 0, Qt::AlignLeft);
|
|
|
|
layout->addWidget(m_manualConnectionGroup);
|
|
}
|
|
|
|
void SSHTerminalTool::updateDeviceList()
|
|
{
|
|
// Clear existing devices
|
|
clearDeviceButtons();
|
|
|
|
// Add wired devices
|
|
QList<std::shared_ptr<iDescriptorDevice>> wiredDevices =
|
|
AppContext::sharedInstance()->getAllDevices();
|
|
for (const std::shared_ptr<iDescriptorDevice> &device : wiredDevices) {
|
|
addDevice(device);
|
|
}
|
|
|
|
// Add wireless devices
|
|
QMap<QString, NetworkDevice> wirelessDevices =
|
|
NetworkDeviceProvider::sharedInstance()->getNetworkDevices();
|
|
for (const NetworkDevice &device : wirelessDevices) {
|
|
addWirelessDevice(device);
|
|
}
|
|
}
|
|
|
|
void SSHTerminalTool::clearDeviceButtons()
|
|
{
|
|
// Remove all buttons from button group and layouts
|
|
for (QAbstractButton *button : m_deviceButtonGroup->buttons()) {
|
|
m_deviceButtonGroup->removeButton(button);
|
|
// button->deleteLater();
|
|
}
|
|
|
|
// // Clear layouts
|
|
// QLayoutItem *item;
|
|
// while ((item = m_deviceLayout->takeAt(0)) != nullptr) {
|
|
// delete item->widget();
|
|
// delete item;
|
|
// }
|
|
// while ((item = m_networkDevicesLayout->takeAt(0)) != nullptr) {
|
|
// delete item->widget();
|
|
// delete item;
|
|
// }
|
|
|
|
auto clearLayout = [](QLayout *layout) {
|
|
QLayoutItem *item;
|
|
while ((item = layout->takeAt(0)) != nullptr) {
|
|
if (QWidget *w = item->widget()) {
|
|
w->deleteLater();
|
|
}
|
|
delete item;
|
|
}
|
|
};
|
|
|
|
// Only clear the inner layouts, keep the group boxes and m_deviceLayout
|
|
clearLayout(m_connectedDevicesLayout);
|
|
clearLayout(m_networkDevicesLayout);
|
|
}
|
|
|
|
// FIXME: should not add already exiting devices
|
|
void SSHTerminalTool::addDevice(const std::shared_ptr<iDescriptorDevice> device)
|
|
{
|
|
QString deviceName = QString::fromStdString(device->deviceInfo.deviceName);
|
|
QString udid = device->udid;
|
|
QString displayText = QString("%1\n%2").arg(deviceName, udid);
|
|
|
|
QRadioButton *radioButton = new QRadioButton(displayText);
|
|
if (device->deviceInfo.isWireless) {
|
|
radioButton->setProperty(
|
|
"deviceName",
|
|
QString::fromStdString(device->deviceInfo.deviceName));
|
|
radioButton->setProperty(
|
|
"uniq", QString::fromStdString(device->deviceInfo.wifiMacAddress));
|
|
radioButton->setProperty(
|
|
"ip", QString::fromStdString(device->deviceInfo.ipAddress));
|
|
// workaround
|
|
radioButton->setProperty("udid", device->udid);
|
|
|
|
radioButton->setProperty("deviceType", "wireless");
|
|
} else {
|
|
radioButton->setProperty("uniq", udid);
|
|
radioButton->setProperty("deviceType", "wired");
|
|
}
|
|
|
|
m_deviceButtonGroup->addButton(radioButton);
|
|
m_connectedDevicesLayout->addWidget(radioButton);
|
|
}
|
|
|
|
void SSHTerminalTool::addWirelessDevice(const NetworkDevice &device)
|
|
{
|
|
QString displayText = QString("%1\n%2").arg(device.name, device.address);
|
|
|
|
QRadioButton *radioButton = new QRadioButton(displayText);
|
|
radioButton->setProperty("deviceName", device.name);
|
|
radioButton->setProperty("uniq", device.macAddress);
|
|
radioButton->setProperty("ip", device.address);
|
|
radioButton->setProperty("deviceType", "wireless");
|
|
|
|
m_deviceButtonGroup->addButton(radioButton);
|
|
m_networkDevicesLayout->addWidget(radioButton);
|
|
}
|
|
// m_selectedUniq
|
|
void SSHTerminalTool::On_iDeviceAdded(
|
|
const std::shared_ptr<iDescriptorDevice> device)
|
|
{
|
|
addDevice(device);
|
|
}
|
|
|
|
void SSHTerminalTool::On_iDeviceRemoved(const QString &udid)
|
|
{
|
|
// Find and remove the corresponding radio button
|
|
for (QAbstractButton *button : m_deviceButtonGroup->buttons()) {
|
|
if (button->property("udid").toString() == udid) {
|
|
m_deviceButtonGroup->removeButton(button);
|
|
button->deleteLater();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reset selection if this device was selected
|
|
// if (m_selectedUniq == qudid) {
|
|
// resetSelection();
|
|
// }
|
|
}
|
|
|
|
void SSHTerminalTool::onWirelessDeviceAdded(const NetworkDevice &device)
|
|
{
|
|
addWirelessDevice(device);
|
|
}
|
|
|
|
void SSHTerminalTool::onWirelessDeviceRemoved(const QString &deviceName)
|
|
{
|
|
// Find and remove the corresponding radio button
|
|
for (QAbstractButton *button : m_deviceButtonGroup->buttons()) {
|
|
if (button->property("deviceType").toString() == "wireless" &&
|
|
button->property("deviceName").toString() == deviceName) {
|
|
m_deviceButtonGroup->removeButton(button);
|
|
button->deleteLater();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// // Reset selection if this device was selected
|
|
// if (m_selectedDeviceType == DeviceType::Wireless &&
|
|
// m_selectedNetworkDevice.name == deviceName) {
|
|
// resetSelection();
|
|
// }
|
|
}
|
|
|
|
void SSHTerminalTool::onDeviceSelected(QAbstractButton *button)
|
|
{
|
|
QString deviceType = button->property("deviceType").toString();
|
|
QString ip = button->property("ip").toString();
|
|
bool isWireless = (deviceType == "wireless");
|
|
|
|
m_selectedUniq =
|
|
iDescriptor::Uniq(button->property("uniq").toString(), isWireless);
|
|
|
|
// required for wireless devices
|
|
if (!ip.isEmpty()) {
|
|
m_selectedUniq.setIP(ip);
|
|
}
|
|
|
|
auto selectedDevice =
|
|
AppContext::sharedInstance()->getDevice(m_selectedUniq);
|
|
|
|
if (!selectedDevice) {
|
|
QString udid = button->property("udid").toString();
|
|
if (!udid.isEmpty()) {
|
|
auto selectedDevice = AppContext::sharedInstance()->getDevice(udid);
|
|
if (selectedDevice) {
|
|
if (selectedDevice->deviceInfo.jailbroken) {
|
|
m_infoLabel->setText(
|
|
"Device selected (detected as jailbroken)");
|
|
} else {
|
|
m_infoLabel->setText(
|
|
"Device selected (detected as non-jailbroken)");
|
|
}
|
|
}
|
|
} else {
|
|
// device may get removed just as user clicks,
|
|
// so we'll treat it as unknown
|
|
m_infoLabel->setText(
|
|
"Network device selected (jailbreak status unknown)");
|
|
}
|
|
} else {
|
|
if (selectedDevice->deviceInfo.jailbroken) {
|
|
m_infoLabel->setText("Device selected (detected as jailbroken)");
|
|
} else {
|
|
m_infoLabel->setText(
|
|
"Device selected (detected as non-jailbroken)");
|
|
}
|
|
}
|
|
|
|
m_connectButton->setEnabled(true);
|
|
m_connectButton->setText("Connect");
|
|
}
|
|
|
|
void SSHTerminalTool::onManualConnectClicked()
|
|
{
|
|
QString ip = m_manualIpEdit->text().trimmed();
|
|
if (ip.isEmpty()) {
|
|
m_infoLabel->setText("Please enter an IP address for manual connect");
|
|
QMessageBox::warning(this, "Missing IP Address",
|
|
"Please enter a device IP address.");
|
|
return;
|
|
}
|
|
|
|
// Treat manual connection as wireless with unknown jailbreak status
|
|
m_selectedUniq.set(ip, true); // mark as "wireless"-style
|
|
m_selectedUniq.setIP(ip);
|
|
|
|
m_infoLabel->setText(
|
|
QString("Manual IP %1 selected (jailbreak status unknown)").arg(ip));
|
|
|
|
// Reuse normal connect logic
|
|
onOpenSSHTerminal();
|
|
}
|
|
|
|
void SSHTerminalTool::resetSelection()
|
|
{
|
|
// m_selectedDeviceType = DeviceType::None;
|
|
// m_selectedWiredDevice = nullptr;
|
|
// m_selectedNetworkDevice = NetworkDevice{};
|
|
m_connectButton->setEnabled(false);
|
|
m_infoLabel->setText("Select a device to connect");
|
|
|
|
// Uncheck all radio buttons
|
|
if (m_deviceButtonGroup->checkedButton()) {
|
|
m_deviceButtonGroup->setExclusive(false);
|
|
m_deviceButtonGroup->checkedButton()->setChecked(false);
|
|
m_deviceButtonGroup->setExclusive(true);
|
|
}
|
|
}
|
|
|
|
void SSHTerminalTool::onOpenSSHTerminal()
|
|
{
|
|
if (m_selectedUniq.get().isEmpty()) {
|
|
m_infoLabel->setText("Please select a device first");
|
|
QMessageBox::warning(
|
|
this, "No Device Selected",
|
|
"Please select a device before trying to connect.");
|
|
return;
|
|
}
|
|
|
|
// Warn if known device is not jailbroken
|
|
auto selectedDevice =
|
|
AppContext::sharedInstance()->getDevice(m_selectedUniq);
|
|
if (selectedDevice && !selectedDevice->deviceInfo.jailbroken) {
|
|
auto reply = QMessageBox::question(
|
|
this, "Device Not Jailbroken",
|
|
"The selected device is not detected as jailbroken.\n"
|
|
"SSH access may not be available.\n\n"
|
|
"Do you want to continue anyway?",
|
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
|
if (reply == QMessageBox::No) {
|
|
m_infoLabel->setText(
|
|
"Connection cancelled (device not jailbroken)");
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool isWireless = m_selectedUniq.isMac();
|
|
|
|
// there must be an IP for wireless devices, check just in case
|
|
if (isWireless && m_selectedUniq.getIP().isEmpty()) {
|
|
m_infoLabel->setText(
|
|
"Selected network device is missing IP address. Please try again.");
|
|
QMessageBox::warning(this, "Missing IP Address",
|
|
"The selected network device is missing an IP "
|
|
"address. Please try again.");
|
|
return;
|
|
}
|
|
|
|
// Prepare connection info
|
|
ConnectionInfo connectionInfo;
|
|
|
|
if (!isWireless) {
|
|
connectionInfo.type = ConnectionType::Wired;
|
|
connectionInfo.deviceName = "FIXME";
|
|
connectionInfo.deviceUdid = m_selectedUniq.get();
|
|
connectionInfo.hostAddress = "127.0.0.1";
|
|
connectionInfo.port = 22;
|
|
} else {
|
|
connectionInfo.type = ConnectionType::Wireless;
|
|
connectionInfo.deviceName = m_selectedUniq.get();
|
|
connectionInfo.deviceUdid = "";
|
|
connectionInfo.hostAddress = m_selectedUniq.getIP();
|
|
connectionInfo.port = 22;
|
|
}
|
|
|
|
// Create and show SSH terminal widget in a new window
|
|
SSHTerminalWidget *sshTerminal = new SSHTerminalWidget(connectionInfo);
|
|
sshTerminal->setAttribute(Qt::WA_DeleteOnClose);
|
|
sshTerminal->show();
|
|
sshTerminal->raise();
|
|
sshTerminal->activateWindow();
|
|
}
|
|
|
|
SSHTerminalTool::~SSHTerminalTool() {}
|