diff --git a/PowerMate/IPowerMateClient.cs b/PowerMate/IPowerMateClient.cs
index 16c2ca5..d381315 100644
--- a/PowerMate/IPowerMateClient.cs
+++ b/PowerMate/IPowerMateClient.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using HidClient;
namespace PowerMate;
@@ -6,42 +7,17 @@ namespace PowerMate;
/// Listen for events and control the light on a connected Griffin PowerMate USB device.
/// To get started, construct a new instance of .
/// Once you have constructed an instance, you can subscribe to events to be notified when the PowerMate knob is rotated, pressed, or released.
-/// You can also subscribe to or to be notified when it connects or disconnects from a PowerMate.
+/// You can also subscribe to or to be notified when it connects or disconnects from a PowerMate.
/// The light is controlled by the , , and properties.
/// Remember to dispose of this instance when you're done using it by calling , or with a statement or declaration.
///
-public interface IPowerMateClient: IDisposable, INotifyPropertyChanged {
-
- ///
- /// if the client is currently connected to a PowerMate device, or if it is disconnected, possibly because there is no PowerMate device
- /// plugged into the computer.
- /// will automatically try to connect to a PowerMate device when you construct a new instance, so you don't have to call any additional methods in order to make
- /// it start connecting.
- /// If a PowerMate device is plugged in, will already be by the time the constructor returns.
- /// To receive notifications when this property changes, you can subscribe to the or events.
- ///
- bool IsConnected { get; }
+public interface IPowerMateClient: IHidClient {
///
/// Fired whenever the PowerMate knob is rotated, pressed, or released.
///
event EventHandler InputReceived;
- ///
- /// Fired whenever the connection state of the PowerMate changes. Not fired when constructing or disposing the instance.
- /// The event argument contains the new value of .
- /// This value can also be accessed at any time by reading the property.
- /// If you want to use data binding which expects events, also implements
- /// , so you can use that event instead.
- ///
- event EventHandler IsConnectedChanged;
-
- ///
- /// on which to run event callbacks. Useful if your delegates need to update a user interface on the main thread. Callbacks run on the current thread by
- /// default.
- ///
- SynchronizationContext EventSynchronizationContext { get; set; }
-
///
/// Get or set how bright the blue/cyan LED in the base of the PowerMate is, between 0 (off) and 255 (brightest), inclusive. When the device is first plugged in, it defaults to
/// 80.
diff --git a/PowerMate/PowerMate.csproj b/PowerMate/PowerMate.csproj
index 8f81985..f0f3ce0 100644
--- a/PowerMate/PowerMate.csproj
+++ b/PowerMate/PowerMate.csproj
@@ -1,7 +1,7 @@
- 1.0.0
+ 1.0.1
Ben Hutchison
Ben Hutchison
PowerMate
@@ -36,7 +36,7 @@
-
+
diff --git a/PowerMate/PowerMateClient.cs b/PowerMate/PowerMateClient.cs
index 5f1b35d..abab496 100644
--- a/PowerMate/PowerMateClient.cs
+++ b/PowerMate/PowerMateClient.cs
@@ -1,137 +1,53 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
+using HidClient;
using HidSharp;
namespace PowerMate;
-///
-public class PowerMateClient: IPowerMateClient {
+///
+public class PowerMateClient: AbstractHidClient, IPowerMateClient {
- private const int PowerMateVendorId = 0x077d;
- private const int PowerMateProductId = 0x0410;
private const byte DefaultLightBrightness = 80;
- private readonly object _hidStreamLock = new();
-
- private DeviceList? _deviceList;
- private CancellationTokenSource? _cancellationTokenSource;
- private bool _isConnected;
- private HidStream? _hidStream;
- private byte _lightBrightness = DefaultLightBrightness;
- private LightAnimation _lightAnimation = LightAnimation.Solid;
- private int _lightPulseSpeed = 12;
-
///
- public event EventHandler? IsConnectedChanged;
+ protected override int VendorId { get; } = 0x077d;
///
- public event PropertyChangedEventHandler? PropertyChanged;
+ protected override int ProductId { get; } = 0x0410;
+
+ private byte _lightBrightness = DefaultLightBrightness;
+ private LightAnimation _lightAnimation = LightAnimation.Solid;
+ private int _lightPulseSpeed = 12;
+ private DateTime? _mostRecentFeatureSetTime;
///
public event EventHandler? InputReceived;
///
- public SynchronizationContext EventSynchronizationContext { get; set; } = SynchronizationContext.Current ?? new SynchronizationContext();
-
- ///
- /// Constructs a new instance that communicates with a PowerMate device.
- /// Upon construction, the new instance will immediately attempt to connect to any PowerMate connected to your computer. If none are connected, it will wait and connect when one is plugged
- /// in. If a PowerMate disconnects, it will try to reconnect whenever one is plugged in again.
- /// If multiple PowerMate devices are present, it will pick one arbitrarily and connect to it.
- /// Once you have constructed an instance, you can subscribe to events to be notified when the PowerMate knob is rotated, pressed, or released.
- /// You can also subscribe to or to be notified when it connects or disconnects from a PowerMate.
- /// The light is controlled by the , , and properties.
- /// Remember to dispose of this instance when you're done using it by calling , or with a statement or declaration.
- ///
- public PowerMateClient(): this(DeviceList.Local) { }
-
- internal PowerMateClient(DeviceList deviceList) {
- _deviceList = deviceList;
- _deviceList.Changed += onDeviceListChanged;
- AttachToDevice();
- }
+ public PowerMateClient() { }
///
- public bool IsConnected {
- get => _isConnected;
- private set {
- if (value != _isConnected) {
- _isConnected = value;
- EventSynchronizationContext.Post(_ => {
- IsConnectedChanged?.Invoke(this, value);
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsConnected)));
- }, null);
- }
- }
- }
+ public PowerMateClient(DeviceList deviceList): base(deviceList) { }
- private void onDeviceListChanged(object? sender, DeviceListChangedEventArgs e) {
- AttachToDevice();
- }
-
- private void AttachToDevice() {
- bool isNewStream = false;
- lock (_hidStreamLock) {
- if (_hidStream == null) {
- HidDevice? newDevice = _deviceList?.GetHidDeviceOrNull(PowerMateVendorId, PowerMateProductId);
- if (newDevice != null) {
- _hidStream = newDevice.Open();
- isNewStream = true;
- }
- }
- }
-
- if (_hidStream != null && isNewStream) {
- _hidStream.Closed += ReattachToDevice;
- _hidStream.ReadTimeout = Timeout.Infinite;
- _cancellationTokenSource = new CancellationTokenSource();
- IsConnected = true;
- LightAnimation = LightAnimation; //resend all pulsing and brightness values to device
-
- try {
- Task.Factory.StartNew(HidReadLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
- } catch (TaskCanceledException) { }
- }
- }
-
- private async Task HidReadLoop() {
- CancellationToken cancellationToken = _cancellationTokenSource!.Token;
-
- try {
- byte[] readBuffer = new byte[7];
- while (!cancellationToken.IsCancellationRequested) {
- int readBytes = await _hidStream!.ReadAsync(readBuffer, 0, readBuffer.Length, cancellationToken);
- if (readBuffer.Length == readBytes) {
- PowerMateInput powerMateInput = new(readBuffer);
- EventSynchronizationContext.Post(_ => { InputReceived?.Invoke(this, powerMateInput); }, null);
- }
- }
- } catch (IOException) {
- ReattachToDevice();
- }
+ ///
+ protected override void OnConnect() {
+ LightAnimation = LightAnimation; //resend all pulsing and brightness values to device
}
- private void ReattachToDevice(object? sender = null, EventArgs? e = null) {
- bool disconnected = false;
- lock (_hidStreamLock) {
- if (_hidStream != null) {
- _hidStream.Closed -= ReattachToDevice;
- _hidStream.Close();
- _hidStream.Dispose();
- _hidStream = null;
- disconnected = true;
- }
- }
-
- if (disconnected) {
- IsConnected = false;
+ ///
+ protected override void OnHidRead(byte[] readBuffer) {
+ Console.WriteLine($"Read HID bytes {string.Join(" ", readBuffer.Select(b => $"{b:x2}"))}"); //FIXME development
+ PowerMateInput input = new(readBuffer);
+ EventSynchronizationContext.Post(_ => { InputReceived?.Invoke(this, input); }, null);
+
+ if ((LightAnimation != input.ActualLightAnimation
+ || (LightAnimation != LightAnimation.Pulsing && LightBrightness != input.ActualLightBrightness)
+ || (LightAnimation == LightAnimation.Pulsing && LightPulseSpeed != input.ActualLightPulseSpeed))
+ && _mostRecentFeatureSetTime is not null && DateTime.Now - _mostRecentFeatureSetTime > TimeSpan.FromMilliseconds(500)) {
+ Console.WriteLine("Resetting features...");
+ LightAnimation = LightAnimation;
}
-
- try {
- _cancellationTokenSource?.Cancel();
- } catch (AggregateException) { }
-
- AttachToDevice();
}
///
@@ -193,12 +109,22 @@ public class PowerMateClient: IPowerMateClient {
// ExceptionAdjustment: M:System.Array.Copy(System.Array,System.Int32,System.Array,System.Int32,System.Int32) -T:System.RankException
// ExceptionAdjustment: M:System.Array.Copy(System.Array,System.Int32,System.Array,System.Int32,System.Int32) -T:System.ArrayTypeMismatchException
private void SetFeature(PowerMateFeature feature, params byte[] payload) {
+ _mostRecentFeatureSetTime = DateTime.Now;
byte[] featureData = { 0x00, 0x41, 0x01, (byte) feature, 0x00, /* payload copied here */ 0x00, 0x00, 0x00, 0x00 };
Array.Copy(payload, 0, featureData, 5, Math.Min(payload.Length, 4));
- _hidStream?.SetFeature(featureData);
+ try {
+ DeviceStream?.SetFeature(featureData);
+ } catch (IOException e) {
+ if (e.InnerException is Win32Exception { NativeErrorCode: 0 }) {
+ // retry once with no delay if we get a "The operation completed successfully" error
+ DeviceStream?.SetFeature(featureData);
+ }
+ }
}
+ /// in the range [0, 24]
+ /// two big-endian bytes to send to the PowerMate to set its pulsing speed
private static byte[] EncodePulseSpeed(int pulseSpeed) {
byte[] encoded = BitConverter.GetBytes((ushort) (pulseSpeed switch {
< 8 => Math.Min(0x00e, (7 - pulseSpeed) * 2),
@@ -210,56 +136,12 @@ public class PowerMateClient: IPowerMateClient {
(encoded[0], encoded[1]) = (encoded[1], encoded[0]);
}
+ Console.WriteLine($"Encoded pulse speed {pulseSpeed} to {encoded[0]:x2} {encoded[1]:x2}"); //FIXME debugging
return encoded;
}
private void TriggerPropertyChangedEvent([CallerMemberName] string propertyName = "") {
- EventSynchronizationContext.Post(_ => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)), null);
- }
-
- ///
- /// Clean up managed and, optionally, unmanaged resources.
- /// When inheriting from , you should override this method, dispose of your managed resources if is , then
- /// free your unmanaged resources regardless of the value of , and finally call this base implementation.
- /// For more information, see Implement
- /// the dispose pattern for a derived class.
- ///
- /// Should be when called from a finalizer, and when called from the method. In other words, it is
- /// when deterministically called and when non-deterministically called.
- protected virtual void Dispose(bool disposing) {
- if (disposing) {
- try {
- _cancellationTokenSource?.Cancel();
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
- } catch (AggregateException) { }
-
- lock (_hidStreamLock) {
- if (_hidStream != null) {
- _hidStream.Closed -= ReattachToDevice;
- _hidStream.Close();
- _hidStream.Dispose();
- _hidStream = null;
- }
- }
-
- if (_deviceList != null) {
- _deviceList.Changed -= onDeviceListChanged;
- _deviceList = null;
- }
- }
- }
-
- ///
- /// Disconnect from any connected PowerMate device and clean up managed resources.
- /// and events will not be emitted if a PowerMate is disconnected during disposal.
- /// Subclasses of should override .
- /// For more information, see Cleaning Up Unmanaged Resources and
- /// Implementing a Dispose Method.
- ///
- public void Dispose() {
- Dispose(true);
- GC.SuppressFinalize(this);
+ EventSynchronizationContext.Post(_ => OnPropertyChanged(propertyName), null);
}
}
\ No newline at end of file
diff --git a/PowerMate/PowerMateInput.cs b/PowerMate/PowerMateInput.cs
index c80f95c..ac40180 100644
--- a/PowerMate/PowerMateInput.cs
+++ b/PowerMate/PowerMateInput.cs
@@ -3,6 +3,16 @@
///
/// Event data describing how the PowerMate knob was rotated, pressed, or released. Emitted by .
///
+///
+/// This is received as a 7-byte array.
+/// 0: always 0?
+/// 1: 1 if knob is pushed down, 0 otherwise
+/// 2: knob rotation direction and distance, or 0 if not rotated
+/// 3: always 0?
+/// 4: led brightness, 0-255, reflects the pulsing brightness too
+/// 5: 0x10 when not pulsing, 0x21 when pulsing at speed 9, 10, or 11, 0x11 pulse speed 8
+/// 6: 0 when not pulsing, 0x1 speed 8, 0x2 speed 9, 0x4 when pulsing with speed 10, 0x6 when pulsing with speed 11, 0x8 when pulsing with speed 12
+///
public readonly struct PowerMateInput {
///
@@ -27,11 +37,16 @@
///
public readonly uint RotationDistance = 0;
+ internal readonly byte ActualLightBrightness;
+ internal readonly LightAnimation ActualLightAnimation;
+ internal readonly int ActualLightPulseSpeed;
+
///
/// Parse input from raw HID bytes.
///
/// 7-item byte array from the HID update
- public PowerMateInput(IReadOnlyList rawData): this(rawData[1] == 0x01, GetRotationDirection(rawData), GetRotationDistance(rawData)) { }
+ public PowerMateInput(IReadOnlyList rawData): this(rawData[1] == 0x01, DecodeRotationDirection(rawData), DecodeRotationDistance(rawData), rawData[4], DecodeLightAnimation(rawData),
+ DecodeLightPulseSpeed(rawData)) { }
///
/// Construct a synthetic instance, useful for testing.
@@ -45,18 +60,39 @@
/// events, each with this set to 1. As you rotate it faster, updates are batched and this number increases to 2 or more. The highest value I have seen is 8.This
/// is always non-negative, regardless of the rotation direction; use to determine the direction.If the knob is pressed without being rotated, this
/// will be 0 and will be .
- public PowerMateInput(bool isPressed, RotationDirection rotationDirection, uint rotationDistance) {
- IsPressed = isPressed;
- RotationDirection = rotationDirection;
- RotationDistance = rotationDistance;
+ public PowerMateInput(bool isPressed, RotationDirection rotationDirection, uint rotationDistance): this(isPressed, rotationDirection, rotationDistance, 0, LightAnimation.Solid, 0) { }
+
+ private PowerMateInput(bool isPressed, RotationDirection rotationDirection, uint rotationDistance, byte actualLightBrightness, LightAnimation actualLightAnimation, int actualLightPulseSpeed) {
+ IsPressed = isPressed;
+ RotationDirection = rotationDirection;
+ RotationDistance = rotationDistance;
+ ActualLightBrightness = actualLightBrightness;
+ ActualLightAnimation = actualLightAnimation;
+ ActualLightPulseSpeed = actualLightPulseSpeed;
}
- private static uint GetRotationDistance(IReadOnlyList rawData) => (uint) Math.Abs((int) (sbyte) rawData[2]);
+ private static uint DecodeRotationDistance(IReadOnlyList rawData) => (uint) Math.Abs((int) (sbyte) rawData[2]);
- private static RotationDirection GetRotationDirection(IReadOnlyList rawData) => rawData[2] switch {
+ private static RotationDirection DecodeRotationDirection(IReadOnlyList rawData) => rawData[2] switch {
> 0 and < 128 => RotationDirection.Clockwise,
> 128 => RotationDirection.Counterclockwise,
- 0 or _ => RotationDirection.None
+ _ => RotationDirection.None
+ };
+
+ /// on unknown values
+ private static LightAnimation DecodeLightAnimation(IReadOnlyList rawData) => (rawData[5] & 0b111) switch {
+ 0 => LightAnimation.Solid,
+ 1 => LightAnimation.Pulsing,
+ 4 => LightAnimation.SolidWhileAwakeAndPulsingDuringComputerStandby,
+ _ => throw new ArgumentOutOfRangeException($"Unknown light animation read: 0x{rawData[5]:x2}")
+ };
+
+ /// on unknown values
+ private static int DecodeLightPulseSpeed(IReadOnlyList rawData) => (rawData[5] >> 4) switch {
+ 0 => 7 - rawData[6] / 2,
+ 1 => 7 + rawData[6],
+ 2 => 8 + rawData[6] / 2,
+ _ => throw new ArgumentOutOfRangeException($"Unknown light pulse speed read: 0x{rawData[5]:x2} 0x{rawData[6]:x2}")
};
///
diff --git a/PowerMateVolume/PowerMateVolume.cs b/PowerMateVolume/PowerMateVolume.cs
index 21d242f..13c59be 100644
--- a/PowerMateVolume/PowerMateVolume.cs
+++ b/PowerMateVolume/PowerMateVolume.cs
@@ -8,10 +8,10 @@
volumeIncrement = 0.01f;
}
-using IPowerMateClient powerMateClient = new PowerMateClient();
-using IVolumeChanger volumeChanger = new VolumeChanger { VolumeIncrement = volumeIncrement };
+using IPowerMateClient powerMate = new PowerMateClient();
+using IVolumeChanger volumeChanger = new VolumeChanger { VolumeIncrement = volumeIncrement };
-powerMateClient.LightBrightness = 0;
+powerMate.LightBrightness = 0;
CancellationTokenSource cancellationTokenSource = new();
Console.CancelKeyPress += (_, eventArgs) => {
@@ -19,7 +19,7 @@
cancellationTokenSource.Cancel();
};
-powerMateClient.InputReceived += (_, powerMateEvent) => {
+powerMate.InputReceived += (_, powerMateEvent) => {
switch (powerMateEvent) {
case { IsPressed: true, RotationDirection: RotationDirection.None }:
volumeChanger.ToggleMute();
@@ -35,10 +35,11 @@
}
};
+//FIXME remove this once the light settings are being reset based on HID reads, not the computer resuming from standby
SystemEvents.PowerModeChanged += (_, args) => {
if (args.Mode == PowerModes.Resume) {
// #1: On Jarnsaxa, waking up from sleep resets the PowerMate's light settings, so set them all again
- powerMateClient.LightAnimation = powerMateClient.LightAnimation;
+ powerMate.LightAnimation = powerMate.LightAnimation;
}
};
diff --git a/PowerMateVolume/PowerMateVolume.csproj b/PowerMateVolume/PowerMateVolume.csproj
index 8b4d347..7d1d3a8 100644
--- a/PowerMateVolume/PowerMateVolume.csproj
+++ b/PowerMateVolume/PowerMateVolume.csproj
@@ -12,13 +12,14 @@
Ben Hutchison
© 2023 $(Authors)
PowerMate Volume
- 1.0.0
+ 1.0.1
$(AssemblyTitle)
$(Version)
app.manifest
false
true
powermate.ico
+ 1701;1702;NU1701
diff --git a/PowerMateVolume/VolumeChanger.cs b/PowerMateVolume/VolumeChanger.cs
index 9a1d0ab..13be51a 100644
--- a/PowerMateVolume/VolumeChanger.cs
+++ b/PowerMateVolume/VolumeChanger.cs
@@ -32,8 +32,8 @@ public class VolumeChanger: IVolumeChanger {
private readonly MMDeviceEnumerator _mmDeviceEnumerator = new();
- private MMDevice? _defaultAudioEndpoint;
- private AudioEndpointVolume? _audioEndpointVolume;
+ private MMDevice? _audioOutputEndpoint;
+ private AudioEndpointVolume? _audioOutputVolume;
private float _volumeIncrement = 0.01f;
@@ -51,33 +51,40 @@ public class VolumeChanger: IVolumeChanger {
public VolumeChanger() {
_mmDeviceEnumerator.DefaultDeviceChanged += onDefaultDeviceChanged;
- AttachToDefaultDevice();
+ AttachToDevice();
}
- private void AttachToDefaultDevice(MMDevice? newDefaultAudioEndpoint = null) {
- DetachFromDefaultDevice();
- _defaultAudioEndpoint = newDefaultAudioEndpoint ?? _mmDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
- _audioEndpointVolume = AudioEndpointVolume.FromDevice(_defaultAudioEndpoint);
+ private void AttachToDevice(MMDevice? newAudioEndpoint = null) {
+ DetachFromCurrentDevice();
+ try {
+ _audioOutputEndpoint = newAudioEndpoint ?? _mmDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
+ } catch (CoreAudioAPIException) {
+ // program started when there were no audio output devices connected
+ // leave _defaultAudioEndpoint null and wait for onDefaultDeviceChanged to update it when a device is connected
+ return;
+ }
+
+ _audioOutputVolume = AudioEndpointVolume.FromDevice(_audioOutputEndpoint);
}
- private void DetachFromDefaultDevice() {
- _audioEndpointVolume?.Dispose();
- _audioEndpointVolume = null;
- _defaultAudioEndpoint?.Dispose();
- _defaultAudioEndpoint = null;
+ private void DetachFromCurrentDevice() {
+ _audioOutputVolume?.Dispose();
+ _audioOutputVolume = null;
+ _audioOutputEndpoint?.Dispose();
+ _audioOutputEndpoint = null;
}
public void IncreaseVolume(int increments = 1) {
- if (_audioEndpointVolume is not null && increments != 0) {
- float newVolume = Math.Max(0, Math.Min(1, _audioEndpointVolume.MasterVolumeLevelScalar + VolumeIncrement * increments));
- _audioEndpointVolume.MasterVolumeLevelScalar = newVolume;
- Console.WriteLine($"Set volume to {newVolume:P2}");
+ if (_audioOutputVolume is not null && increments != 0) {
+ float newVolume = Math.Max(0, Math.Min(1, _audioOutputVolume.MasterVolumeLevelScalar + VolumeIncrement * increments));
+ _audioOutputVolume.MasterVolumeLevelScalar = newVolume;
+ // Console.WriteLine($"Set volume to {newVolume:P2}");
}
}
public void ToggleMute() {
- if (_audioEndpointVolume is not null) {
- _audioEndpointVolume.IsMuted ^= true;
+ if (_audioOutputVolume is not null) {
+ _audioOutputVolume.IsMuted ^= true;
}
}
@@ -86,14 +93,14 @@ public class VolumeChanger: IVolumeChanger {
eventArgs.TryGetDevice(out MMDevice? newDefaultAudioEndpoint);
if (newDefaultAudioEndpoint is not null) {
- AttachToDefaultDevice(newDefaultAudioEndpoint);
+ AttachToDevice(newDefaultAudioEndpoint);
}
}
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
- DetachFromDefaultDevice();
+ DetachFromCurrentDevice();
_mmDeviceEnumerator.DefaultDeviceChanged -= onDefaultDeviceChanged;
_mmDeviceEnumerator.Dispose();
}
diff --git a/PowerMateVolume/packages.lock.json b/PowerMateVolume/packages.lock.json
index d4e7440..fb88f80 100644
--- a/PowerMateVolume/packages.lock.json
+++ b/PowerMateVolume/packages.lock.json
@@ -8,6 +8,14 @@
"resolved": "1.2.1.2",
"contentHash": "DoQKUcdZLQawo3mOg8CvujJD9YkvTt6NtEu7S+OCo8+ySl8eCeUw4LFyL2E1OcAkPU918HZM6sUNcvIMcXWg6g=="
},
+ "HidClient": {
+ "type": "Transitive",
+ "resolved": "1.0.1",
+ "contentHash": "VtwKEL99yG3NHdTNbJQyyrx4KfzpQEmuIgWqd57f45pHf0qOHnzFjJ4lRKjmKIkNu28QXCVhHKnfV7ORyX+Z1w==",
+ "dependencies": {
+ "HidSharp": "2.1.0"
+ }
+ },
"HidSharp": {
"type": "Transitive",
"resolved": "2.1.0",
@@ -16,7 +24,7 @@
"powermate": {
"type": "Project",
"dependencies": {
- "HidSharp": "[2.1.0, )"
+ "HidClient": "[1.0.1, )"
}
}
},
diff --git a/Tests/.editorconfig b/Tests/.editorconfig
new file mode 100644
index 0000000..7fc29bf
--- /dev/null
+++ b/Tests/.editorconfig
@@ -0,0 +1,41 @@
+[*]
+
+# Exception Analyzers: Exception adjustments syntax error
+# default = error
+; dotnet_diagnostic.Ex0001.severity = none
+
+# Exception Analyzers: Exception adjustments syntax error: Symbol does not exist or identifier is invalid
+# default = warning
+; dotnet_diagnostic.Ex0002.severity = none
+
+# Exception Analyzers: Member may throw undocumented exception
+# default = warning
+dotnet_diagnostic.Ex0100.severity = none
+
+# Exception Analyzers: Member accessor may throw undocumented exception
+# default = warning
+dotnet_diagnostic.Ex0101.severity = none
+
+# Exception Analyzers: Implicit constructor may throw undocumented exception
+# default = warning
+dotnet_diagnostic.Ex0103.severity = none
+
+# Exception Analyzers: Member initializer may throw undocumented exception
+# default = warning
+dotnet_diagnostic.Ex0104.severity = none
+
+# Exception Analyzers: Delegate created from member may throw undocumented exception
+# default = silent
+; dotnet_diagnostic.Ex0120.severity = none
+
+# Exception Analyzers: Delegate created from anonymous function may throw undocumented exception
+# default = silent
+; dotnet_diagnostic.Ex0121.severity = none
+
+# Exception Analyzers: Member is documented as throwing exception not documented on member in base or interface type
+# default = warning
+dotnet_diagnostic.Ex0200.severity = none
+
+# Exception Analyzers: Member accessor is documented as throwing exception not documented on member in base or interface type
+# default = warning
+dotnet_diagnostic.Ex0201.severity = none
\ No newline at end of file
diff --git a/Tests/PowerMateClientInputTest.cs b/Tests/PowerMateClientInputTest.cs
index 0419faf..fc86e9c 100644
--- a/Tests/PowerMateClientInputTest.cs
+++ b/Tests/PowerMateClientInputTest.cs
@@ -1,4 +1,5 @@
using HidSharp;
+using PowerMate;
namespace Tests;
@@ -18,12 +19,13 @@ public class PowerMateClientInputTest {
A.CallTo(_device).Where(call => call.Method.Name == "OpenDeviceAndRestrictAccess").WithReturnType().Returns(_stream);
A.CallTo(() => _stream.ReadAsync(A._, An._, An._, A._)).ReturnsLazily(call => {
- byte[] buffer = (byte[]) call.Arguments[0]!;
- int offset = (int) call.Arguments[1]!;
- int count = (int) call.Arguments[2]!;
- byte[] fakeHidBytes = Convert.FromHexString("000100004F1000");
- Array.Copy(fakeHidBytes, 0, buffer, offset, count);
- return Task.FromResult(Math.Min(count, fakeHidBytes.Length));
+ byte[] buffer = (byte[]) call.Arguments[0]!;
+ int offset = (int) call.Arguments[1]!;
+ int count = (int) call.Arguments[2]!;
+ byte[] fakeHidBytes = Convert.FromHexString("000100004F1000");
+ int occupiedCount = Math.Min(count, fakeHidBytes.Length);
+ Array.Copy(fakeHidBytes, 0, buffer, offset, occupiedCount);
+ return Task.FromResult(occupiedCount);
});
}
@@ -48,94 +50,4 @@ public class PowerMateClientInputTest {
actualEvent!.Value.RotationDistance.Should().Be(0);
}
- [Fact]
- public void LateAttach() {
- A.CallTo(() => _deviceList.GetDevices(A._)).ReturnsNextFromSequence(
- Enumerable.Empty(),
- new[] { _device });
-
- bool? connectedEventArg = null;
- PowerMateClient client = new(_deviceList);
- client.IsConnected.Should().BeFalse();
- PowerMateInput? actualEvent = null;
- ManualResetEventSlim inputReceived = new();
- ManualResetEventSlim isConnectedChanged = new();
- client.InputReceived += (_, @event) => {
- actualEvent = @event;
- inputReceived.Set();
- };
- client.IsConnectedChanged += (_, b) => {
- connectedEventArg = b;
- isConnectedChanged.Set();
- };
-
- _deviceList.RaiseChanged();
-
- inputReceived.Wait(TestTimeout);
- isConnectedChanged.Wait(TestTimeout);
-
- client.IsConnected.Should().BeTrue();
- connectedEventArg.HasValue.Should().BeTrue();
- connectedEventArg!.Value.Should().BeTrue();
- actualEvent.HasValue.Should().BeTrue();
- actualEvent!.Value.IsPressed.Should().BeTrue();
- actualEvent!.Value.RotationDirection.Should().Be(RotationDirection.None);
- actualEvent!.Value.RotationDistance.Should().Be(0);
- }
-
- [Fact]
- public void Reconnect() {
- A.CallTo(() => _stream.ReadAsync(A._, An._, An._, A._))
- .ThrowsAsync(new IOException("fake disconnected")).Once().Then
- .ReturnsLazily(call => {
- byte[] buffer = (byte[]) call.Arguments[0]!;
- int offset = (int) call.Arguments[1]!;
- int count = (int) call.Arguments[2]!;
- byte[] fakeHidBytes = Convert.FromHexString("000100004F1000");
- Array.Copy(fakeHidBytes, 0, buffer, offset, count);
- return Task.FromResult(Math.Min(count, fakeHidBytes.Length));
- });
-
- ManualResetEventSlim eventArrived = new();
- PowerMateClient client = new(_deviceList);
- PowerMateInput? actualEvent = null;
- client.InputReceived += (_, @event) => {
- actualEvent = @event;
- eventArrived.Set();
- };
-
- _deviceList.RaiseChanged();
-
- eventArrived.Wait(TestTimeout);
- actualEvent.HasValue.Should().BeTrue();
- actualEvent!.Value.IsPressed.Should().BeTrue();
- actualEvent!.Value.RotationDirection.Should().Be(RotationDirection.None);
- actualEvent!.Value.RotationDistance.Should().Be(0);
- }
-
- [Fact]
- public void SynchronizationContext() {
- ManualResetEventSlim eventArrived = new();
- SynchronizationContext synchronizationContext = A.Fake();
- A.CallTo(() => synchronizationContext.Post(A._, An