/// <summary>
        /// Function to unregister the device from the raw input provider.
        /// </summary>
        /// <param name="device">The device to unregister from the raw input provider.</param>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="device"/> parameter is <b>null</b>.</exception>
        /// <remarks>
        /// This will unregister a previously registered <see cref="IGorgonRawInputDevice"/>. When the last device of a specific type (e.g. a mouse, keyboard, etc...) is unregistered, then the
        /// Raw Input messages for that device type will also be unregistered and the application will no longer receive messages from that type of device.
        /// </remarks>
        public void UnregisterDevice(IGorgonRawInputDevice device)
        {
            if (device == null)
            {
                throw new ArgumentNullException(nameof(device));
            }

            lock (_syncLock)
            {
                var key = new DeviceKey
                {
                    DeviceType   = device.DeviceType,
                    DeviceHandle = device.Handle
                };

                switch (device.DeviceType)
                {
                case RawInputType.Keyboard:
                    _keyboardDevices.Remove(key);
                    break;

                case RawInputType.Mouse:
                    _mouseDevices.Remove(key);
                    break;

                case RawInputType.HID:
                    _hids.Remove(key);
                    break;

                default:
                    throw new ArgumentException(string.Format(Resources.GORINP_RAW_ERR_UNKNOWN_DEVICE_TYPE, device.DeviceType), nameof(device));
                }

                if (!_devices.ContainsKey(key))
                {
                    return;
                }

                _devices.Remove(key);

                // If all devices of this type have been unregistered, unregister with raw input as well.
                if ((_devices.Count(item => item.Value.DeviceType == device.DeviceType) == 0) &&
                    (RawInputApi.GetDeviceRegistration(device.DeviceUsage) != null))
                {
                    RawInputApi.UnregisterRawInputDevice(device.DeviceUsage);
                }

                // If we have no more registered devices, then uninstall the filter.
                if (_devices.Count != 0)
                {
                    return;
                }

                _filter?.Dispose();
                _filter = null;
            }
        }
        /// <summary>
        /// Function to register the device with the raw input provider.
        /// </summary>
        /// <param name="device">The device to register with the raw input provider.</param>
        /// <param name="settings">[Optional] Settings for the device type.</param>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="device"/> parameter is <b>null</b>.</exception>
        /// <remarks>
        /// <para>
        /// This will register the <see cref="IGorgonRawInputDevice"/> with the application. For the very first device of a specific type (e.g. a mouse, keyboard, etc...) the Raw Input object will set up
        /// the device type registration for the device. This enables an application to start receiving Raw Input messages from a device type.
        /// </para>
        /// <para>
        /// The optional <paramref name="settings"/> parameter allows an application change how raw input handles the device being registered. It can be used to set up background input monitoring, or a
        /// target window for raw input messages (which must be set if the background option is turned on). By default, there is no background message processing and no target window (messages go to
        /// whichever window has focus).
        /// </para>
        /// <para>
        /// Every call to this method should be paired with a call to <see cref="UnregisterDevice"/> when the device(s) are no longer needed.
        /// </para>
        /// </remarks>
        public void RegisterDevice(IGorgonRawInputDevice device, GorgonRawInputSettings?settings = null)
        {
            IntPtr targetHandle = IntPtr.Zero;

            if (device == null)
            {
                throw new ArgumentNullException(nameof(device));
            }

            if (settings == null)
            {
                settings = new GorgonRawInputSettings();
            }

            lock (_syncLock)
            {
                // If we've not set up the filter yet, then add it to the window now.
                if (_filter == null)
                {
                    _filter = new RawInputMessageFilter(_keyboardDevices, _mouseDevices, _hids, _applicationWindow, _useNativeHook);
                }

                var key = new DeviceKey
                {
                    DeviceType   = device.DeviceType,
                    DeviceHandle = device.Handle
                };

                if (_devices.ContainsKey(key))
                {
                    return;
                }

                switch (device.DeviceType)
                {
                case RawInputType.Keyboard:
                    _keyboardDevices.Add(key, (IRawInputDeviceData <GorgonRawKeyboardData>)device);
                    break;

                case RawInputType.Mouse:
                    _mouseDevices.Add(key, (IRawInputDeviceData <GorgonRawMouseData>)device);
                    break;

                case RawInputType.HID:
                    _hids.Add(key, (IRawInputDeviceData <GorgonRawHIDData>)device);
                    break;

                default:
                    throw new ArgumentException(string.Format(Resources.GORINP_RAW_ERR_UNKNOWN_DEVICE_TYPE, device.DeviceType), nameof(device));
                }

                _devices.Add(key, device);

                // Get the current device registration properties.
                RAWINPUTDEVICE?deviceReg = RawInputApi.GetDeviceRegistration(device.DeviceUsage);
                if (deviceReg != null)
                {
                    // Remove the device before updating it.
                    UnregisterDevice(device);
                }

                // If we omit the target window, and specify background messages, we'll use the application window instead.
                // This is because Raw Input requires that background devices have a window target.
                if ((settings.Value.TargetWindow.IsNull) && (settings.Value.AllowBackground))
                {
                    unsafe
                    {
                        settings = new GorgonRawInputSettings
                        {
                            TargetWindow    = new GorgonReadOnlyPointer((void *)_applicationWindow, IntPtr.Size),
                            AllowBackground = true
                        };
                    }
                }

                RawInputDeviceFlags flags = RawInputDeviceFlags.None;

                if (settings.Value.AllowBackground)
                {
                    flags |= RawInputDeviceFlags.InputSink;
                }

                RawInputApi.RegisterRawInputDevice(device.DeviceUsage, targetHandle, flags);
            }
        }