/InTheHand.Net.Personal/InTheHand.Net.Personal/Net.Bluetooth/BluetoothWin32Events.cs

# · C# · 1065 lines · 616 code · 83 blank · 366 comment · 108 complexity · b6a23ae3d9ba718e9ba02b5a421cf131 MD5 · raw file

  1. // 32feet.NET - Personal Area Networking for .NET
  2. //
  3. // Net.Bluetooth.BluetoothWin32Events
  4. //
  5. // Copyright (c) 2008-2011 Alan J.McFarlane, All rights reserved.
  6. // Copyright (c) 2008-2011 In The Hand Ltd, All rights reserved.
  7. // This source code is licensed under the In The Hand Community License - see License.txt
  8. using System;
  9. #if !NETCF
  10. using System.ComponentModel;
  11. using System.Diagnostics;
  12. using System.Diagnostics.CodeAnalysis;
  13. using System.Runtime.InteropServices;
  14. using System.Security.Permissions;
  15. using System.Threading;
  16. #if !NO_WINFORMS
  17. using System.Windows.Forms;
  18. #endif
  19. using InTheHand.Net.Bluetooth.Msft;
  20. using Microsoft.Win32.SafeHandles;
  21. using System.Globalization;
  22. #endif
  23. namespace InTheHand.Net.Bluetooth
  24. {
  25. /// <summary>
  26. /// Provides access to the Bluetooth events from the Microsoft stack on
  27. /// desktop Windows.
  28. /// </summary>
  29. /// -
  30. /// <remarks>
  31. /// <note>Supported only by the Microsoft stack on desktop Windows.
  32. /// </note>
  33. /// <para>The Microsoft Bluetooth stack on Window raises events for various
  34. /// Bluetooth actions. We expose that feature via this class.
  35. /// </para>
  36. /// <para>Currently it raises two types of event: in-range and out-of-range
  37. /// using classes: <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32RadioInRangeEventArgs"/>
  38. /// and <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32RadioOutOfRangeEventArgs"/>.
  39. /// Both have properties <c>Device</c> which return a <c>BluetoothDeviceInfo</c>.
  40. /// Then the in-range event also includes a set of flags, which in
  41. /// Windows XP are: Address, Cod, Name, Paired, Personal, and Connected;
  42. /// more events are available in Windows 7. These events are provided on
  43. /// the <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32RadioInRangeEventArgs"/>
  44. /// class via properties:
  45. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32RadioInRangeEventArgs.CurrentState"/>
  46. /// and <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32RadioInRangeEventArgs.PreviousState"/>,
  47. /// and also <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32RadioInRangeEventArgs.GainedStates"/> etc.
  48. /// </para>
  49. /// <para>To see the events get an instance of this class via its method
  50. /// <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32Events.GetInstance"/>.
  51. /// Then one should register for the events on that instance and keep a
  52. /// reference to it.
  53. /// </para>
  54. /// <para>Note that just being in range is not enough for
  55. /// devices to know that the other is present. Without running device
  56. /// discovery or a connection attempt the two devices will not see each
  57. /// other. Note however that Windows XP also does not raise events when
  58. /// running device discovery (inquiry), this is fixed in Windows 7
  59. /// (probably Vista). See
  60. /// <see href="http://32feetnetdev.wordpress.com/2010/11/15/device-discovery-improvements-on-msftwin32/">32feet blog: Device Discovery improvements on MSFT+Win32</see>
  61. /// for more information.
  62. /// </para>
  63. ///
  64. /// <para>For example when connecting and disconnecting on Windows XP to
  65. /// another device that is not paired we see:
  66. /// </para>
  67. /// <example>
  68. /// <code lang="none">
  69. /// 12:23:48.9582648: InRange 000A3A6865BB 'joe',
  70. /// now 'Address, Cod, Name, Connected'
  71. /// was 'Address, Cod, Name'.
  72. /// 12:24:16.8009456: InRange 000A3A6865BB 'joe',
  73. /// now 'Address, Cod, Name'
  74. /// was 'Address, Cod, Name, Connected'.}}
  75. /// </code>
  76. /// </example>
  77. /// <para>For example when connecting and then disconnecting on Windows 7
  78. /// to another v2.1 device that is paired with we see:
  79. /// </para>
  80. /// <example>
  81. /// <code lang="none">
  82. /// 20:53:25.5605469: InRange 00190E02C916 'alanlt2ws',
  83. /// now 'Address, Cod, Name, Paired, Personal, Connected, SspSupported, SspPaired, Rssi, Eir'
  84. /// was 'Address, Cod, Name, Paired, Personal, SspSupported, SspPaired, Rssi, Eir'.
  85. /// 20:53:27.7949219: InRange 00190E02C916 'fred',
  86. /// now 'Address, Cod, Name, Paired, Personal, SspSupported, SspPaired, Rssi, Eir'
  87. /// was 'Address, Cod, Name, Paired, Personal, Connected, SspSupported, SspPaired, Rssi, Eir'.}}
  88. /// </code>
  89. /// </example>
  90. /// </remarks>
  91. public class BluetoothWin32Events : IDisposable
  92. {
  93. #if NO_WINFORMS
  94. public BluetoothWin32Events()
  95. {
  96. throw new NotSupportedException("BluetoothWin32Events is not supported when built with NO_WINFORMS.");
  97. }
  98. public BluetoothWin32Events(BluetoothRadio microsoftWin32BluetoothRadio)
  99. : this()
  100. {
  101. }
  102. public static BluetoothWin32Events GetInstance()
  103. {
  104. return new BluetoothWin32Events();
  105. }
  106. /// <summary>
  107. /// &#x201C;This message is sent when any of the following attributes
  108. /// of a remote Bluetooth device has changed: the device has been
  109. /// discovered, the class of device, name, connected state, or device
  110. /// remembered state. This message is also sent when these attributes
  111. /// are set or cleared.&#x201D;
  112. /// </summary>
  113. public event EventHandler<BluetoothWin32RadioInRangeEventArgs> InRange = delegate { };
  114. /// <summary>
  115. /// &#x201C;This message is sent when a previously discovered device
  116. /// has not been found after the completion of the last inquiry.&#x201D;
  117. /// </summary>
  118. public event EventHandler<BluetoothWin32RadioOutOfRangeEventArgs> OutOfRange = delegate { };
  119. /// <summary>
  120. /// Releases the resources used by the instance.
  121. /// </summary>
  122. public void Dispose()
  123. {
  124. }
  125. #else
  126. #if !NETCF
  127. //static void Marshal_PtrToStructure<T>(IntPtr ptr, out T result)
  128. // where T : struct
  129. //{
  130. // result = (T)Marshal.PtrToStructure(ptr, typeof(T));
  131. //}
  132. static T Marshal_PtrToStructure<T>(IntPtr ptr)
  133. where T : struct
  134. {
  135. var result = (T)Marshal.PtrToStructure(ptr, typeof(T));
  136. return result;
  137. }
  138. //--------
  139. static readonly Int32 _OffsetOfData;
  140. //
  141. static WeakReference/*BluetoothWin32Events>*/ _instance;
  142. //
  143. BtEventsForm _form;
  144. Thread _formThread;
  145. ManualResetEvent _inited;
  146. Exception _formThreadException;
  147. readonly IntPtr _bluetoothRadioHandle;
  148. #endif
  149. #if !NETCF
  150. static BluetoothWin32Events()
  151. {
  152. _OffsetOfData = Marshal.OffsetOf(typeof(DEV_BROADCAST_HANDLE__withData),
  153. "dbch_data__0").ToInt32();
  154. #if DEBUG
  155. if (IntPtr.Size == 4) {
  156. Debug.Assert(40 == _OffsetOfData);
  157. } else {
  158. Debug.Assert(52 == _OffsetOfData);
  159. }
  160. #endif
  161. }
  162. #endif
  163. /// <summary>
  164. /// Initialise an instance of the class.
  165. /// </summary>
  166. /// -
  167. /// <remarks>
  168. /// <para>Consider using the method <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32Events.GetInstance"/>
  169. /// instead of calling this constructor.
  170. /// </para>
  171. /// </remarks>
  172. public BluetoothWin32Events()
  173. {
  174. #if NETCF
  175. throw new NotSupportedException("BluetoothWin32Events is Win32 only.");
  176. #else
  177. BluetoothRadio microsoftWin32BluetoothRadio = null;
  178. foreach (var cur in BluetoothRadio.AllRadios) {
  179. if (cur.SoftwareManufacturer == Manufacturer.Microsoft) {
  180. microsoftWin32BluetoothRadio = cur;
  181. break;
  182. }
  183. }//for
  184. if (microsoftWin32BluetoothRadio == null) {
  185. throw new InvalidOperationException("There is no Radio using the Microsoft Bluetooth stack.");
  186. } else {
  187. IntPtr bluetoothRadioHandle = microsoftWin32BluetoothRadio.Handle;
  188. if (bluetoothRadioHandle == IntPtr.Zero)
  189. throw new ArgumentException("The bluetoothRadioHandle may not be NULL.");
  190. _bluetoothRadioHandle = bluetoothRadioHandle;
  191. Start();
  192. }
  193. #endif
  194. }
  195. /// <summary>
  196. /// Initialise an instance of the class for the specified radio.
  197. /// </summary>
  198. /// -
  199. /// <param name="microsoftWin32BluetoothRadio">
  200. /// The radio to listen for events from.
  201. /// Must be non-null and a MSFT+Win32 stack radio.
  202. /// </param>
  203. /// -
  204. /// <remarks>Note that since the Microsoft stack supports only one radio
  205. /// (controller) there is lilely no benefit in calling this constructor
  206. /// as opposed to the other constructor or method
  207. /// <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32Events.GetInstance"/>.
  208. /// </remarks>
  209. public BluetoothWin32Events(BluetoothRadio microsoftWin32BluetoothRadio)
  210. {
  211. #if NETCF
  212. throw new NotSupportedException("BluetoothWin32Events is Win32 only.");
  213. #else
  214. if (microsoftWin32BluetoothRadio == null) throw new ArgumentNullException("microsoftWin32BluetoothRadio");
  215. if (microsoftWin32BluetoothRadio.SoftwareManufacturer != Manufacturer.Microsoft)
  216. throw new ArgumentException("The specified Radio does not use the Microsoft Bluetooth stack.");
  217. IntPtr bluetoothRadioHandle = microsoftWin32BluetoothRadio.Handle;
  218. if (bluetoothRadioHandle == IntPtr.Zero)
  219. throw new ArgumentException("The bluetoothRadioHandle may not be NULL.");
  220. _bluetoothRadioHandle = bluetoothRadioHandle;
  221. Start();
  222. #endif
  223. }
  224. //----
  225. /// <summary>
  226. /// Gets a possible shared instance of this class.
  227. /// </summary>
  228. /// -
  229. /// <remarks>
  230. /// <para>If more that one piece of code is using this class then there
  231. /// is no need for each to have a private instance. This method allows
  232. /// them to access a shared instance. When first called it creates a
  233. /// new instance and keeps a weak-reference to it. Subsequent callers
  234. /// will then get the same instance. The instance is kept alive only
  235. /// as long as at least one caller keeps a reference to it. If no
  236. /// references are kept then the instance will be deleted and a new
  237. /// instance will be created when this method is next called.
  238. /// </para>
  239. /// </remarks>
  240. /// -
  241. /// <returns>An instance of this class.
  242. /// </returns>
  243. public static BluetoothWin32Events GetInstance()
  244. {
  245. #if NETCF
  246. throw new NotSupportedException("BluetoothWin32Events is Win32 only.");
  247. #else
  248. var weakRef = _instance;
  249. object target;
  250. if (weakRef == null || (target = weakRef.Target) == null) {
  251. target = new BluetoothWin32Events();
  252. _instance = new WeakReference(target);
  253. } else {
  254. target.ToString(); // COVERAGE
  255. }
  256. return (BluetoothWin32Events)target;
  257. #endif
  258. }
  259. //----
  260. void RaiseInRange(BluetoothWin32RadioInRangeEventArgs e)
  261. {
  262. OnInRange(e);
  263. }
  264. /// <summary>
  265. /// Raises the <see cref="E:InTheHand.Net.Bluetooth.BluetoothWin32Events.InRange"/> event.
  266. /// </summary>
  267. /// -
  268. /// <param name="e">A <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32RadioInRangeEventArgs"/>
  269. /// that contains the event data.
  270. /// </param>
  271. protected void OnInRange(BluetoothWin32RadioInRangeEventArgs e)
  272. {
  273. InRange(this, e);
  274. }
  275. /// <summary>
  276. /// &#x201C;This message is sent when any of the following attributes
  277. /// of a remote Bluetooth device has changed: the device has been
  278. /// discovered, the class of device, name, connected state, or device
  279. /// remembered state. This message is also sent when these attributes
  280. /// are set or cleared.&#x201D;
  281. /// </summary>
  282. public event EventHandler<BluetoothWin32RadioInRangeEventArgs> InRange = delegate { };
  283. void RaiseOutOfRange(BluetoothWin32RadioOutOfRangeEventArgs e)
  284. {
  285. OnOutOfRange(e);
  286. }
  287. /// <summary>
  288. /// Raises the <see cref="E:InTheHand.Net.Bluetooth.BluetoothWin32Events.OutOfRange"/> event.
  289. /// </summary>
  290. /// -
  291. /// <param name="e">A <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32RadioOutOfRangeEventArgs"/>
  292. /// that contains the event data.
  293. /// </param>
  294. protected void OnOutOfRange(BluetoothWin32RadioOutOfRangeEventArgs e)
  295. {
  296. OutOfRange(this, e);
  297. }
  298. /// <summary>
  299. /// &#x201C;This message is sent when a previously discovered device
  300. /// has not been found after the completion of the last inquiry.&#x201D;
  301. /// </summary>
  302. public event EventHandler<BluetoothWin32RadioOutOfRangeEventArgs> OutOfRange = delegate { };
  303. //----
  304. #if !NETCF
  305. private void Start()
  306. {
  307. if (Application.MessageLoop) {
  308. StartUseExistingAppLoop();
  309. } else {
  310. StartOwnAppLoop();
  311. }
  312. }
  313. private void StartUseExistingAppLoop()
  314. {
  315. CreateForm();
  316. //form.Show(); // Not necessary -- enable for debugging only.
  317. }
  318. private void StartOwnAppLoop()
  319. {
  320. bool success = false;
  321. try {
  322. using (_inited = new System.Threading.ManualResetEvent(false)) {
  323. _formThread = new Thread(MessageLoop_Runner);
  324. _formThread.IsBackground = true;
  325. _formThread.SetApartmentState(ApartmentState.STA);
  326. _formThread.Start(this);
  327. _inited.WaitOne();
  328. }
  329. _inited = null;
  330. Thread.MemoryBarrier();
  331. if (_formThreadException != null)
  332. throw _formThreadException;
  333. _formThreadException = null;
  334. success = true;
  335. } finally {
  336. if (!success) {
  337. if (_form != null) {
  338. //TODO MUST be on UI thread!! form.Dispose();
  339. //form = null;
  340. }
  341. }
  342. }
  343. }
  344. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
  345. Justification = "Is rethrown on the creator.")]
  346. void MessageLoop_Runner(object state)
  347. {
  348. try {
  349. ApartmentState a = _formThread.GetApartmentState();
  350. Debug.Assert(a == ApartmentState.STA, "!STA!");
  351. CreateForm();
  352. //form.Show(); // Not necessary -- enable for debugging only.
  353. _inited.Set(); // Initial success
  354. Application.Run();
  355. } catch (Exception ex) {
  356. _formThreadException = ex;
  357. Thread.MemoryBarrier();
  358. } finally {
  359. if (_form != null)
  360. _form.Dispose();
  361. if (_inited != null)
  362. _inited.Set();
  363. }
  364. }
  365. private void CreateForm()
  366. {
  367. _form = new BtEventsForm(this);
  368. _form.btRegister(_bluetoothRadioHandle);
  369. }
  370. #endif
  371. //----
  372. #if NETCF
  373. /// <summary>
  374. /// Releases the resources used by the instance.
  375. /// </summary>
  376. public void Dispose()
  377. {
  378. }
  379. #else
  380. /// <summary>
  381. /// Releases the resources used by the instance.
  382. /// </summary>
  383. public void Dispose()
  384. {
  385. Dispose(true);
  386. GC.SuppressFinalize(this);
  387. }
  388. /// <summary>
  389. /// Releases the unmanaged resources used by the instance
  390. /// and optionally releases the managed resources.
  391. /// </summary>
  392. [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_form",
  393. Justification = "It is disposed on the UI thread.")]
  394. protected virtual void Dispose(bool disposing)
  395. {
  396. if (disposing) {
  397. if (_form != null) {
  398. MethodInvoker dlgt = DisposeForm;
  399. _form.Invoke(dlgt);
  400. _form = null;
  401. //
  402. if (_formThread != null) {
  403. int bigTimeoutAllowForDebugging = 60 * 1000;
  404. bool terminated = _formThread.Join(bigTimeoutAllowForDebugging);
  405. Debug.Assert(terminated, "!terminated");
  406. if (_formThreadException != null)
  407. throw _formThreadException;
  408. _formThread = null;
  409. }
  410. }
  411. }
  412. }
  413. void DisposeForm()
  414. {
  415. // Need to call these from the UI thread
  416. Debug.Assert(!_form.InvokeRequired, "DisposeForm() not on Form-UI thread.");
  417. _form.Dispose();
  418. if (_formThread != null) { // Using our own app loop.
  419. Application.ExitThread();
  420. }
  421. }
  422. //--------------------------------------------------------------
  423. sealed class RegisterDeviceNotificationSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
  424. {
  425. public RegisterDeviceNotificationSafeHandle()
  426. : base(true) //ownsHandle
  427. {
  428. }
  429. protected override bool ReleaseHandle()
  430. {
  431. return btUnregister(this.handle);
  432. }
  433. internal static bool btUnregister(IntPtr hDevNotification)
  434. {
  435. System.Diagnostics.Debug.Assert(hDevNotification != IntPtr.Zero, "btUnregister, not registered.");
  436. bool success = UnsafeNativeMethods.UnregisterDeviceNotification(hDevNotification);
  437. hDevNotification = IntPtr.Zero;
  438. System.Diagnostics.Debug.Assert(success, "UnregisterDeviceNotification success false.");
  439. return success;
  440. }
  441. }
  442. //--------------------------------------------------------------
  443. sealed class BtEventsForm : Form
  444. {
  445. BluetoothWin32Events _parent;
  446. //----------------
  447. RegisterDeviceNotificationSafeHandle _hDevNotification;
  448. internal BtEventsForm(BluetoothWin32Events parent)
  449. {
  450. _parent = parent;
  451. this.Text = "32feet.NET WM_DEVICECHANGE Window";
  452. }
  453. protected override void Dispose(bool disposing)
  454. {
  455. try {
  456. if (_hDevNotification != null && !_hDevNotification.IsClosed) {
  457. _hDevNotification.Close();
  458. }
  459. } finally {
  460. base.Dispose(disposing);
  461. }
  462. }
  463. [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)
  464. // Keep FxCop happy, for
  465. ]
  466. internal void btRegister(IntPtr bluetoothRadioHandle)
  467. {
  468. Debug.Assert(_hDevNotification == null, "btRegister, already set.");
  469. Debug.Assert(_hDevNotification == null || _hDevNotification.IsInvalid, "btRegister, already registered.");
  470. IntPtr windowHandle = this.Handle;
  471. DEV_BROADCAST_HANDLE devHandle = new DEV_BROADCAST_HANDLE(bluetoothRadioHandle);
  472. RegisterDeviceNotificationSafeHandle hDevNotification
  473. = UnsafeNativeMethods.RegisterDeviceNotification_SafeHandle(windowHandle,
  474. ref devHandle, RegisterDeviceNotificationFlags.DEVICE_NOTIFY_WINDOW_HANDLE);
  475. if (hDevNotification.IsInvalid) {
  476. throw new Win32Exception(/*error code from GetLastError*/);
  477. }
  478. _hDevNotification = hDevNotification;
  479. }
  480. //----------------
  481. [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand,
  482. Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
  483. protected override void WndProc(ref Message m)
  484. {
  485. _parent.HandleMessage(ref m);
  486. base.WndProc(ref m);
  487. }
  488. }//class Form
  489. //--
  490. void HandleMessage(ref Message m)
  491. {
  492. WindowMessageId msgId = (WindowMessageId)m.Msg;
  493. if (msgId == WindowMessageId.DeviceChange) {
  494. Dbt subId = (Dbt)m.WParam;
  495. if (subId == Dbt.CustomEvent
  496. || subId == Dbt.DeviceArrival
  497. || subId == Dbt.DeviceQueryRemove
  498. || subId == Dbt.DeviceQueryRemoveFailed
  499. || subId == Dbt.DeviceRemoveComplete
  500. || subId == Dbt.DeviceRemovePending
  501. || subId == Dbt.DeviceTypeSpecific) {
  502. DoBroadcastHdr(m);
  503. }
  504. } else if (msgId == WindowMessageId.ActivateApp) {
  505. }
  506. }
  507. //----------------
  508. private void DoBroadcastHdr(Message m)
  509. {
  510. //IntPtr pXXX;
  511. String text = String.Empty;
  512. DEV_BROADCAST_HDR hdr = (DEV_BROADCAST_HDR)m.GetLParam(typeof(DEV_BROADCAST_HDR));
  513. if (hdr.dbch_devicetype == DbtDevTyp.Port) {
  514. DoDevTypPort(ref m, ref text, ref hdr);
  515. } else if (hdr.dbch_devicetype == DbtDevTyp.Handle) {
  516. DoDevTypHandle(ref m, ref text);
  517. }
  518. }
  519. private void DoDevTypHandle(ref Message m, ref String text)
  520. {
  521. #if DEBUG
  522. WindowsBluetoothDeviceInfo dev = null;
  523. #endif
  524. DEV_BROADCAST_HANDLE hdrHandle = (DEV_BROADCAST_HANDLE)m.GetLParam(typeof(DEV_BROADCAST_HANDLE));
  525. var pData = Utils.Pointers.Add(m.LParam, _OffsetOfData);
  526. if (BluetoothDeviceNotificationEvent.BthPortDeviceInterface == hdrHandle.dbch_eventguid) {
  527. text += "GUID_BTHPORT_DEVICE_INTERFACE";
  528. } else if (BluetoothDeviceNotificationEvent.RadioInRange == hdrHandle.dbch_eventguid) {
  529. text += "GUID_BLUETOOTH_RADIO_IN_RANGE";
  530. BTH_RADIO_IN_RANGE inRange
  531. = (BTH_RADIO_IN_RANGE)Marshal.PtrToStructure(pData, typeof(BTH_RADIO_IN_RANGE));
  532. text += String.Format(System.Globalization.CultureInfo.InvariantCulture,
  533. " 0x{0:X12}", inRange.deviceInfo.address);
  534. text += String.Format(System.Globalization.CultureInfo.InvariantCulture,
  535. " is ({0}) 0x{0:X}", inRange.deviceInfo.flags);
  536. text += String.Format(System.Globalization.CultureInfo.InvariantCulture,
  537. " was ({0}) 0x{0:X}", inRange.previousDeviceFlags);
  538. var bdi0 = BLUETOOTH_DEVICE_INFO.Create(inRange.deviceInfo);
  539. var e = BluetoothWin32RadioInRangeEventArgs.Create(
  540. inRange.previousDeviceFlags,
  541. inRange.deviceInfo.flags, bdi0);
  542. #if DEBUG
  543. dev = new WindowsBluetoothDeviceInfo(bdi0);
  544. Debug.WriteLine("InRange: " + dev.DeviceAddress);
  545. #endif
  546. RaiseInRange(e);
  547. } else if (BluetoothDeviceNotificationEvent.RadioOutOfRange == hdrHandle.dbch_eventguid) {
  548. BTH_RADIO_OUT_OF_RANGE outOfRange
  549. = (BTH_RADIO_OUT_OF_RANGE)Marshal.PtrToStructure(pData, typeof(BTH_RADIO_OUT_OF_RANGE));
  550. text += "GUID_BLUETOOTH_RADIO_OUT_OF_RANGE";
  551. text += String.Format(System.Globalization.CultureInfo.InvariantCulture,
  552. " 0x{0:X12}", outOfRange.deviceAddress);
  553. var e = BluetoothWin32RadioOutOfRangeEventArgs.Create(
  554. outOfRange.deviceAddress);
  555. Debug.WriteLine("OutOfRange: " + outOfRange.deviceAddress);
  556. RaiseOutOfRange(e);
  557. } else if (BluetoothDeviceNotificationEvent.PinRequest == hdrHandle.dbch_eventguid) {
  558. text += "GUID_BLUETOOTH_PIN_REQUEST";
  559. // "This message should be ignored by the application.
  560. // If the application must receive PIN requests, the
  561. // BluetoothRegisterForAuthentication function should be used."
  562. } else if (BluetoothDeviceNotificationEvent.L2capEvent == hdrHandle.dbch_eventguid) {
  563. text += "GUID_BLUETOOTH_L2CAP_EVENT";
  564. // struct BTH_L2CAP_EVENT_INFO {
  565. // BTH_ADDR bthAddress; USHORT psm; UCHAR connected; UCHAR initiated; }
  566. #if DEBUG
  567. var l2capE = Marshal_PtrToStructure<BTH_L2CAP_EVENT_INFO>(pData);
  568. Debug.WriteLine(string.Format(CultureInfo.InvariantCulture,
  569. "L2CAP_EVENT: addr: {0:X}, psm: {1}, conn: {2}, init'd: {3}",
  570. l2capE.bthAddress, l2capE.psm, l2capE.connected, l2capE.initiated));
  571. #endif
  572. } else if (BluetoothDeviceNotificationEvent.HciEvent == hdrHandle.dbch_eventguid) {
  573. text += "GUID_BLUETOOTH_HCI_EVENT";
  574. // struct BTH_HCI_EVENT_INFO {
  575. // BTH_ADDR bthAddress; UCHAR connectionType; UCHAR connected; }
  576. #if DEBUG
  577. var hciE = Marshal_PtrToStructure<BTH_HCI_EVENT_INFO>(pData);
  578. Debug.WriteLine(string.Format(CultureInfo.InvariantCulture,
  579. "HCI_EVENT: addr: {0:X}, type: {1}, conn: {2}",
  580. hciE.bthAddress, hciE.connectionType, hciE.connected));
  581. #endif
  582. }
  583. // -- New somewhere after WinXP.
  584. else if (BluetoothDeviceNotificationEvent.AuthenticationRequestEvent == hdrHandle.dbch_eventguid) {
  585. text += "GUID_BLUETOOTH_AUTHENTICATION_REQUEST";
  586. // Same content as BluetoothRegisterForAuthenticationEx
  587. } else if (BluetoothDeviceNotificationEvent.KeyPressEvent == hdrHandle.dbch_eventguid) {
  588. text += "GUID_BLUETOOTH_KEYPRESS_EVENT";
  589. // struct BTH_HCI_KEYPRESS_INFO {
  590. // BTH_ADDR BTH_ADDR; UCHAR NotificationType; // HCI_KEYPRESS_XXX value }
  591. } else if (BluetoothDeviceNotificationEvent.HciVendorEvent == hdrHandle.dbch_eventguid) {
  592. text += "GUID_BLUETOOTH_HCI_VENDOR_EVENT";
  593. }
  594. // -- Unknown
  595. else {
  596. text += "Unknown event: " + hdrHandle.dbch_eventguid;
  597. }
  598. Debug.WriteLine("Text: " + text);
  599. }
  600. private static void DoDevTypPort(ref Message m, ref String text, ref DEV_BROADCAST_HDR hdr)
  601. {
  602. #if false==false
  603. text += "Port: ";
  604. //DEV_BROADCAST_PORT hdrPort = (DEV_BROADCAST_PORT)m.GetLParam(typeof(DEV_BROADCAST_PORT));
  605. const int OffsetOfStringMember = 12; // ints not pointers, so fixed size.
  606. System.Diagnostics.Debug.Assert(OffsetOfStringMember
  607. == Marshal.OffsetOf(typeof(DEV_BROADCAST_PORT), "____dbcp_name").ToInt64());
  608. int cbSpaceForString = hdr.dbch_size - OffsetOfStringMember;
  609. String str;
  610. if (cbSpaceForString > 0) {
  611. Int64 startPtrXX = OffsetOfStringMember + m.LParam.ToInt64();
  612. IntPtr startPtr = (IntPtr)startPtrXX;
  613. // We won't use the length parameter in method PtrToStringUni as the
  614. // string we have here is null-terminated and often has trailing nulls
  615. // also, using the length overload would force their inclusion.
  616. str = System.Runtime.InteropServices.Marshal.PtrToStringUni(startPtr);
  617. } else {
  618. str = null;
  619. }
  620. text += str;
  621. #endif
  622. }
  623. //--------------------------------------------------------------
  624. // From bth_def.h
  625. //--------------------------------------------------------------------------
  626. static class BluetoothDeviceNotificationEvent
  627. {
  628. /// <summary>
  629. /// &#x201C;&#x201D;
  630. /// </summary>
  631. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Bth")]
  632. public static readonly Guid BthPortDeviceInterface = new Guid("{0850302A-B344-4fda-9BE9-90576B8D46F0}");
  633. /// <summary>
  634. /// &#x201C;This message is sent when any of the following attributes of a remote Bluetooth device has changed:
  635. /// the device has been discovered, the class of device, name, connected state, or device remembered state.
  636. /// This message is also sent when these attributes are set or cleared.&#x201D;
  637. /// </summary>
  638. public static readonly Guid RadioInRange = new Guid("{EA3B5B82-26EE-450E-B0D8-D26FE30A3869}");
  639. /// <summary>
  640. /// &#x201C;This message is sent when a previously discovered device has not been found after the completion of the last inquiry.
  641. /// This message will not be sent for remembered devices.
  642. /// The BTH_ADDRESS structure is the address of the device that was not found.&#x201D;
  643. /// </summary>
  644. public static readonly Guid RadioOutOfRange = new Guid("{E28867C9-C2AA-4CED-B969-4570866037C4}");
  645. /// <summary>
  646. /// &#x201C;This message should be ignored by the application.
  647. /// If the application must receive PIN requests, the BluetoothRegisterForAuthentication function should be used.&#x201D;
  648. /// </summary>
  649. public static readonly Guid PinRequest = new Guid("{BD198B7C-24AB-4B9A-8C0D-A8EA8349AA16}");
  650. /// <summary>
  651. /// &#x201C;This message is sent when an L2CAP channel between the local radio and a remote Bluetooth device has been established or terminated.
  652. /// For L2CAP channels that are multiplexers, such as RFCOMM, this message is only sent when the underlying channel is established,
  653. /// not when each multiplexed channel, such as an RFCOMM channel, is established or terminated.&#x201D;
  654. /// </summary>
  655. public static readonly Guid L2capEvent = new Guid("{7EAE4030-B709-4AA8-AC55-E953829C9DAA}");
  656. /// <summary>
  657. /// &#x201C;This message is sent when a remote Bluetooth device connects or disconnects at the ACL level.&#x201D;
  658. /// </summary>
  659. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Hci")]
  660. public static readonly Guid HciEvent = new Guid("{FC240062-1541-49BE-B463-84C4DCD7BF7F}");
  661. // New somewhere after WinXP.
  662. public static readonly Guid AuthenticationRequestEvent = new Guid("{5DC9136D-996C-46DB-84F5-32C0A3F47352}");
  663. public static readonly Guid KeyPressEvent = new Guid("{D668DFCD-0F4E-4EFC-BFE0-392EEEC5109C}");
  664. public static readonly Guid HciVendorEvent = new Guid("{547247e6-45bb-4c33-af8c-c00efe15a71d}");
  665. }
  666. struct BTH_RADIO_IN_RANGE
  667. {
  668. internal BTH_DEVICE_INFO deviceInfo;
  669. internal BluetoothDeviceInfoProperties previousDeviceFlags;
  670. }
  671. struct BTH_RADIO_OUT_OF_RANGE
  672. {
  673. internal Int64 deviceAddress;
  674. }
  675. /// <summary>
  676. /// Buffer associated with GUID_BLUETOOTH_L2CAP_EVENT
  677. /// </summary>
  678. struct BTH_L2CAP_EVENT_INFO
  679. {
  680. /// <summary>
  681. /// Remote radio address which the L2CAP event is associated with
  682. /// </summary>
  683. internal readonly Int64 bthAddress;
  684. /// <summary>
  685. /// The PSM that is either being connected to or disconnected from
  686. /// </summary>
  687. internal readonly ushort psm;
  688. /// <summary>
  689. /// If != 0, then the channel has just been established. If == 0, then the
  690. /// channel has been destroyed. Notifications for a destroyed channel will
  691. /// only be sent for channels successfully established.
  692. /// </summary>
  693. [MarshalAs(UnmanagedType.U1)]
  694. internal readonly bool connected;
  695. /// <summary>
  696. /// If != 0, then the local host iniated the l2cap connection. If == 0, then
  697. /// the remote host initated the connection. This field is only valid if
  698. /// connect is != 0.
  699. /// </summary>
  700. [MarshalAs(UnmanagedType.U1)]
  701. internal readonly bool initiated;
  702. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "shutUpCompiler")]
  703. internal BTH_L2CAP_EVENT_INFO(Version shutUpCompiler)
  704. {
  705. this.bthAddress = 0;
  706. this.psm = 0;
  707. this.connected = false;
  708. this.initiated = false;
  709. }
  710. }
  711. enum HCI_CONNNECTION_TYPE : byte
  712. {
  713. ACL = (1),
  714. SCO = (2),
  715. LE = (3), // Added in Windows 8
  716. }
  717. /// <summary>
  718. /// Buffer associated with GUID_BLUETOOTH_HCI_EVENT
  719. /// </summary>
  720. struct BTH_HCI_EVENT_INFO
  721. {
  722. /// <summary>
  723. /// Remote radio address which the HCI event is associated with
  724. /// </summary>
  725. internal readonly Int64 bthAddress;
  726. /// <summary>
  727. /// HCI_CONNNECTION_TYPE_XXX value
  728. /// </summary>
  729. internal readonly HCI_CONNNECTION_TYPE connectionType;
  730. /// <summary>
  731. /// If != 0, then the underlying connection to the remote radio has just
  732. /// been estrablished. If == 0, then the underlying conneciton has just been
  733. /// destroyed.
  734. /// </summary>
  735. [MarshalAs(UnmanagedType.U1)]
  736. internal readonly bool connected;
  737. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "shutUpCompiler")]
  738. internal BTH_HCI_EVENT_INFO(Version shutUpCompiler)
  739. {
  740. this.bthAddress = 0;
  741. this.connectionType = 0;
  742. this.connected = false;
  743. }
  744. }
  745. //--------
  746. enum RegisterDeviceNotificationFlags
  747. {
  748. DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000,
  749. DEVICE_NOTIFY_SERVICE_HANDLE = 0x00000001,
  750. DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x00000004
  751. };
  752. static class UnsafeNativeMethods
  753. {
  754. //[Obsolete("SafeHandle one please!")]
  755. //[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true,
  756. // EntryPoint = "RegisterDeviceNotification")]
  757. //internal static extern IntPtr RegisterDeviceNotification_IntPtr(
  758. // IntPtr hRecipient,
  759. // ref DEV_BROADCAST_HANDLE notificationFilter,
  760. // RegisterDeviceNotificationFlags flags
  761. // );
  762. [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true,
  763. EntryPoint = "RegisterDeviceNotification")]
  764. internal static extern RegisterDeviceNotificationSafeHandle RegisterDeviceNotification_SafeHandle(
  765. IntPtr hRecipient,
  766. ref DEV_BROADCAST_HANDLE notificationFilter,
  767. RegisterDeviceNotificationFlags flags
  768. );
  769. [DllImport("User32.dll", SetLastError = true)]
  770. [return: MarshalAs(UnmanagedType.Bool)]
  771. internal static extern bool UnregisterDeviceNotification(IntPtr handle);
  772. }
  773. //--------------------------------------------------------------------------
  774. // From DBT.h
  775. //--------------------------------------------------------------------------
  776. enum WindowMessageId
  777. {
  778. DeviceChange = 0x0219,
  779. //
  780. ActivateApp = 0x1C,
  781. }//class
  782. enum Dbt
  783. {
  784. /// <summary>
  785. /// A request to change the current configuration (dock or undock) has been canceled.
  786. /// </summary>
  787. ConfigChangeCanceled = 0x0019,
  788. /// <summary>
  789. /// The current configuration has changed, due to a dock or undock.
  790. /// </summary>
  791. ConfigChanged = 0x0018,
  792. /// <summary>
  793. /// A custom event has occurred.
  794. /// </summary>
  795. /// <remarks>Windows NT 4.0 and Windows 95:  This value is not supported.</remarks>
  796. CustomEvent = 0x8006,
  797. /// <summary>
  798. /// A device or piece of media has been inserted and is now available.
  799. /// </summary>
  800. DeviceArrival = 0x8000,
  801. /// <summary>
  802. /// Permission is requested to remove a device or piece of media. Any application can deny this request and cancel the removal.
  803. /// </summary>
  804. DeviceQueryRemove = 0x8001,
  805. /// <summary>
  806. /// A request to remove a device or piece of media has been canceled.
  807. /// </summary>
  808. DeviceQueryRemoveFailed = 0x8002,
  809. /// <summary>
  810. /// A device or piece of media has been removed.
  811. /// </summary>
  812. DeviceRemoveComplete = 0x8004,
  813. /// <summary>
  814. /// A device or piece of media is about to be removed. Cannot be denied.
  815. /// </summary>
  816. DeviceRemovePending = 0x8003,
  817. /// <summary>
  818. /// A device-specific event has occurred.
  819. /// </summary>
  820. DeviceTypeSpecific = 0x8005,
  821. /// <summary>
  822. /// A device has been added to or removed from the system.
  823. /// </summary>
  824. /// <remarks>Windows NT 4.0 and Windows Me/98/95:  This value is not supported.</remarks>
  825. DevNodesChanged = 0x0007,
  826. /// <summary>
  827. /// Permission is requested to change the current configuration (dock or undock).
  828. /// </summary>
  829. QueryChangeConfig = 0x0017,
  830. /// <summary>
  831. /// The meaning of this message is user-defined.
  832. /// </summary>
  833. UserDefined = 0xFFFF,
  834. }//class
  835. enum DbtDevTyp : uint
  836. {
  837. /// <summary>
  838. /// oem-defined device type
  839. /// </summary>
  840. Oem = 0x00000000,
  841. /// <summary>
  842. /// devnode number
  843. /// /// </summary>
  844. DevNode = 0x00000001,
  845. /// <summary>
  846. ///
  847. /// </summary>
  848. Volume = 0x00000002,
  849. /// <summary>
  850. /// l
  851. /// </summary>
  852. Port = 0x00000003,
  853. /// <summary>
  854. /// network resource
  855. /// </summary>
  856. Network = 0x00000004,
  857. /// <summary>
  858. /// device interface class
  859. /// </summary>
  860. DeviceInterface = 0x00000005,
  861. /// <summary>
  862. /// file system handle
  863. /// </summary>
  864. Handle = 0x00000006
  865. }//enum
  866. struct DEV_BROADCAST_HDR
  867. {
  868. internal Int32 dbch_size;
  869. internal DbtDevTyp dbch_devicetype;
  870. internal Int32 dbch_reserved;
  871. }//struct
  872. //[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential,
  873. // CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
  874. struct DEV_BROADCAST_PORT
  875. {
  876. internal DEV_BROADCAST_HDR header;
  877. //[//error CS0647: Error emitting 'System.Runtime.InteropServices.MarshalAsAttribute' attribute -- 'SizeConst is required for a fixed string.'
  878. //System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr)]
  879. //public IntPtr ____dbcp_name;//String
  880. internal byte[] ____dbcp_name;//String
  881. }//struct
  882. struct DEV_BROADCAST_HANDLE
  883. {
  884. // 32-bit: 3*4 + 2*4 + 16 + 4 + a = 6*4+16 = 24+16 + a = 40 + a = 44
  885. // 64-bit: (3+1)*4 + 2*8 + 16 + 4 + a = 16+16+16+4 + a = 52 + a = 56
  886. const int SizeWithoutFakeDataArray = 40;
  887. const int SizeOfOneByteWithPadding = 4;
  888. const int SizeWithFakeDataArray = SizeWithoutFakeDataArray + SizeOfOneByteWithPadding;
  889. static int ActualSizeWithFakeDataArray;
  890. public DEV_BROADCAST_HDR header;
  891. //--
  892. internal readonly IntPtr dbch_handle;
  893. internal readonly IntPtr dbch_hdevnotify;
  894. internal readonly Guid dbch_eventguid;
  895. internal readonly Int32 dbch_nameoffset;
  896. // We can't include the fake data array because we use this struct as
  897. // the first field in other structs!
  898. // byte dbch_data__0; //dbch_data[1];
  899. //----
  900. public DEV_BROADCAST_HANDLE(IntPtr deviceHandle)
  901. {
  902. this.header.dbch_reserved = 0;
  903. this.dbch_hdevnotify = IntPtr.Zero;
  904. this.dbch_eventguid = Guid.Empty;
  905. this.dbch_nameoffset = 0;
  906. //--
  907. this.header.dbch_devicetype = DbtDevTyp.Handle;
  908. this.dbch_handle = deviceHandle;
  909. //System.Diagnostics.Debug.Assert(
  910. // SizeWithoutFakeDataArray == System.Runtime.InteropServices.Marshal.SizeOf(typeof(DEV_BROADCAST_HANDLE)),
  911. // "Size not as expected");
  912. if (ActualSizeWithFakeDataArray == 0) {
  913. int actualSizeWithoutFakeDataArray = System.Runtime.InteropServices.Marshal.SizeOf(typeof(DEV_BROADCAST_HANDLE));
  914. ActualSizeWithFakeDataArray = Pad(1 + actualSizeWithoutFakeDataArray, IntPtr.Size);
  915. }
  916. this.header.dbch_size = ActualSizeWithFakeDataArray;
  917. //this.header.dbch_size = actualSizeWithoutFakeDataArray;
  918. }
  919. private static int Pad(int size, int alignment)
  920. {
  921. int x = size + alignment - 1;
  922. x /= alignment;
  923. x *= alignment;
  924. return x;
  925. }
  926. }//struct
  927. struct DEV_BROADCAST_HANDLE__withData
  928. {
  929. public DEV_BROADCAST_HDR header;
  930. //--
  931. readonly IntPtr dbch_handle;
  932. readonly IntPtr dbch_hdevnotify;
  933. readonly Guid dbch_eventguid;
  934. readonly Int32 dbch_nameoffset;
  935. readonly byte dbch_data__0; //dbch_data[1];
  936. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId="weAreForPinvoke")]
  937. private DEV_BROADCAST_HANDLE__withData(int weAreForPinvoke)
  938. {
  939. this.header = new DEV_BROADCAST_HDR();
  940. this.dbch_handle = IntPtr.Zero;
  941. this.dbch_hdevnotify = IntPtr.Zero;
  942. this.dbch_eventguid = Guid.Empty;
  943. this.dbch_nameoffset = this.dbch_nameoffset = 0;
  944. this.dbch_data__0 = this.dbch_data__0 = 0;
  945. }
  946. }//struct
  947. static class ShutUpCompiler
  948. {
  949. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
  950. internal static void ShutUpCompilerTheStructFieldAsUsed()
  951. {
  952. DEV_BROADCAST_HDR hdr;
  953. hdr.dbch_size = 0;
  954. hdr.dbch_devicetype = DbtDevTyp.DevNode;
  955. hdr.dbch_reserved = 0;
  956. //----
  957. DEV_BROADCAST_PORT port;
  958. port.header = hdr;
  959. port.____dbcp_name = null; //IntPtr.Zero;
  960. //----
  961. //DEV_BROADCAST_HANDLE handle;
  962. //handle.dbch_handle = IntPtr.Zero;
  963. //handle.dbch_hdevnotify = IntPtr.Zero;
  964. //handle.dbch_eventguid = Guid.Empty;
  965. //handle.dbch_nameoffset = 0;
  966. //
  967. BTH_RADIO_IN_RANGE inRange;
  968. inRange.deviceInfo.flags = 0;
  969. inRange.previousDeviceFlags = 0;
  970. BTH_RADIO_OUT_OF_RANGE oor;
  971. oor.deviceAddress = 0;
  972. //
  973. BTH_DEVICE_INFO di;
  974. di.address = 0;
  975. di.classOfDevice = 0;
  976. di.name = new byte[0];
  977. }
  978. }
  979. #endif
  980. #endif
  981. }
  982. }