mirror of
https://github.com/palera1n/palera1n.git
synced 2026-10-23 18:49:48 +02:00
feat: add tui (#758)
This commit also contains an entire redesign of the GUI interface, which now contains a sidebar for all panels. Hopefully checkra1n approves :)
This commit is contained in:
42 files changed
+2895
-487
No files matched your search
+322
-143
@@ -1,183 +1,362 @@
|
||||
#ifdef WITH_GUI
|
||||
#if defined(WITH_GUI) || defined(WITH_TUI)
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
#include "wx/AppFrame.hpp"
|
||||
#include "state.hpp"
|
||||
#include "sequence.hpp"
|
||||
#include "utils.h"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libirecovery.h>
|
||||
|
||||
static inline DeviceState make_fresh_state()
|
||||
{
|
||||
return DeviceState{};
|
||||
namespace {
|
||||
|
||||
struct ManagedDevice {
|
||||
DeviceState state;
|
||||
bool normalPresent = false;
|
||||
bool recoveryPresent = false;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::vector<DeviceStateCallback> g_callbacks;
|
||||
std::unordered_map<uint64_t, ManagedDevice> g_devices; // keyed by ECID
|
||||
std::unordered_map<std::string, uint64_t> g_udidToEcid;
|
||||
uint64_t g_activeEcid = 0;
|
||||
std::once_flag g_startOnce;
|
||||
irecv_device_event_context_t g_recoveryCtx = nullptr;
|
||||
|
||||
void dispatch_callbacks(const std::vector<DeviceStateCallback>& callbacks, const DeviceState& state) {
|
||||
for (const auto& cb : callbacks) {
|
||||
if (cb)
|
||||
cb(state);
|
||||
}
|
||||
}
|
||||
|
||||
void normal_device_event_cb(
|
||||
const idevice_event_t* event,
|
||||
void* user_data)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
auto* frame = static_cast<MainFrame*>(user_data);
|
||||
if (!frame || !event)
|
||||
return;
|
||||
#endif
|
||||
DeviceState build_public_state_locked() {
|
||||
DeviceState out;
|
||||
out.connectedDeviceCount = static_cast<uint32_t>(g_devices.size());
|
||||
out.multipleDevices = out.connectedDeviceCount > 1;
|
||||
|
||||
DeviceState state = make_fresh_state();
|
||||
|
||||
if (event->event == IDEVICE_DEVICE_REMOVE)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return;
|
||||
if (g_devices.size() != 1) {
|
||||
g_activeEcid = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
if (event->event != IDEVICE_DEVICE_ADD)
|
||||
return;
|
||||
auto it = g_devices.begin();
|
||||
if (g_activeEcid != 0) {
|
||||
auto activeIt = g_devices.find(g_activeEcid);
|
||||
if (activeIt != g_devices.end())
|
||||
it = activeIt;
|
||||
}
|
||||
|
||||
g_activeEcid = it->first;
|
||||
out = it->second.state;
|
||||
out.connected = true;
|
||||
out.connectedDeviceCount = 1;
|
||||
out.multipleDevices = false;
|
||||
return out;
|
||||
}
|
||||
|
||||
void publish_state() {
|
||||
std::vector<DeviceStateCallback> callbacks;
|
||||
DeviceState state;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
callbacks = g_callbacks;
|
||||
state = build_public_state_locked();
|
||||
}
|
||||
dispatch_callbacks(callbacks, state);
|
||||
}
|
||||
|
||||
bool lockdown_get_string(lockdownd_client_t client, const char* key, std::string& out) {
|
||||
plist_t value = nullptr;
|
||||
if (lockdownd_get_value(client, nullptr, key, &value) != LOCKDOWN_E_SUCCESS || !value)
|
||||
return false;
|
||||
|
||||
char* raw = nullptr;
|
||||
plist_get_string_val(value, &raw);
|
||||
if (raw) {
|
||||
out = raw;
|
||||
free(raw);
|
||||
}
|
||||
|
||||
plist_free(value);
|
||||
return !out.empty();
|
||||
}
|
||||
|
||||
bool lockdown_get_uint(lockdownd_client_t client, const char* key, uint64_t& out) {
|
||||
plist_t value = nullptr;
|
||||
if (lockdownd_get_value(client, nullptr, key, &value) != LOCKDOWN_E_SUCCESS || !value)
|
||||
return false;
|
||||
|
||||
plist_get_uint_val(value, &out);
|
||||
plist_free(value);
|
||||
return out != 0;
|
||||
}
|
||||
|
||||
bool build_normal_state(const idevice_event_t* event, DeviceState& out, std::string& connectionType) {
|
||||
idevice_t device = nullptr;
|
||||
if (idevice_new(&device, event->udid) != IDEVICE_E_SUCCESS)
|
||||
return;
|
||||
|
||||
state.connected = true;
|
||||
state.mode = DeviceMode::Normal;
|
||||
state.udid = event->udid ? event->udid : "";
|
||||
if (!event || idevice_new(&device, event->udid) != IDEVICE_E_SUCCESS || !device)
|
||||
return false;
|
||||
|
||||
lockdownd_client_t client = nullptr;
|
||||
|
||||
if (lockdownd_client_new_with_handshake(device, &client, "palera1n") == LOCKDOWN_E_SUCCESS)
|
||||
{
|
||||
plist_t product = nullptr;
|
||||
plist_t version = nullptr;
|
||||
plist_t ecid = nullptr;
|
||||
|
||||
char *p = nullptr, *v = nullptr;
|
||||
uint64_t ecid_val = 0;
|
||||
|
||||
lockdownd_get_value(client, nullptr, "ProductType", &product);
|
||||
lockdownd_get_value(client, nullptr, "ProductVersion", &version);
|
||||
lockdownd_get_value(client, nullptr, "UniqueChipID", &ecid);
|
||||
|
||||
if (product) plist_get_string_val(product, &p);
|
||||
if (version) plist_get_string_val(version, &v);
|
||||
if (ecid) plist_get_uint_val(ecid, &ecid_val);
|
||||
|
||||
state.productType = p ? p : "";
|
||||
state.productVersion = v ? v : "";
|
||||
state.ecid = ecid_val;
|
||||
|
||||
free(p);
|
||||
free(v);
|
||||
|
||||
if (product) plist_free(product);
|
||||
if (version) plist_free(version);
|
||||
if (ecid) plist_free(ecid);
|
||||
|
||||
lockdownd_client_free(client);
|
||||
if (lockdownd_client_new_with_handshake(device, &client, "palera1n") != LOCKDOWN_E_SUCCESS) {
|
||||
idevice_free(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
out.connected = true;
|
||||
out.mode = DeviceMode::Normal;
|
||||
out.udid = event->udid ? event->udid : "";
|
||||
|
||||
lockdown_get_string(client, "ProductType", out.productType);
|
||||
lockdown_get_string(client, "ProductVersion", out.productVersion);
|
||||
lockdown_get_string(client, "ConnectionType", connectionType);
|
||||
lockdown_get_uint(client, "UniqueChipID", out.ecid);
|
||||
|
||||
out.isSupported = SequenceIsSupported(out.productType);
|
||||
|
||||
lockdownd_client_free(client);
|
||||
idevice_free(device);
|
||||
|
||||
state.isSupported = SequenceIsSupported(state.productType);
|
||||
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void recovery_device_event_cb(
|
||||
const irecv_device_event_t* event,
|
||||
void* user_data)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
auto* frame = static_cast<MainFrame*>(user_data);
|
||||
if (!frame || !event)
|
||||
return;
|
||||
#endif
|
||||
bool build_recovery_state(const irecv_device_event_t* event, DeviceState& out) {
|
||||
if (!event || !event->device_info || !event->device_info->ecid)
|
||||
return false;
|
||||
|
||||
DeviceState state = make_fresh_state();
|
||||
|
||||
if (event->type == IRECV_DEVICE_REMOVE)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type != IRECV_DEVICE_ADD)
|
||||
return;
|
||||
|
||||
state.connected = true;
|
||||
|
||||
if (event->mode == IRECV_K_DFU_MODE)
|
||||
{
|
||||
state.mode = DeviceMode::DFU;
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event->mode == IRECV_K_RECOVERY_MODE_1 ||
|
||||
event->mode == IRECV_K_RECOVERY_MODE_2 ||
|
||||
event->mode == IRECV_K_RECOVERY_MODE_3 ||
|
||||
event->mode == IRECV_K_RECOVERY_MODE_4))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.mode = DeviceMode::Recovery;
|
||||
|
||||
uint64_t ecid_val = 0;
|
||||
if (event->device_info && event->device_info->ecid)
|
||||
ecid_val = event->device_info->ecid;
|
||||
state.ecid = ecid_val;
|
||||
out.connected = true;
|
||||
out.ecid = event->device_info->ecid;
|
||||
out.mode = (event->mode == IRECV_K_DFU_MODE) ? DeviceMode::DFU : DeviceMode::Recovery;
|
||||
|
||||
irecv_client_t client = nullptr;
|
||||
|
||||
int attempts = 0;
|
||||
const int max_attempts = 10;
|
||||
|
||||
while (attempts < max_attempts)
|
||||
{
|
||||
if (irecv_open_with_ecid(&client, state.ecid) == IRECV_E_SUCCESS)
|
||||
const int maxAttempts = 10;
|
||||
while (attempts < maxAttempts) {
|
||||
if (irecv_open_with_ecid(&client, out.ecid) == IRECV_E_SUCCESS)
|
||||
break;
|
||||
|
||||
attempts++;
|
||||
++attempts;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
}
|
||||
|
||||
if (client)
|
||||
{
|
||||
LOG_VERBOSE("Opened client after %d attempts", attempts);
|
||||
|
||||
if (client) {
|
||||
irecv_device_t device = nullptr;
|
||||
if (irecv_devices_get_device_by_client(client, &device) == IRECV_E_SUCCESS && device)
|
||||
{
|
||||
state.productType = device->product_type ? device->product_type : "";
|
||||
state.displayName = device->display_name ? device->display_name : "";
|
||||
if (irecv_devices_get_device_by_client(client, &device) == IRECV_E_SUCCESS && device) {
|
||||
out.productType = device->product_type ? device->product_type : "";
|
||||
out.displayName = device->display_name ? device->display_name : "";
|
||||
}
|
||||
|
||||
irecv_close(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("Failed to open irecv client after retries");
|
||||
} else {
|
||||
LOG_VERBOSE("Failed to open irecv client after retries for ECID=%llu", static_cast<unsigned long long>(out.ecid));
|
||||
}
|
||||
|
||||
state.isSupported = SequenceIsSupported(state.productType);
|
||||
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
out.isSupported = SequenceIsSupported(out.productType);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_GUI
|
||||
void handle_normal_add(const idevice_event_t* event) {
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
if (event->conn_type != CONNECTION_USBMUXD) {
|
||||
LOG_VERBOSE("Ignoring non-USB normal mode device event");
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceState discovered;
|
||||
std::string connectionType;
|
||||
if (!build_normal_state(event, discovered, connectionType))
|
||||
return;
|
||||
|
||||
if (!connectionType.empty() && connectionType != "USB") {
|
||||
LOG_VERBOSE("Ignoring over-the-air normal mode device (ConnectionType=%s)", connectionType.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (discovered.ecid == 0) {
|
||||
LOG_VERBOSE("Ignoring normal mode device without ECID");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
ManagedDevice& m = g_devices[discovered.ecid];
|
||||
m.state = discovered;
|
||||
m.normalPresent = true;
|
||||
m.recoveryPresent = false;
|
||||
|
||||
if (!discovered.udid.empty())
|
||||
g_udidToEcid[discovered.udid] = discovered.ecid;
|
||||
}
|
||||
|
||||
publish_state();
|
||||
}
|
||||
|
||||
void handle_normal_remove(const idevice_event_t* event) {
|
||||
if (!event || !event->udid)
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
auto mapIt = g_udidToEcid.find(event->udid);
|
||||
if (mapIt == g_udidToEcid.end())
|
||||
return;
|
||||
|
||||
const uint64_t ecid = mapIt->second;
|
||||
g_udidToEcid.erase(mapIt);
|
||||
|
||||
auto devIt = g_devices.find(ecid);
|
||||
if (devIt == g_devices.end())
|
||||
return;
|
||||
|
||||
devIt->second.normalPresent = false;
|
||||
if (!devIt->second.normalPresent && !devIt->second.recoveryPresent)
|
||||
g_devices.erase(devIt);
|
||||
}
|
||||
|
||||
publish_state();
|
||||
}
|
||||
|
||||
void handle_recovery_add(const irecv_device_event_t* event) {
|
||||
DeviceState discovered;
|
||||
if (!build_recovery_state(event, discovered))
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
ManagedDevice& m = g_devices[discovered.ecid];
|
||||
m.state = discovered;
|
||||
m.recoveryPresent = true;
|
||||
m.normalPresent = false;
|
||||
}
|
||||
|
||||
publish_state();
|
||||
}
|
||||
|
||||
void handle_recovery_remove(const irecv_device_event_t* event) {
|
||||
bool changed = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
|
||||
uint64_t ecid = 0;
|
||||
if (event && event->device_info)
|
||||
ecid = event->device_info->ecid;
|
||||
|
||||
if (ecid == 0) {
|
||||
for (const auto& [key, value] : g_devices) {
|
||||
if (!value.recoveryPresent)
|
||||
continue;
|
||||
if (ecid != 0) {
|
||||
ecid = 0;
|
||||
break;
|
||||
}
|
||||
ecid = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (ecid == 0)
|
||||
return;
|
||||
|
||||
auto devIt = g_devices.find(ecid);
|
||||
if (devIt == g_devices.end())
|
||||
return;
|
||||
|
||||
devIt->second.recoveryPresent = false;
|
||||
if (!devIt->second.normalPresent && !devIt->second.recoveryPresent)
|
||||
g_devices.erase(devIt);
|
||||
|
||||
for (auto it = g_udidToEcid.begin(); it != g_udidToEcid.end();) {
|
||||
if (it->second == ecid && g_devices.find(ecid) == g_devices.end())
|
||||
it = g_udidToEcid.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
publish_state();
|
||||
}
|
||||
|
||||
bool is_supported_recovery_mode(irecv_mode mode) {
|
||||
return mode == IRECV_K_DFU_MODE ||
|
||||
mode == IRECV_K_RECOVERY_MODE_1 ||
|
||||
mode == IRECV_K_RECOVERY_MODE_2 ||
|
||||
mode == IRECV_K_RECOVERY_MODE_3 ||
|
||||
mode == IRECV_K_RECOVERY_MODE_4;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_device_state_callback(DeviceStateCallback cb) {
|
||||
if (!cb)
|
||||
return;
|
||||
|
||||
DeviceState snapshot;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
g_callbacks.push_back(cb);
|
||||
snapshot = build_public_state_locked();
|
||||
}
|
||||
|
||||
cb(snapshot);
|
||||
}
|
||||
|
||||
void ensure_device_event_system_started() {
|
||||
std::call_once(g_startOnce, []() {
|
||||
if (idevice_event_subscribe(normal_device_event_cb, nullptr) != IDEVICE_E_SUCCESS)
|
||||
LOG_ERROR("Failed to subscribe to normal mode device events");
|
||||
|
||||
if (irecv_device_event_subscribe(&g_recoveryCtx, recovery_device_event_cb, nullptr) != IRECV_E_SUCCESS)
|
||||
LOG_ERROR("Failed to subscribe to recovery/DFU device events");
|
||||
|
||||
char** udids = nullptr;
|
||||
int count = 0;
|
||||
if (idevice_get_device_list(&udids, &count) == IDEVICE_E_SUCCESS && udids) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
idevice_event_t synthetic_event;
|
||||
synthetic_event.event = IDEVICE_DEVICE_ADD;
|
||||
synthetic_event.udid = udids[i];
|
||||
synthetic_event.conn_type = CONNECTION_USBMUXD;
|
||||
handle_normal_add(&synthetic_event);
|
||||
}
|
||||
idevice_device_list_free(udids);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void normal_device_event_cb(const idevice_event_t* event, void* user_data) {
|
||||
(void)user_data;
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
if (event->event == IDEVICE_DEVICE_ADD) {
|
||||
handle_normal_add(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->event == IDEVICE_DEVICE_REMOVE)
|
||||
handle_normal_remove(event);
|
||||
}
|
||||
|
||||
void recovery_device_event_cb(const irecv_device_event_t* event, void* user_data) {
|
||||
(void)user_data;
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
if (event->type == IRECV_DEVICE_ADD && is_supported_recovery_mode(event->mode)) {
|
||||
handle_recovery_add(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type == IRECV_DEVICE_REMOVE)
|
||||
handle_recovery_remove(event);
|
||||
}
|
||||
|
||||
#endif // WITH_GUI || WITH_TUI
|
||||
Reference in new issue
Block a user