Solid

imobilemanager.cpp
1/*
2 SPDX-FileCopyrightText: 2020 MBition GmbH
3 SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
4 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "imobilemanager.h"
10
11#include <QFile>
12#include <QFileSystemWatcher>
13
14#include "imobile_debug.h"
15
16#include "../shared/rootdevice.h"
17#include "imobile.h"
18#include "imobiledevice.h"
19
20using namespace Solid::Backends::IMobile;
21using namespace Solid::Backends::Shared;
22using namespace Qt::StringLiterals;
23
24namespace
25{
26constexpr auto VAR_RUN = "/var/run/"_L1;
27constexpr auto MUXD_SOCKET = "/var/run/usbmuxd"_L1;
28} // namespace
29
30Manager::Manager(QObject *parent)
31 : Solid::Ifaces::DeviceManager(parent)
32 , m_watcher(new QFileSystemWatcher)
33{
34 // Lazy initialize. If usbmuxd isn't running we don't need to do anything yet.
35 // This is in part to prevent libusbmuxd from setting up extra inotifies and polling when
36 // we know that it won't find anything yet. Works around a bunch of whoopsies.
37 // https://github.com/libimobiledevice/libusbmuxd/pull/133
38 // https://github.com/libimobiledevice/libusbmuxd/issues/135
39 connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, [this](const QString &) {
40 if (QFile::exists(MUXD_SOCKET)) {
41 spinUp();
42 }
43 });
44 m_watcher->addPath(VAR_RUN);
45 if (QFile::exists(MUXD_SOCKET)) {
46 spinUp();
47 }
48}
49
50Manager::~Manager()
51{
52 if (m_spunUp) {
53 idevice_event_unsubscribe();
54 }
55}
56
57void Manager::spinUp()
58{
59 if (m_spunUp) {
60 return;
61 }
62 m_spunUp = true;
63 // Handing over watching to libusbmuxd. Don't need our watcher anymore.
64 // Deleting later because this function gets called by a signal from m_watcher so we mustn't delete it just now.
65 m_watcher.release()->deleteLater();
66
67 auto ret = idevice_event_subscribe(
68 [](const idevice_event_t *event, void *user_data) {
69 // NOTE this is called from a different thread.
70 static_cast<Manager *>(user_data)->onDeviceEvent(event);
71 },
72 this);
73 if (ret != IDEVICE_E_SUCCESS) {
74 qCWarning(IMOBILE) << "Failed to subscribe to device events";
75 }
76
77 char **devices = nullptr;
78 int count = 0;
79
80 ret = idevice_get_device_list(&devices, &count);
81 if (ret != IDEVICE_E_SUCCESS && ret != IDEVICE_E_NO_DEVICE) {
82 qCWarning(IMOBILE) << "Failed to get list of iOS devices" << ret;
83 return;
84 }
85
86 m_deviceUdis.reserve(count);
87
88 for (int i = 0; i < count; ++i) {
89 m_deviceUdis.append(Solid::Backends::IMobile::udiPrefix() + QLatin1Char('/') + QString::fromLatin1(devices[i]));
90 }
91
92 if (devices) {
93 idevice_device_list_free(devices);
94 }
95}
96
97QObject *Manager::createDevice(const QString &udi)
98{
99 if (udi == udiPrefix()) {
100 RootDevice *root = new RootDevice(udi);
101 root->setProduct(tr("iDevice"));
102 root->setDescription(tr("iOS devices"));
103 root->setIcon(QStringLiteral("phone-apple-iphone"));
104 return root;
105 }
106
107 if (m_deviceUdis.contains(udi)) {
108 return new IMobileDevice(udi);
109 }
110
111 return nullptr;
112}
113
114QStringList Manager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type)
115{
116 QStringList devices;
117
118 if (!parentUdi.isEmpty() || type != Solid::DeviceInterface::Unknown) {
119 for (const QString &udi : m_deviceUdis) {
120 IMobileDevice device(udi);
121 if (!device.queryDeviceInterface(type)) {
122 continue;
123 }
124
125 if (!parentUdi.isEmpty() && device.parentUdi() != parentUdi) {
126 continue;
127 }
128
129 devices << udi;
130 }
131 }
132
133 return devices;
134}
135
136QStringList Manager::allDevices()
137{
138 return m_deviceUdis;
139}
140
141QSet<Solid::DeviceInterface::Type> Manager::supportedInterfaces() const
142{
143 return {Solid::DeviceInterface::PortableMediaPlayer};
144}
145
146QString Manager::udiPrefix() const
147{
148 return Solid::Backends::IMobile::udiPrefix();
149}
150
151void Manager::onDeviceEvent(const idevice_event_t *event)
152{
153 const QString udi = Solid::Backends::IMobile::udiPrefix() + QLatin1Char('/') + QString::fromLatin1(event->udid);
154
155 switch (event->event) {
156 case IDEVICE_DEVICE_ADD:
157 // Post it to the right thread.
158 QMetaObject::invokeMethod(this, [this, udi] {
159 if (!m_deviceUdis.contains(udi)) {
160 m_deviceUdis.append(udi);
161 Q_EMIT deviceAdded(udi);
162 }
163 });
164 return;
165 case IDEVICE_DEVICE_REMOVE:
166 QMetaObject::invokeMethod(this, [this, udi] {
167 if (m_deviceUdis.removeOne(udi)) {
168 Q_EMIT deviceRemoved(udi);
169 }
170 });
171 return;
172#if IMOBILEDEVICE_API >= QT_VERSION_CHECK(1, 3, 0)
173 case IDEVICE_DEVICE_PAIRED:
174 return;
175#endif
176 }
177
178 qCDebug(IMOBILE) << "Unhandled device event" << event->event << "for" << event->udid;
179}
180
181#include "moc_imobilemanager.cpp"
Type
This enum type defines the type of device interface that a Device can have.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
bool exists() const const
void directoryChanged(const QString &path)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:01:48 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.