1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-13 10:08:31 +00:00

Compare commits

..

No commits in common. "21b2f774c46c915053d0c4c8bf2e8d85fdd54361" and "ecf880d297832566be048d9c0d8bc6ceacdf4792" have entirely different histories.

16 changed files with 88 additions and 93 deletions

View File

@ -14,18 +14,13 @@ namespace RGB.NET.Core
private readonly List<T> _decorators = new(); private readonly List<T> _decorators = new();
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<T> Decorators { get; } public IReadOnlyCollection<T> Decorators
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractDecoratable{T}"/> class.
/// </summary>
protected AbstractDecoratable()
{ {
Decorators = new ReadOnlyCollection<T>(_decorators); get
{
lock (_decorators)
return new ReadOnlyCollection<T>(_decorators);
}
} }
#endregion #endregion

View File

@ -20,7 +20,7 @@ namespace RGB.NET.Core
/// <summary> /// <summary>
/// Gets a readonly-list of all <see cref="IDecorator"/> attached to this <see cref="IDecoratable{T}"/>. /// Gets a readonly-list of all <see cref="IDecorator"/> attached to this <see cref="IDecoratable{T}"/>.
/// </summary> /// </summary>
IReadOnlyList<T> Decorators { get; } IReadOnlyCollection<T> Decorators { get; }
/// <summary> /// <summary>
/// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>. /// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>.

View File

@ -30,7 +30,7 @@ namespace RGB.NET.Core
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = new(); protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = new();
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList()); public ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList());
#endregion #endregion

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace RGB.NET.Core namespace RGB.NET.Core
{ {
@ -30,7 +31,7 @@ namespace RGB.NET.Core
/// <summary> /// <summary>
/// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider. /// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider.
/// </summary> /// </summary>
IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers { get; } ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers { get; }
#endregion #endregion

View File

@ -26,15 +26,20 @@ namespace RGB.NET.Core
/// <summary> /// <summary>
/// Gets a readonly list containing all loaded <see cref="IRGBDevice"/>. /// Gets a readonly list containing all loaded <see cref="IRGBDevice"/>.
/// This collection should be locked when enumerated in a multi-threaded application.
/// </summary> /// </summary>
public IReadOnlyList<IRGBDevice> Devices { get; } public IEnumerable<IRGBDevice> Devices
{
get
{
lock (_devices)
return new ReadOnlyCollection<IRGBDevice>(_devices);
}
}
/// <summary> /// <summary>
/// Gets a readonly list containing all registered <see cref="IUpdateTrigger"/>. /// Gets a readonly list containing all registered <see cref="IUpdateTrigger"/>.
/// This collection should be locked when enumerated in a multi-threaded application.
/// </summary> /// </summary>
public IReadOnlyList<IUpdateTrigger> UpdateTriggers { get; } public IEnumerable<IUpdateTrigger> UpdateTriggers => new ReadOnlyCollection<IUpdateTrigger>(_updateTriggers);
/// <summary> /// <summary>
/// Gets a copy of the <see cref="Rectangle"/> representing this <see cref="RGBSurface"/>. /// Gets a copy of the <see cref="Rectangle"/> representing this <see cref="RGBSurface"/>.
@ -48,7 +53,7 @@ namespace RGB.NET.Core
{ {
get get
{ {
lock (Devices) lock (_devices)
return _devices.SelectMany(x => x); return _devices.SelectMany(x => x);
} }
} }
@ -119,9 +124,6 @@ namespace RGB.NET.Core
public RGBSurface() public RGBSurface()
{ {
_deltaTimeCounter = Stopwatch.StartNew(); _deltaTimeCounter = Stopwatch.StartNew();
Devices = new ReadOnlyCollection<IRGBDevice>(_devices);
UpdateTriggers = new ReadOnlyCollection<IUpdateTrigger>(_updateTriggers);
} }
#endregion #endregion
@ -144,8 +146,8 @@ namespace RGB.NET.Core
bool render = customData["render"] as bool? ?? true; bool render = customData["render"] as bool? ?? true;
bool updateDevices = customData["updateDevices"] as bool? ?? true; bool updateDevices = customData["updateDevices"] as bool? ?? true;
lock (UpdateTriggers) lock (_updateTriggers)
lock (Devices) lock (_devices)
{ {
OnUpdating(updateTrigger, customData); OnUpdating(updateTrigger, customData);
@ -176,7 +178,7 @@ namespace RGB.NET.Core
public void Dispose() public void Dispose()
{ {
List<IRGBDevice> devices; List<IRGBDevice> devices;
lock (Devices) lock (_devices)
devices = new List<IRGBDevice>(_devices); devices = new List<IRGBDevice>(_devices);
foreach (IRGBDevice device in devices) foreach (IRGBDevice device in devices)
@ -265,7 +267,7 @@ namespace RGB.NET.Core
/// <param name="device">The <see cref="IRGBDevice"/> to attach.</param> /// <param name="device">The <see cref="IRGBDevice"/> to attach.</param>
public void Attach(IRGBDevice device) public void Attach(IRGBDevice device)
{ {
lock (Devices) lock (_devices)
{ {
if (string.IsNullOrWhiteSpace(device.DeviceInfo.DeviceName)) throw new RGBDeviceException($"The device '{device.DeviceInfo.Manufacturer} {device.DeviceInfo.Model}' has no valid name."); if (string.IsNullOrWhiteSpace(device.DeviceInfo.DeviceName)) throw new RGBDeviceException($"The device '{device.DeviceInfo.Manufacturer} {device.DeviceInfo.Model}' has no valid name.");
if (device.Surface != null) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is already attached to a surface."); if (device.Surface != null) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is already attached to a surface.");
@ -285,7 +287,7 @@ namespace RGB.NET.Core
/// <returns><c>true</c> if the <see cref="IRGBDevice"/> could be detached; <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the <see cref="IRGBDevice"/> could be detached; <c>false</c> otherwise.</returns>
public void Detach(IRGBDevice device) public void Detach(IRGBDevice device)
{ {
lock (Devices) lock (_devices)
{ {
if (!_devices.Contains(device)) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is not attached to this surface."); if (!_devices.Contains(device)) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is not attached to this surface.");
@ -312,7 +314,7 @@ namespace RGB.NET.Core
private void UpdateSurfaceRectangle() private void UpdateSurfaceRectangle()
{ {
lock (Devices) lock (_devices)
{ {
Rectangle devicesRectangle = new(_devices.Select(d => d.Boundary)); Rectangle devicesRectangle = new(_devices.Select(d => d.Boundary));
Boundary = Boundary.SetSize(new Size(devicesRectangle.Location.X + devicesRectangle.Size.Width, devicesRectangle.Location.Y + devicesRectangle.Size.Height)); Boundary = Boundary.SetSize(new Size(devicesRectangle.Location.X + devicesRectangle.Size.Width, devicesRectangle.Location.Y + devicesRectangle.Size.Height));

View File

@ -92,20 +92,10 @@ namespace RGB.NET.Core
if (UpdateTask != null) if (UpdateTask != null)
{ {
UpdateTokenSource?.Cancel(); UpdateTokenSource?.Cancel();
try // ReSharper disable once MethodSupportsCancellation
{ UpdateTask.Wait();
// ReSharper disable once MethodSupportsCancellation UpdateTask.Dispose();
UpdateTask.Wait(); UpdateTask = null;
}
catch (AggregateException)
{
// ignored
}
finally
{
UpdateTask.Dispose();
UpdateTask = null;
}
} }
} }
} }

View File

@ -48,21 +48,6 @@ namespace RGB.NET.Devices.CoolerMaster
{ LedId.Mouse3, (0,2) } { LedId.Mouse3, (0,2) }
} }
}, },
{ CoolerMasterDevicesIndexes.MM830, new Dictionary<LedId, (int row, int column)>
{
{ LedId.Mouse1, (0,0) },
{ LedId.Mouse2, (0,1) },
{ LedId.Mouse3, (0,2) },
{ LedId.Mouse4, (0,3) },
{ LedId.Mouse5, (0,4) },
{ LedId.Mouse6, (0,5) },
{ LedId.Mouse7, (0,6) },
{ LedId.Mouse8, (0,7) },
{ LedId.Mouse9, (0,8) },
{ LedId.Mouse10, (0,9) },
}
},
}; };
#endregion #endregion

View File

@ -68,7 +68,7 @@ namespace RGB.NET.Devices.DMX.E131
this.Hostname = deviceDefinition.Hostname; this.Hostname = deviceDefinition.Hostname;
this.Port = deviceDefinition.Port; this.Port = deviceDefinition.Port;
this.Universe = deviceDefinition.Universe; this.Universe = deviceDefinition.Universe;
byte[]? cid = deviceDefinition.CID; byte[]? cid = deviceDefinition.CID;
if ((cid == null) || (cid.Length != CID_LENGTH)) if ((cid == null) || (cid.Length != CID_LENGTH))
{ {
@ -76,7 +76,7 @@ namespace RGB.NET.Devices.DMX.E131
new Random().NextBytes(cid); new Random().NextBytes(cid);
} }
CID = cid; CID = cid!;
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model); DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
} }

View File

@ -7,14 +7,10 @@ namespace RGB.NET.Devices.Wooting.Enum
/// </summary> /// </summary>
public enum WootingDeviceType public enum WootingDeviceType
{ {
/// <summary>
/// 10 Keyless Keyboard. E.g. Wooting One /// 10 Keyless Keyboard. E.g. Wooting One
/// </summary>
KeyboardTKL = 1, KeyboardTKL = 1,
/// <summary>
/// Full Size keyboard. E.g. Wooting Two /// Full Size keyboard. E.g. Wooting Two
/// </summary>
Keyboard = 2 Keyboard = 2
} }
} }

View File

@ -0,0 +1,21 @@
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global
using System.ComponentModel;
#pragma warning disable 1591 // Missing XML comment for publicly visible type or member
namespace RGB.NET.Devices.Wooting.Enum
{
/// <summary>
/// Contains a list of available device-indexes.
/// </summary>
public enum WootingDevicesIndexes
{
[Description("Wooting One")]
WootingOne = 0,
[Description("Wooting Two")]
WootingTwo = 1
}
}

View File

@ -1,7 +1,6 @@
using RGB.NET.Core; using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum; using RGB.NET.Devices.Wooting.Enum;
using RGB.NET.Devices.Wooting.Helper; using RGB.NET.Devices.Wooting.Helper;
using RGB.NET.Devices.Wooting.Native;
namespace RGB.NET.Devices.Wooting.Generic namespace RGB.NET.Devices.Wooting.Generic
{ {
@ -29,9 +28,9 @@ namespace RGB.NET.Devices.Wooting.Generic
public object? LayoutMetadata { get; set; } public object? LayoutMetadata { get; set; }
/// <summary> /// <summary>
/// Gets the <see cref="WootingDeviceType"/> of the <see cref="WootingRGBDevice{TDeviceInfo}"/>. /// Gets the <see cref="WootingDevicesIndexes"/> of the <see cref="WootingRGBDevice{TDeviceInfo}"/>.
/// </summary> /// </summary>
public WootingDeviceType WootingDeviceType { get; } public WootingDevicesIndexes DeviceIndex { get; }
#endregion #endregion
@ -41,13 +40,13 @@ namespace RGB.NET.Devices.Wooting.Generic
/// Internal constructor of managed <see cref="WootingRGBDeviceInfo"/>. /// Internal constructor of managed <see cref="WootingRGBDeviceInfo"/>.
/// </summary> /// </summary>
/// <param name="deviceType">The type of the <see cref="IRGBDevice"/>.</param> /// <param name="deviceType">The type of the <see cref="IRGBDevice"/>.</param>
/// <param name="deviceInfo">The <see cref="_WootingDeviceInfo"/> of the <see cref="IRGBDevice"/>.</param> /// <param name="deviceIndex">The <see cref="WootingDevicesIndexes"/> of the <see cref="IRGBDevice"/>.</param>
internal WootingRGBDeviceInfo(RGBDeviceType deviceType, _WootingDeviceInfo deviceInfo) internal WootingRGBDeviceInfo(RGBDeviceType deviceType, WootingDevicesIndexes deviceIndex)
{ {
this.DeviceType = deviceType; this.DeviceType = deviceType;
this.WootingDeviceType = deviceInfo.DeviceType; this.DeviceIndex = deviceIndex;
Model = deviceInfo.Model; Model = deviceIndex.GetDescription();
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model); DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
} }

View File

@ -13,9 +13,9 @@ namespace RGB.NET.Devices.Wooting.Keyboard
{ {
#region Properties & Fields #region Properties & Fields
#region TKL #region Wooting One
private static readonly Dictionary<LedId, (int row, int column)> TKL_US = new() private static readonly Dictionary<LedId, (int row, int column)> WootingOne_US = new()
{ {
{ LedId.Keyboard_Escape, (0,0) }, { LedId.Keyboard_Escape, (0,0) },
{ LedId.Keyboard_F1, (0,2) }, { LedId.Keyboard_F1, (0,2) },
@ -111,7 +111,7 @@ namespace RGB.NET.Devices.Wooting.Keyboard
{ LedId.Keyboard_ArrowRight, (5,16) } { LedId.Keyboard_ArrowRight, (5,16) }
}; };
private static readonly Dictionary<LedId, (int row, int column)> TKL_UK = new() private static readonly Dictionary<LedId, (int row, int column)> WootingOne_UK = new()
{ {
{ LedId.Keyboard_Escape, (0,0) }, { LedId.Keyboard_Escape, (0,0) },
{ LedId.Keyboard_F1, (0,2) }, { LedId.Keyboard_F1, (0,2) },
@ -178,7 +178,7 @@ namespace RGB.NET.Devices.Wooting.Keyboard
{ LedId.Keyboard_L, (3,9) }, { LedId.Keyboard_L, (3,9) },
{ LedId.Keyboard_SemicolonAndColon, (3,10) }, { LedId.Keyboard_SemicolonAndColon, (3,10) },
{ LedId.Keyboard_ApostropheAndDoubleQuote, (3,11) }, { LedId.Keyboard_ApostropheAndDoubleQuote, (3,11) },
{ LedId.Keyboard_NonUsTilde, (3,12) }, { LedId.Keyboard_NonUsTilde, (3,11) },
{ LedId.Keyboard_Enter, (3,13) }, { LedId.Keyboard_Enter, (3,13) },
{ LedId.Keyboard_LeftShift, (4,0) }, { LedId.Keyboard_LeftShift, (4,0) },
@ -211,9 +211,9 @@ namespace RGB.NET.Devices.Wooting.Keyboard
#endregion #endregion
#region Fullsize #region Wooting Two
private static readonly Dictionary<LedId, (int row, int column)> Fullsize_US = new() private static readonly Dictionary<LedId, (int row, int column)> WootingTwo_US = new()
{ {
{ LedId.Keyboard_Escape, (0,0) }, { LedId.Keyboard_Escape, (0,0) },
{ LedId.Keyboard_F1, (0,2) }, { LedId.Keyboard_F1, (0,2) },
@ -330,7 +330,7 @@ namespace RGB.NET.Devices.Wooting.Keyboard
{ LedId.Keyboard_NumPeriodAndDelete, (5,19) } { LedId.Keyboard_NumPeriodAndDelete, (5,19) }
}; };
private static readonly Dictionary<LedId, (int row, int column)> Fullsize_UK = new() private static readonly Dictionary<LedId, (int row, int column)> WootingTwo_UK = new()
{ {
{ LedId.Keyboard_Escape, (0,0) }, { LedId.Keyboard_Escape, (0,0) },
{ LedId.Keyboard_F1, (0,2) }, { LedId.Keyboard_F1, (0,2) },
@ -454,20 +454,20 @@ namespace RGB.NET.Devices.Wooting.Keyboard
/// <summary> /// <summary>
/// Contains all the hardware-id mappings for Wooting devices. /// Contains all the hardware-id mappings for Wooting devices.
/// </summary> /// </summary>
public static readonly Dictionary<WootingDeviceType, Dictionary<WootingPhysicalKeyboardLayout, Dictionary<LedId, (int row, int column)>>> Mapping = public static readonly Dictionary<WootingDevicesIndexes, Dictionary<WootingPhysicalKeyboardLayout, Dictionary<LedId, (int row, int column)>>> Mapping =
new() new()
{ {
{ WootingDeviceType.KeyboardTKL, new Dictionary<WootingPhysicalKeyboardLayout, Dictionary<LedId, (int row, int column)>> { WootingDevicesIndexes.WootingOne, new Dictionary<WootingPhysicalKeyboardLayout, Dictionary<LedId, (int row, int column)>>
{ {
{ WootingPhysicalKeyboardLayout.US, TKL_US }, { WootingPhysicalKeyboardLayout.US, WootingOne_US },
{ WootingPhysicalKeyboardLayout.UK, TKL_UK } { WootingPhysicalKeyboardLayout.UK, WootingOne_UK }
} }
}, },
{ WootingDeviceType.Keyboard, new Dictionary<WootingPhysicalKeyboardLayout, Dictionary<LedId, (int row, int column)>> { WootingDevicesIndexes.WootingTwo, new Dictionary<WootingPhysicalKeyboardLayout, Dictionary<LedId, (int row, int column)>>
{ {
{ WootingPhysicalKeyboardLayout.US, Fullsize_US }, { WootingPhysicalKeyboardLayout.US, WootingTwo_US },
{ WootingPhysicalKeyboardLayout.UK, Fullsize_UK } { WootingPhysicalKeyboardLayout.UK, WootingTwo_UK }
} }
} }
}; };

View File

@ -38,14 +38,14 @@ namespace RGB.NET.Devices.Wooting.Keyboard
private void InitializeLayout() private void InitializeLayout()
{ {
//TODO DarthAffe 13.02.2021: Check how the mapping can work without knowing the physical layout //TODO DarthAffe 13.02.2021: Check how the mapping can work without knowing the physical layout
Dictionary<LedId, (int row, int column)> mapping = WootingKeyboardLedMappings.Mapping[DeviceInfo.WootingDeviceType][WootingPhysicalKeyboardLayout.US]; Dictionary<LedId, (int row, int column)> mapping = WootingKeyboardLedMappings.Mapping[DeviceInfo.DeviceIndex][WootingPhysicalKeyboardLayout.US];
foreach (KeyValuePair<LedId, (int row, int column)> led in mapping) foreach (KeyValuePair<LedId, (int row, int column)> led in mapping)
AddLed(led.Key, new Point(led.Value.column * 19, led.Value.row * 19), new Size(19, 19)); AddLed(led.Key, new Point(led.Value.column * 19, led.Value.row * 19), new Size(19, 19));
} }
/// <inheritdoc /> /// <inheritdoc />
protected override object GetLedCustomData(LedId ledId) => WootingKeyboardLedMappings.Mapping[DeviceInfo.WootingDeviceType][WootingPhysicalKeyboardLayout.US][ledId]; protected override object GetLedCustomData(LedId ledId) => WootingKeyboardLedMappings.Mapping[DeviceInfo.DeviceIndex][WootingPhysicalKeyboardLayout.US][ledId];
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(GetUpdateData(ledsToUpdate)); protected override void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(GetUpdateData(ledsToUpdate));

View File

@ -1,7 +1,6 @@
using RGB.NET.Core; using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum; using RGB.NET.Devices.Wooting.Enum;
using RGB.NET.Devices.Wooting.Generic; using RGB.NET.Devices.Wooting.Generic;
using RGB.NET.Devices.Wooting.Native;
namespace RGB.NET.Devices.Wooting.Keyboard namespace RGB.NET.Devices.Wooting.Keyboard
{ {
@ -23,9 +22,9 @@ namespace RGB.NET.Devices.Wooting.Keyboard
/// <summary> /// <summary>
/// Internal constructor of managed <see cref="T:RGB.NET.Devices.Wooting.WootingKeyboardRGBDeviceInfo" />. /// Internal constructor of managed <see cref="T:RGB.NET.Devices.Wooting.WootingKeyboardRGBDeviceInfo" />.
/// </summary> /// </summary>
/// <param name="deviceInfo">The native <see cref="T:RGB.NET.Devices.Wooting.Native._WootingDeviceInfo" />.</param> /// <param name="deviceIndex">The index of the <see cref="T:RGB.NET.Devices.Wooting.WootingKeyboardRGBDevice" />.</param>
internal WootingKeyboardRGBDeviceInfo(_WootingDeviceInfo deviceInfo) internal WootingKeyboardRGBDeviceInfo(WootingDevicesIndexes deviceIndex)
: base(RGBDeviceType.Keyboard, deviceInfo) : base(RGBDeviceType.Keyboard, deviceIndex)
{ } { }
#endregion #endregion

View File

@ -20,7 +20,5 @@ namespace RGB.NET.Devices.Wooting.Native
internal byte KeycodeLimit { get; private set; } internal byte KeycodeLimit { get; private set; }
internal WootingDeviceType DeviceType { get; private set; } internal WootingDeviceType DeviceType { get; private set; }
internal bool V2Interface { get; set; }
} }
} }

View File

@ -70,8 +70,17 @@ namespace RGB.NET.Devices.Wooting
if (_WootingSDK.KeyboardConnected()) if (_WootingSDK.KeyboardConnected())
{ {
_WootingDeviceInfo nativeDeviceInfo = (_WootingDeviceInfo)Marshal.PtrToStructure(_WootingSDK.GetDeviceInfo(), typeof(_WootingDeviceInfo))!; _WootingDeviceInfo nativeDeviceInfo = (_WootingDeviceInfo)Marshal.PtrToStructure(_WootingSDK.GetDeviceInfo(), typeof(_WootingDeviceInfo))!;
IWootingRGBDevice? device = nativeDeviceInfo.Model switch
{
"Wooting two" => new WootingKeyboardRGBDevice(new WootingKeyboardRGBDeviceInfo(WootingDevicesIndexes.WootingTwo), GetUpdateTrigger()),
"Wooting one" => new WootingKeyboardRGBDevice(new WootingKeyboardRGBDeviceInfo(WootingDevicesIndexes.WootingOne), GetUpdateTrigger()),
_ => null
};
yield return new WootingKeyboardRGBDevice(new WootingKeyboardRGBDeviceInfo(nativeDeviceInfo), GetUpdateTrigger()); if (device == null)
Throw(new RGBDeviceException("No supported Wooting keyboard connected"));
else
yield return device;
} }
} }
} }