#include <QtCore/QBitArray>
#include <iostream>
+#include <dbt.h>
+#include <setupapi.h>
QT_USE_NAMESPACE
#define WIDEN(x) _WIDEN(x)
#define LQT_VERSION_STR WIDEN(QT_VERSION_STR)
+// The GUID used by the Windows Phone IP over USB service
+static const GUID GUID_DEVICE_WINPHONE8_USB = { 0x26fedc4eL, 0x6ac3, 0x4241, 0x9e, 0x4d, 0xe3, 0xd4, 0xb2, 0xc5, 0xc5, 0x34 };
+
// Handlers
typedef int (*HandleDeviceFunction)(int, const QString &, const QString &, HANDLE);
extern int handleAppxDevice(int deviceIndex, const QString &app, const QString &cacheDir, HANDLE runLock);
extern int handleXapDevice(int deviceIndex, const QString &app, const QString &cacheDir, HANDLE runLock);
+typedef int (*AppListFunction)(int, QSet<QString> &);
+extern int xapAppNames(int deviceIndex, QSet<QString> &apps);
+
+extern QStringList xapDeviceNames();
// Callbacks
static void __stdcall run(DWORD argc, LPWSTR argv[]);
static DWORD __stdcall control(DWORD control, DWORD eventType, void *eventData, void *context);
static BOOL __stdcall control(DWORD type);
+static LRESULT __stdcall control(HWND window, UINT msg, WPARAM wParam, LPARAM lParam);
+static DWORD __stdcall deviceWorker(LPVOID param);
static DWORD __stdcall appWorker(LPVOID param);
union ErrorId
enum ControlEvent
{
- NoCommand = 0, Stop = 1, NewWorker = 2
+ NoCommand = 0, Stop = 1, NewWorker = 2, PhoneConnected = 3
};
struct D3DServicePrivate
, checkPoint(1)
, isService(false)
, controlEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
+ , controlWindow(0)
+ , deviceHandle(0)
{
GetModuleFileName(NULL, path, MAX_PATH);
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
}
~D3DServicePrivate()
{
+ if (deviceHandle)
+ UnregisterDeviceNotification(deviceHandle);
+ if (controlWindow)
+ CloseHandle(controlWindow);
if (controlEvent)
CloseHandle(controlEvent);
}
// Internal use
bool isService;
HANDLE controlEvent;
+ HWND controlWindow;
+ HDEVNOTIFY deviceHandle;
QList<ControlEvent> eventQueue;
QList<QStringPair> workerQueue;
};
D3DService::reportStatus(SERVICE_RUNNING, NO_ERROR, 0);
} else {
SetConsoleCtrlHandler(&control, TRUE);
+
+ // Create an invisible window for getting broadcast events
+ WNDCLASS controlWindowClass = { 0, &control, 0, 0, NULL, NULL,
+ NULL, NULL, NULL, L"controlWindow" };
+ if (!RegisterClass(&controlWindowClass)) {
+ qCCritical(lcD3DService) << "Unable to register control window class:"
+ << qt_error_string(GetLastError());
+ return;
+ }
+ d->controlWindow = CreateWindowEx(0, L"controlWindow", NULL, 0, 0, 0, 0, 0,
+ NULL, NULL, NULL, NULL);
}
+ // Register for USB notifications
+ DEV_BROADCAST_DEVICEINTERFACE filter = {
+ sizeof(DEV_BROADCAST_DEVICEINTERFACE), DBT_DEVTYP_DEVICEINTERFACE,
+ 0, GUID_DEVICE_WINPHONE8_USB, 0
+ };
+ d->deviceHandle = RegisterDeviceNotification(
+ d->isService ? d->statusHandle : static_cast<HANDLE>(d->controlWindow), &filter,
+ d->isService ? DEVICE_NOTIFY_SERVICE_HANDLE : DEVICE_NOTIFY_WINDOW_HANDLE);
+
QVector<HANDLE> waitHandles;
waitHandles.append(d->controlEvent);
+ // Dummy handle for phone (gets replaced by a worker when needed)
+ HANDLE dummyHandle;
+ DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(),
+ GetCurrentProcess(), &dummyHandle, SYNCHRONIZE, FALSE, 0);
+ waitHandles.append(dummyHandle);
+
+ // Event handles for emulators (XDE)
+ WCHAR username[MAX_PATH];
+ ULONG usernameSize = MAX_PATH;
+ GetUserName(username, &usernameSize);
+ const QStringList emulatorNames = xapDeviceNames().mid(1);
+ foreach (const QString &name, emulatorNames) {
+ const QString eventName = QStringLiteral("Local\\XdeOnServerInitialize")
+ + name + QLatin1Char('.') + QString::fromWCharArray(username, usernameSize).toLower();
+ HANDLE event = CreateEvent(NULL, TRUE, FALSE, reinterpret_cast<LPCWSTR>(eventName.utf16()));
+ if (event)
+ waitHandles.append(event);
+ else
+ qCWarning(lcD3DService) << "Unable to create event:" << qt_error_string(GetLastError());
+ }
+
// App monitoring threads
QVector<Worker *> workers;
+ // Device monitoring threads - one per device
+ QVector<Worker *> deviceWorkers(emulatorNames.size() + 1, NULL);
+
// Static list of registrations
foreach (const QStringPair ®istration, D3DService::registrations()) {
Worker *worker = new Worker(registration, &appWorker);
waitHandles.append(worker->thread());
}
+ // If a Windows Phone is already connected, queue a device worker
+ HDEVINFO info = SetupDiGetClassDevs(&GUID_DEVICE_WINPHONE8_USB, NULL, NULL,
+ DIGCF_DEVICEINTERFACE|DIGCF_PRESENT);
+ if (info != INVALID_HANDLE_VALUE) {
+ SP_DEVINFO_DATA infoData = { sizeof(SP_DEVINFO_DATA) };
+ if (SetupDiEnumDeviceInfo(info, 0, &infoData)) {
+ d->eventQueue.append(PhoneConnected);
+ SetEvent(d->controlEvent);
+ }
+ SetupDiDestroyDeviceInfoList(info);
+ }
+
// Master loop
// This loop handles incoming events from the service controller and
// worker threads. It also creates new worker threads as needed.
- const uint minWorker = WAIT_OBJECT_0 + 1;
+ const uint phoneEvent = WAIT_OBJECT_0 + 1;
+ const uint minEmulatorEvent = phoneEvent + 1;
+ const uint maxEmulatorEvent = minEmulatorEvent + emulatorNames.size() - 1;
+ const uint minWorker = maxEmulatorEvent + 1;
uint maxWorker = minWorker + workers.size() - 1;
forever {
- DWORD event = WaitForMultipleObjects(waitHandles.size(), waitHandles.data(), FALSE, INFINITE);
+ DWORD event = MsgWaitForMultipleObjects(waitHandles.size(), waitHandles.data(), FALSE, INFINITE, QS_ALLINPUT);
if (event >= WAIT_OBJECT_0 && event < WAIT_OBJECT_0 + waitHandles.size()) {
// A control event occurred
if (event == WAIT_OBJECT_0) {
}
continue;
}
+
+ // A Windows Phone device was connected
+ if (controlEvent == PhoneConnected) {
+ qCDebug(lcD3DService) << "A Windows Phone has connected.";
+ // The worker is already active
+ if (deviceWorkers.first())
+ continue;
+
+ // Start the phone monitoring thread
+ Worker *worker = new Worker(qMakePair(QString::number(0), QString()), &deviceWorker);
+ deviceWorkers[0] = worker;
+ CloseHandle(waitHandles[phoneEvent - WAIT_OBJECT_0]);
+ waitHandles[phoneEvent - WAIT_OBJECT_0] = worker->thread();
+ continue;
+ }
}
if (shutdown)
continue;
}
+ // Device events
+ if (event >= phoneEvent && event <= maxEmulatorEvent) {
+ const int deviceIndex = event - phoneEvent;
+ // Determine if the handle in the slot is an event or thread
+ if (GetThreadId(waitHandles[event - WAIT_OBJECT_0])) {
+ qCDebug(lcD3DService) << "Device worker exited:" << deviceIndex;
+ // The thread has exited, close the handle and replace the event
+ delete deviceWorkers.at(deviceIndex);
+ deviceWorkers[deviceIndex] = 0;
+
+ // The phone case is handled elsewhere; set a dummy handle
+ if (event == phoneEvent) {
+ HANDLE dummyHandle;
+ DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(),
+ GetCurrentProcess(), &dummyHandle, SYNCHRONIZE, FALSE, 0);
+ waitHandles[event - WAIT_OBJECT_0] = dummyHandle;
+ continue;
+ }
+
+ // Re-create the event handle
+ const QString eventName = QStringLiteral("Local\\XdeOnServerInitialize")
+ + emulatorNames.at(deviceIndex - 1) + QLatin1Char('.')
+ + QString::fromWCharArray(username, usernameSize).toLower();
+ HANDLE emulatorEvent = CreateEvent(
+ NULL, TRUE, FALSE, reinterpret_cast<LPCWSTR>(eventName.utf16()));
+ if (emulatorEvent) {
+ waitHandles[event - WAIT_OBJECT_0] = emulatorEvent;
+ } else {
+ // If the above fails, replace it with a dummy to keep things going
+ qCCritical(lcD3DService).nospace() << "Unable to create event for emulator "
+ << emulatorNames.at(deviceIndex - 1)
+ << ": " << qt_error_string(GetLastError());
+ HANDLE dummyHandle;
+ DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(),
+ GetCurrentProcess(), &dummyHandle, SYNCHRONIZE, FALSE, 0);
+ waitHandles[event - WAIT_OBJECT_0] = dummyHandle;
+ }
+ } else {
+ qCDebug(lcD3DService) << "An emulator was activated:" << deviceIndex;
+ // The event was set; close the handle and replace with a thread
+ CloseHandle(waitHandles[event - WAIT_OBJECT_0]);
+
+ // This shouldn't happen
+ if (event == phoneEvent)
+ continue;
+
+ const QStringPair config = qMakePair(QString::number(deviceIndex), QString());
+ Worker *worker = new Worker(config, &deviceWorker);
+ deviceWorkers[deviceIndex] = worker;
+ waitHandles[event - WAIT_OBJECT_0] = worker->thread();
+ }
+ continue;
+ }
+
// A worker exited
if (event >= minWorker && event <= maxWorker) {
// Delete the worker and clear the handle (TODO: remove it?)
continue;
}
}
+
+ // TODO: check return val for this
+ if (!d->isService) {
+ MSG msg;
+ if (PeekMessage(&msg, d->controlWindow, 0, 0, PM_REMOVE))
+ DispatchMessage(&msg);
+ }
}
qDeleteAll(workers);
D3DService::reportStatus(SERVICE_STOPPED, NO_ERROR, 0);
}
+DWORD __stdcall deviceWorker(LPVOID param)
+{
+ WorkerParam *args = reinterpret_cast<WorkerParam *>(param);
+
+ // The list of applications on this device will be polled until
+ // an error code is returned (e.g. the device is disconnected) or
+ // the thread is told to exit.
+ QSet<QString> appNames;
+ AppListFunction appList;
+
+ int deviceIndex = 0;
+ if (args->deviceName.isEmpty() || args->deviceName == QStringLiteral("local")) {
+ // Not implemented
+ return 0;
+ } else {
+ // CoreCon (Windows Phone)
+ bool ok;
+ deviceIndex = args->deviceName.toInt(&ok);
+ if (!ok)
+ return WorkerParam::BadDeviceIndex;
+
+ appList = xapAppNames;
+ }
+
+ forever {
+ if (WaitForSingleObject(args->runLock, 0) == WAIT_OBJECT_0)
+ return 0;
+
+ QSet<QString> latestAppNames;
+ int exitCode = appList(deviceIndex, latestAppNames);
+ if (exitCode != WorkerParam::NoError)
+ return exitCode;
+
+ QSet<QString> newAppNames = latestAppNames - appNames;
+ if (!newAppNames.isEmpty()) {
+ // Create a new app watcher for each new app
+ foreach (const QString &app, newAppNames) {
+ qCWarning(lcD3DService).nospace() << "Found app " << app << " on device "
+ << args->deviceName << '.';
+ d->workerQueue.append(qMakePair(args->deviceName, app));
+ }
+
+ d->eventQueue.append(NewWorker);
+ SetEvent(d->controlEvent);
+ }
+
+ appNames = latestAppNames;
+ Sleep(1000);
+ }
+
+ return 0;
+}
+
DWORD __stdcall appWorker(LPVOID param)
{
WorkerParam *args = reinterpret_cast<WorkerParam *>(param);
SetEvent(d->controlEvent);
return NO_ERROR;
}
+ case SERVICE_CONTROL_DEVICEEVENT: {
+ if (eventType == DBT_DEVICEARRIVAL) {
+ DEV_BROADCAST_DEVICEINTERFACE *header =
+ reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE *>(eventData);
+ if (header->dbcc_classguid != GUID_DEVICE_WINPHONE8_USB)
+ break;
+ d->eventQueue.append(PhoneConnected);
+ SetEvent(d->controlEvent);
+ return NO_ERROR;
+ }
+ break;
+ }
default:
break;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
+// Console message controller
+LRESULT __stdcall control(HWND window, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_DEVICECHANGE && wParam == DBT_DEVICEARRIVAL) {
+ DEV_BROADCAST_DEVICEINTERFACE *header =
+ reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE *>(lParam);
+ if (header->dbcc_classguid == GUID_DEVICE_WINPHONE8_USB) {
+ d->eventQueue.append(PhoneConnected);
+ SetEvent(d->controlEvent);
+ }
+ }
+ return DefWindowProc(window, msg, wParam, lParam);
+}
+
// Console CTRL controller
BOOL __stdcall control(DWORD type)
{
#define bstr(s) _bstr_t((const wchar_t *)s.utf16())
+// This is used by the service to simplify gathering of device data
+extern QStringList xapDeviceNames()
+{
+ if (!coreConServer->initialize()) {
+ while (!coreConServer.exists())
+ Sleep(1);
+ }
+
+ QStringList deviceNames;
+ foreach (const CoreConDevice *device, coreConServer->devices())
+ deviceNames.append(device->name());
+
+ return deviceNames;
+}
+
+// Allows looking up of application names
+extern int xapAppNames(int deviceIndex, QSet<QString> &apps)
+{
+ if (!coreConServer->initialize()) {
+ while (!coreConServer.exists())
+ Sleep(1);
+ }
+
+ CoreConDevice *device = coreConServer->devices().value(deviceIndex, 0);
+ if (!device) {
+ qCWarning(lcD3DService) << "Device at index" << deviceIndex << "not found.";
+ return 1;
+ }
+
+ HRESULT hr;
+ _bstr_t connectionName;
+ ComPtr<ICcConnection> connection;
+ hr = coreConServer->handle()->GetConnection(
+ device->handle(), 5000, NULL, connectionName.GetAddress(), &connection);
+ if (FAILED(hr)) {
+ qCWarning(lcD3DService) << "Unable to initialize connection:"
+ << coreConServer->formatError(hr);
+ return 1;
+ }
+
+ hr = connection->ConnectDevice();
+ // For phones, we wait around for a pin unlock (or a different error)
+ if (!device->isEmulator()) {
+ while (hr == 0x89740006) { // Device is pinlocked
+ qCDebug(lcD3DService) << coreConServer->formatError(hr);
+ Sleep(1000);
+ hr = connection->ConnectDevice();
+ }
+ }
+ if (FAILED(hr)) {
+ qCWarning(lcD3DService) << "Unable to connect to device:"
+ << coreConServer->formatError(hr);
+ return 1;
+ }
+
+ ComPtr<ICcConnection3> connection3;
+ hr = connection.As(&connection3);
+ if (FAILED(hr)) {
+ qCWarning(lcD3DService) << "Unable to obtain connection3 interface:"
+ << coreConServer->formatError(hr);
+ return 1;
+ }
+
+ SAFEARRAY *productIds, *instanceIds;
+ hr = connection3->GetInstalledApplicationIDs(&productIds, &instanceIds);
+ if (FAILED(hr)) {
+ qCWarning(lcD3DService) << "Unable to get installed applications:"
+ << coreConServer->formatError(hr);
+ return 1;
+ }
+ if (productIds && instanceIds) {
+ Q_ASSERT(productIds->rgsabound[0].cElements == instanceIds->rgsabound[0].cElements);
+ for (ulong i = 0; i < productIds->rgsabound[0].cElements; ++i) {
+ LONG indices[] = { i };
+ _bstr_t productId;
+ _bstr_t instanceId;
+ if (SUCCEEDED(SafeArrayGetElement(productIds, indices, productId.GetAddress()))
+ && SUCCEEDED(SafeArrayGetElement(instanceIds, indices, instanceId.GetAddress()))) {
+ apps.insert(QString::fromWCharArray(productId));
+ }
+ }
+ SafeArrayDestroy(productIds);
+ SafeArrayDestroy(instanceIds);
+ return 0;
+ }
+
+ // No installed applications
+ return 0;
+}
+
/* This method runs in its own thread for each CoreCon device/application combo
* the service is currently handling. */
extern int handleXapDevice(int deviceIndex, const QString &app, const QString &localBase, HANDLE runLock)
continue;
}
- int retryCount = 0;
- while (!connected) {
+ if (!connected) {
hr = connection->ConnectDevice();
connected = SUCCEEDED(hr);
if (connected) {
qCWarning(lcD3DService).nospace() << "Connected to " << device->name() << ".";
wasDisconnected = true;
- }
- if (FAILED(hr)) {
- qCDebug(lcD3DService) << "Unable to connect to device. Retrying..."
- << coreConServer->formatError(hr);
- if (++retryCount > 30) {
- qCCritical(lcD3DService) << "Unable to connect to device. Exiting.";
- return 1;
- }
- Sleep(1000);
- }
- }
-
- VARIANT_BOOL isInstalled = false;
- retryCount = 0;
- while (!isInstalled) {
- hr = connection3->IsApplicationInstalled(bstr(app), &isInstalled);
- if (FAILED(hr)) {
- qCCritical(lcD3DService) << "Unable to determine if package is installed:"
- << coreConServer->formatError(hr);
+ } else {
+ qCDebug(lcD3DService).nospace() << "Unable to connect to " << device->name()
+ << ": " << coreConServer->formatError(hr);
return 1;
}
+ }
- if (!isInstalled) {
- qCDebug(lcD3DService) << "Package is not installed. Checking again...";
- if (++retryCount > 30) {
- qCCritical(lcD3DService) << "Package did not materialize within 30 seconds. Exiting.";
- return 1;
- }
- Sleep(1000);
- }
+ VARIANT_BOOL isInstalled;
+ hr = connection3->IsApplicationInstalled(bstr(app), &isInstalled);
+ if (FAILED(hr)) {
+ qCCritical(lcD3DService) << "Unable to determine if package is installed:"
+ << coreConServer->formatError(hr);
+ return 1;
+ }
+ if (!isInstalled) {
+ qCWarning(lcD3DService) << "Package" << app << "is not installed. Exiting worker.";
+ return 1;
}
// Run certain setup steps once per connection