1 /*
2     SPDX-FileCopyrightText: 2020 MBition GmbH
3     SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
4 
5     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 #include "imobiledevice.h"
9 
10 #include <QCoreApplication>
11 #include <QScopeGuard>
12 
13 #include "imobile_debug.h"
14 
15 #include "imobile.h"
16 #include "imobileportablemediaplayer.h"
17 
18 #include <libimobiledevice/libimobiledevice.h>
19 #include <libimobiledevice/lockdown.h>
20 
21 using namespace Solid::Backends::IMobile;
22 
IMobileDevice(const QString & udi)23 IMobileDevice::IMobileDevice(const QString &udi)
24     : Solid::Ifaces::Device()
25     , m_udi(udi)
26 {
27     const QString deviceId = udi.mid(udiPrefix().length() + 1);
28 
29     idevice_t device;
30     idevice_new(&device, deviceId.toUtf8().constData());
31     if (!device) {
32         qCWarning(IMOBILE) << "Failed to create device instance for" << deviceId;
33         return;
34     }
35 
36     auto deviceCleanup = qScopeGuard([device] {
37         idevice_free(device);
38     });
39 
40     lockdownd_client_t lockdowndClient = nullptr;
41     auto ret = lockdownd_client_new(device, &lockdowndClient, "kde_solid_imobile");
42     if (ret != LOCKDOWN_E_SUCCESS || !lockdowndClient) {
43         qCWarning(IMOBILE) << "Failed to create lockdownd client for" << deviceId;
44         return;
45     }
46 
47     auto lockdowndClientCleanup = qScopeGuard([lockdowndClient] {
48         lockdownd_client_free(lockdowndClient);
49     });
50 
51     char *name = nullptr;
52     auto lockdownRet = lockdownd_get_device_name(lockdowndClient, &name);
53     if (lockdownRet != LOCKDOWN_E_SUCCESS) {
54         qCWarning(IMOBILE) << "Failed to get device name for" << deviceId << lockdownRet;
55     } else if (name) {
56         m_name = QString::fromUtf8(name);
57         free(name);
58     }
59 
60     plist_t deviceClassEntry = nullptr;
61     lockdownRet = lockdownd_get_value(lockdowndClient, nullptr /*global domain*/, "DeviceClass", &deviceClassEntry);
62     if (lockdownRet != LOCKDOWN_E_SUCCESS) {
63         qCWarning(IMOBILE) << "Failed to get device class for" << deviceId << lockdownRet;
64     } else {
65         char *deviceClass = nullptr;
66         plist_get_string_val(deviceClassEntry, &deviceClass);
67         if (deviceClass) {
68             m_deviceClass = QString::fromUtf8(deviceClass);
69             free(deviceClass);
70         }
71     }
72 }
73 
~IMobileDevice()74 IMobileDevice::~IMobileDevice()
75 {
76 }
77 
udi() const78 QString IMobileDevice::udi() const
79 {
80     return m_udi;
81 }
82 
parentUdi() const83 QString IMobileDevice::parentUdi() const
84 {
85     return udiPrefix();
86 }
87 
vendor() const88 QString IMobileDevice::vendor() const
89 {
90     return QCoreApplication::translate("imobiledevice", "Apple", "Company name");
91 }
92 
product() const93 QString IMobileDevice::product() const
94 {
95     // TODO would be nice to use actual product names, e.g. "iPhone 5S"
96     // but accessing device type requires doing a handshake with the device,
97     // which will fail if locked or not paired, and also would require us
98     // to maintain a giant mapping table
99     return m_deviceClass;
100 }
101 
icon() const102 QString IMobileDevice::icon() const
103 {
104     if (m_deviceClass.contains(QLatin1String("iPod"))) {
105         return QStringLiteral("multimedia-player-apple-ipod-touch");
106     } else if (m_deviceClass.contains(QLatin1String("iPad"))) {
107         return QStringLiteral("computer-apple-ipad");
108     } else {
109         return QStringLiteral("phone-apple-iphone");
110     }
111 }
112 
emblems() const113 QStringList IMobileDevice::emblems() const
114 {
115     return {};
116 }
117 
description() const118 QString IMobileDevice::description() const
119 {
120     return m_name;
121 }
122 
queryDeviceInterface(const Solid::DeviceInterface::Type & type) const123 bool IMobileDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const
124 {
125     switch (type) {
126         // TODO would be cool to support GenericInterface for reading
127         // arbitrary plist configuration, cf. what ideviceinfo tool does
128 
129     case Solid::DeviceInterface::PortableMediaPlayer:
130         return true;
131 
132     default:
133         return false;
134     }
135 }
136 
createDeviceInterface(const Solid::DeviceInterface::Type & type)137 QObject *IMobileDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type)
138 {
139     if (!queryDeviceInterface(type)) {
140         return nullptr;
141     }
142 
143     switch (type) {
144     case Solid::DeviceInterface::PortableMediaPlayer:
145         return new PortableMediaPlayer(this);
146 
147     default:
148         Q_UNREACHABLE();
149         return nullptr;
150     }
151 }
152