#include "pch.h"
#include "EventHandlerForDevice.h"
#include "App.xaml.h"
using namespace VBA10;
using namespace Platform;
using namespace Concurrency;
using namespace Windows::ApplicationModel;
using namespace Windows::Devices;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Devices::HumanInterfaceDevice;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml;
EventHandlerForDevice^ EventHandlerForDevice::eventHandlerForDevice = nullptr;
SRWLOCK EventHandlerForDevice::srwSingletonCreationLock = SRWLOCK_INIT;
///
/// Enforces the singleton pattern so that there is only one object handling app events
/// as it relates to the UsbDevice because this sample app only supports communicating with one device at a time.
///
/// An instance of EventHandlerForDevice is globally available because the device needs to persist across scenario pages.
///
/// If there is no instance of EventHandlerForDevice created before this property is called,
/// an EventHandlerForDevice will be created.
///
EventHandlerForDevice^ EventHandlerForDevice::Current::get(void)
{
// Acquiring Shared lock is much quicker than an exclusive lock, so avoid exclusive lock unless it's necessary
AcquireSRWLockShared(&srwSingletonCreationLock);
if (eventHandlerForDevice == nullptr)
{
// Multiple threads could have entered here, so acquire exclusive lock to make sure only one instance is created.
ReleaseSRWLockShared(&srwSingletonCreationLock);
AcquireSRWLockExclusive(&srwSingletonCreationLock);
if (eventHandlerForDevice == nullptr)
{
CreateNewEventHandlerForDevice();
}
ReleaseSRWLockExclusive(&srwSingletonCreationLock);
}
else
{
ReleaseSRWLockShared(&srwSingletonCreationLock);
}
return eventHandlerForDevice;
}
///
/// Creates a new instance of EventHandlerForDevice, enables auto reconnect, and uses it as the Current instance.
///
void EventHandlerForDevice::CreateNewEventHandlerForDevice(void)
{
eventHandlerForDevice = ref new EventHandlerForDevice();
}
///
/// This method opens the device using the WinRT Hid API. After the device is opened, we will save the device
/// so that it can be used across scenarios.
///
/// It is important that the FromIdAsync call is made on the UI thread because the consent prompt can only be displayed
/// on the UI thread.
///
/// This method is used to reopen the device after the device reconnects to the computer and when the app resumes.
///
/// Device information of the device to be opened
/// The AQS used to find this device
/// True if the device was successfully opened, false if the device could not be opened for well known reasons.
/// An exception may be thrown if the device could not be opened for extraordinary reasons.
IAsyncOperation^ EventHandlerForDevice::OpenDeviceAsync(Enumeration::DeviceInformation^ deviceInfo)
{
// This sample uses FileAccessMode.ReadWrite to open the device because we do not want other apps opening our device and
// changing the state of our device. FileAccessMode.Read can be used instead.
return create_async([this, deviceInfo]()
{
String^ test = deviceInfo->Id;
return create_task(HidDevice::FromIdAsync(deviceInfo->Id, FileAccessMode::Read))
.then([this, deviceInfo](task deviceTask)
{
bool successfullyOpenedDevice = false;
//NotifyType notificationStatus;
//String^ notificationMessage = nullptr;
// This may throw an exception or return null if we could not open the device
try
{
device = deviceTask.get();
}
catch (Platform::Exception^)
{
device = nullptr;
}
// Device could have been blocked by user or the device has already been opened by another app.
if (device != nullptr)
{
successfullyOpenedDevice = true;
deviceInformation = deviceInfo;
//this->deviceSelector = deviceSelector;
//notificationStatus = NotifyType::StatusMessage;
//notificationMessage = "Device " + deviceInformation->Id + " opened";
if (appSuspendEventToken.Value == 0 || appResumeEventToken.Value == 0)
{
RegisterForAppEvents();
}
// User can block the device after it has been opened in the Settings charm. We can detect this by registering for the
// DeviceAccessInformation->AccessChanged event
if (deviceAccessInformation == nullptr)
{
RegisterForDeviceAccessStatusChange();
}
// Create and register device watcher events for the device to be opened unless we're reopening the device
if (deviceWatcher == nullptr)
{
deviceWatcher = Enumeration::DeviceInformation::CreateWatcher(this->deviceSelector);
RegisterForDeviceWatcherEvents();
}
if (!watcherStarted)
{
// Start the device watcher after we made sure that the device is opened.
StartDeviceWatcher();
}
}
else
{
successfullyOpenedDevice = false;
//notificationStatus = NotifyType::ErrorMessage;
auto deviceAccessStatus = Enumeration::DeviceAccessInformation::CreateFromId(deviceInfo->Id)->CurrentStatus;
if (deviceAccessStatus == DeviceAccessStatus::DeniedByUser)
{
//notificationMessage = "Access to the device was blocked by the user : " + deviceInfo->Id;
}
else if (deviceAccessStatus == DeviceAccessStatus::DeniedBySystem)
{
// This status is most likely caused by app permissions (did not declare the device in the app's package.appxmanifest)
// This status does not cover the case where the device is already opened by another app.
//notificationMessage = "Access to the device was blocked by the system : " + deviceInfo->Id;
}
else
{
// Most likely the device is opened by another app, but cannot be sure
//notificationMessage = "Unknown error, possibly opened by another app : " + deviceInfo->Id;
}
}
//MainPage::Current->NotifyUser(notificationMessage, notificationStatus);
// Notify registered callback handle whether or not the device has been opened
if (deviceConnectedCallback != nullptr)
{
auto deviceConnectedEventArgs = ref new OnDeviceConnectedEventArgs(successfullyOpenedDevice, deviceInfo);
deviceConnectedCallback(this, deviceConnectedEventArgs);
}
return successfullyOpenedDevice;
});
});
}
///
/// Closes the device, stops the device watcher, stops listening for app events, and resets object state to before a device
/// was ever connected.
///
void EventHandlerForDevice::CloseDevice(void)
{
if (IsDeviceConnected)
{
CloseCurrentlyConnectedDevice();
}
if (deviceWatcher != nullptr)
{
if (watcherStarted)
{
StopDeviceWatcher();
UnregisterFromDeviceWatcherEvents();
}
deviceWatcher = nullptr;
}
if (deviceAccessInformation != nullptr)
{
UnregisterFromDeviceAccessStatusChange();
deviceAccessInformation = nullptr;
}
if (appSuspendEventToken.Value != 0 || appResumeEventToken.Value != 0)
{
UnregisterFromAppEvents();
}
deviceInformation = nullptr;
deviceSelector = nullptr;
deviceConnectedCallback = nullptr;
deviceCloseCallback = nullptr;
isEnabledAutoReconnect = true;
}
EventHandlerForDevice::EventHandlerForDevice(void) :
watcherStarted(false),
watcherSuspended(false),
isEnabledAutoReconnect(true)
{
}
EventHandlerForDevice::~EventHandlerForDevice()
{
if (IsDeviceConnected)
{
CloseDevice();
}
}
///
/// This method demonstrates how to close the device properly using the WinRT Hid API.
///
/// When the HidDevice is closing, it will cancel all IO operations that are still pending (not complete).
/// The close will not wait for any IO completion callbacks to be called, so the close call may complete before any of
/// the IO completion callbacks are called.
/// The pending IO operations will still call their respective completion callbacks with either a task
/// cancelled error or the operation completed.
///
void EventHandlerForDevice::CloseCurrentlyConnectedDevice(void)
{
if (device != nullptr)
{
if (deviceCloseCallback != nullptr)
{
deviceCloseCallback(this, deviceInformation);
}
// This closes the handle to the device
delete device;
device = nullptr;
// Save the deviceInformation->Id in case deviceInformation is set to null when closing the
// device
String^ deviceId = deviceInformation->Id;
//MainPage::Current->Dispatcher->RunAsync(
// CoreDispatcherPriority::Normal,
// ref new DispatchedHandler([deviceId]()
//{
// MainPage::Current->NotifyUser(deviceId + " is closed", NotifyType::StatusMessage);
//}));
}
}
///
/// Register for app suspension/resume events. See the comments
/// for the event handlers for more information on what is being done to the device.
///
/// We will also register for when the app exists so that we may close the device handle.
///
void EventHandlerForDevice::RegisterForAppEvents()
{
// This event is raised when the app is exited and when the app is suspended
appSuspendEventToken = App::Current->Suspending += ref new SuspendingEventHandler(this, &EventHandlerForDevice::OnAppSuspension);
appResumeEventToken = App::Current->Resuming += ref new EventHandler