Skip to content

Commit

Permalink
#3: Crash on resume if device is unplugged (or not responding) [retry…
Browse files Browse the repository at this point in the history
… setting features once after 2 seconds if it fails]
  • Loading branch information
Aldaviva committed Sep 5, 2023
1 parent 9bf7212 commit cf6c75a
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 19 deletions.
2 changes: 1 addition & 1 deletion PowerMate/PowerMate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageTags>griffin powermate hid rotary-encoder</PackageTags>
<PackageTags>griffin powermate hid rotary-encoder usb knob dial</PackageTags>
<PackageIcon>icon.jpg</PackageIcon>
<PackageReadmeFile>Readme.md</PackageReadmeFile>

Expand Down
12 changes: 9 additions & 3 deletions PowerMate/PowerMateClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,22 @@ public class PowerMateClient: AbstractHidClient, 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));

try {
DeviceStream?.SetFeature(featureData);
SetFeatureAndTime();
} 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);
SetFeatureAndTime();
}
}

void SetFeatureAndTime() {
if (DeviceStream != null) {
DeviceStream.SetFeature(featureData);
_mostRecentFeatureSetTime = DateTime.Now;
}
}
}
Expand Down
27 changes: 19 additions & 8 deletions PowerMateVolume/PowerMateVolume.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@

powerMate.LightBrightness = 0;

CancellationTokenSource cancellationTokenSource = new();
Console.CancelKeyPress += (_, eventArgs) => {
eventArgs.Cancel = true;
cancellationTokenSource.Cancel();
};

powerMate.InputReceived += (_, powerMateEvent) => {
switch (powerMateEvent) {
case { IsPressed: true, RotationDirection: RotationDirection.None }:
Expand All @@ -37,7 +31,24 @@
using IStandbyListener standbyListener = new EventLogStandbyListener();
standbyListener.FatalError += (_, exception) =>
MessageBox.Show("Event log subscription is broken, continuing without resume detection: " + exception, "PowerMateVolume", MessageBoxButtons.OK, MessageBoxIcon.Error);
standbyListener.Resumed += (_, _) => powerMate.SetAllFeaturesIfStale();
standbyListener.Resumed += (_, _) => {
try {
powerMate.SetAllFeaturesIfStale();
} catch (IOException) {
Thread.Sleep(2000);
try {
powerMate.SetAllFeaturesIfStale();
} catch (IOException) {
// device is probably in a bad state, but there's nothing we can do about it
}
}
};

CancellationTokenSource exitTokenSource = new();
Console.CancelKeyPress += (_, eventArgs) => {
eventArgs.Cancel = true;
exitTokenSource.Cancel();
};

Console.WriteLine("Listening for PowerMate events");
cancellationTokenSource.Token.WaitHandle.WaitOne();
exitTokenSource.Token.WaitHandle.WaitOne();
2 changes: 1 addition & 1 deletion PowerMateVolume/PowerMateVolume.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Authors>Ben Hutchison</Authors>
<Copyright>© 2023 $(Authors)</Copyright>
<AssemblyTitle>PowerMate Volume</AssemblyTitle> <!-- File description -->
<Version>1.0.2</Version> <!-- Product version -->
<Version>1.0.3</Version> <!-- Product version -->
<Product>$(AssemblyTitle)</Product> <!-- Product name -->
<FileVersion>$(Version)</FileVersion> <!-- File version -->
<ApplicationManifest>app.manifest</ApplicationManifest>
Expand Down
10 changes: 7 additions & 3 deletions PowerMateVolume/StandbyEventEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public interface IStandbyListener: IDisposable {

public class EventLogStandbyListener: IStandbyListener {

private const int StandByEventId = 42;
private const int ResumeEventId = 107;

public event EventHandler? StandingBy;
public event EventHandler? Resumed;
public event EventHandler<Exception>? FatalError;
Expand All @@ -21,7 +24,8 @@ public class EventLogStandbyListener: IStandbyListener {
/// <exception cref="EventLogNotFoundException">if the given event log or file was not found</exception>
/// <exception cref="UnauthorizedAccessException">if the log did not already exist and this program is not running elevated</exception>
public EventLogStandbyListener() {
_logWatcher = new EventLogWatcher(new EventLogQuery("System", PathType.LogName, "*[System[Provider/@Name=\"Microsoft-Windows-Kernel-Power\" and (EventID=42 or EventID=107)]]"));
_logWatcher = new EventLogWatcher(new EventLogQuery("System", PathType.LogName,
$"*[System[Provider/@Name=\"Microsoft-Windows-Kernel-Power\" and (EventID={StandByEventId} or EventID={ResumeEventId})]]"));

_logWatcher.EventRecordWritten += onEventRecord;

Expand All @@ -43,10 +47,10 @@ public class EventLogStandbyListener: IStandbyListener {
} else {
using EventRecord? record = e.EventRecord;
switch (record?.Id) {
case 42:
case StandByEventId:
StandingBy?.Invoke(this, EventArgs.Empty);
break;
case 107:
case ResumeEventId:
Resumed?.Invoke(this, EventArgs.Empty);
break;
}
Expand Down
40 changes: 37 additions & 3 deletions PowerMateVolume/VolumeChanger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,23 @@ public interface IVolumeChanger: IDisposable {
/// <param name="increments"></param>
void IncreaseVolume(int increments = 1);

/// <summary>
/// Get or set the default audio output device's volume.
/// </summary>
/// <value>The absolute volume level, in the range <c>[0, 1]</c>. Will be clipped if it's set to a value outside that range.</value>
float Volume { get; set; }

/// <summary>
/// <para>If the default audio output device is not currently muted, then mute it. Otherwise, unmute it.</para>
/// </summary>
void ToggleMute();

/// <summary>
/// Mute or unmute the default audio output device, or get whether or not it's currently muted.
/// </summary>
/// <value><see langword="true"/> if the device is or should be muted, or <see langword="false"/> if it is or should be unmuted.</value>
bool Muted { get; set; }

}

public class VolumeChanger: IVolumeChanger {
Expand Down Expand Up @@ -74,17 +86,39 @@ public class VolumeChanger: IVolumeChanger {
_audioOutputEndpoint = null;
}

/// <inheritdoc />
public void IncreaseVolume(int increments = 1) {
if (_audioOutputVolume is not null && increments != 0) {
float newVolume = Math.Max(0, Math.Min(1, _audioOutputVolume.MasterVolumeLevelScalar + VolumeIncrement * increments));
_audioOutputVolume.MasterVolumeLevelScalar = newVolume;
Volume = _audioOutputVolume.MasterVolumeLevelScalar + VolumeIncrement * increments;
// Console.WriteLine($"Set volume to {newVolume:P2}");
}
}

/// <inheritdoc />
public float Volume {
get => _audioOutputVolume?.MasterVolumeLevelScalar ?? 0;
set {
float newVolume = Math.Max(0, Math.Min(1, value));
if (_audioOutputVolume != null) {
_audioOutputVolume.MasterVolumeLevelScalar = newVolume;
}
}
}

/// <inheritdoc />
public void ToggleMute() {
if (_audioOutputVolume is not null) {
_audioOutputVolume.IsMuted ^= true;
Muted = !Muted;
}
}

/// <inheritdoc />
public bool Muted {
get => _audioOutputVolume?.IsMuted ?? true;
set {
if (_audioOutputVolume is not null) {
_audioOutputVolume.IsMuted = value;
}
}
}

Expand Down

0 comments on commit cf6c75a

Please sign in to comment.