From d6c4640051ff0215cd9a69d1665a1b26ff82008f Mon Sep 17 00:00:00 2001 From: Ronny Chan Date: Sun, 23 Feb 2020 18:31:35 -0500 Subject: [PATCH] Device: add API for capability labels --- .../Input/Device/IDeviceCapabilityLabels.cs | 20 ++++++++++ .../Input/Device/IInputDriverInstance.cs | 6 +++ .../Input/LabelsTests.cs | 35 ++++++++++++++++++ .../Device/DefaultDeviceCapabilityLabels.cs | 32 ++++++++++++++++ .../DictionaryDeviceCapabilityLabels.cs | 37 +++++++++++++++++++ .../Input/Device/DirectInputDeviceInstance.cs | 8 +++- .../Input/Device/KeyboardDeviceInstance.cs | 6 ++- .../Input/Device/PassthroughDeviceInstance.cs | 2 + .../Input/Device/XInputDeviceInstance.cs | 3 ++ .../Snowflake.Framework.xml | 22 +++++++++++ ...iceCapabilityLabelKeyValuePairGraphType.cs | 19 ++++++++++ .../DeviceCapabilityLabelsGraphType.cs | 23 ++++++++++++ .../InputDeviceInstanceGraphType.cs | 5 ++- .../WindowsDeviceEnumerator.cs | 4 +- 14 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 src/Snowflake.Framework.Primitives/Input/Device/IDeviceCapabilityLabels.cs create mode 100644 src/Snowflake.Framework.Tests/Input/LabelsTests.cs create mode 100644 src/Snowflake.Framework/Input/Device/DefaultDeviceCapabilityLabels.cs create mode 100644 src/Snowflake.Framework/Input/Device/DictionaryDeviceCapabilityLabels.cs create mode 100644 src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelKeyValuePairGraphType.cs create mode 100644 src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelsGraphType.cs diff --git a/src/Snowflake.Framework.Primitives/Input/Device/IDeviceCapabilityLabels.cs b/src/Snowflake.Framework.Primitives/Input/Device/IDeviceCapabilityLabels.cs new file mode 100644 index 000000000..bf42e557c --- /dev/null +++ b/src/Snowflake.Framework.Primitives/Input/Device/IDeviceCapabilityLabels.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Snowflake.Input.Device +{ + /// + /// Represents a mapping from to a label on the real device. + /// When enumerated, + /// + public interface IDeviceCapabilityLabels : IEnumerable> + { + /// + /// Gets a friendly string representation of the provided device capability. + /// + /// The to get a name for. + /// A string representation of the device capbility + string this[DeviceCapability capability] { get; } + } +} diff --git a/src/Snowflake.Framework.Primitives/Input/Device/IInputDriverInstance.cs b/src/Snowflake.Framework.Primitives/Input/Device/IInputDriverInstance.cs index fc678e766..b54dac832 100644 --- a/src/Snowflake.Framework.Primitives/Input/Device/IInputDriverInstance.cs +++ b/src/Snowflake.Framework.Primitives/Input/Device/IInputDriverInstance.cs @@ -78,5 +78,11 @@ public interface IInputDeviceInstance /// the natural mapping from controller element to a device capability. /// IDeviceLayoutMapping DefaultLayout { get; } + + /// + /// Gets the friendly label names for capabilities this device instance supports. + /// This collection must at least have labels for all supported capabilities in . + /// + IDeviceCapabilityLabels CapabilityLabels { get; } } } diff --git a/src/Snowflake.Framework.Tests/Input/LabelsTests.cs b/src/Snowflake.Framework.Tests/Input/LabelsTests.cs new file mode 100644 index 000000000..668a35cc9 --- /dev/null +++ b/src/Snowflake.Framework.Tests/Input/LabelsTests.cs @@ -0,0 +1,35 @@ +using Snowflake.Input.Device; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Snowflake.Input.Tests +{ + public class LabelsTests + { + [Fact] + public void DefaultLabels_Test() + { + Assert.Equal("Button0", DefaultDeviceCapabilityLabels.DefaultLabels[DeviceCapability.Button0]); + foreach(var (c, l) in DefaultDeviceCapabilityLabels.DefaultLabels) + { + Assert.Equal(l, c.ToString()); + } + } + + [Fact] + public void DictLabels_Test() + { + var dictLabels = new Dictionary() + { + { DeviceCapability.Button0, "Button A" } + }; + + IDeviceCapabilityLabels labels = new DictionaryDeviceCapabilityLabels(dictLabels); + Assert.Equal("Button A", labels[DeviceCapability.Button0]); + Assert.Single(labels); + Assert.Equal(string.Empty, labels[DeviceCapability.Axis0Negative]); + } + } +} diff --git a/src/Snowflake.Framework/Input/Device/DefaultDeviceCapabilityLabels.cs b/src/Snowflake.Framework/Input/Device/DefaultDeviceCapabilityLabels.cs new file mode 100644 index 000000000..7b1c1919d --- /dev/null +++ b/src/Snowflake.Framework/Input/Device/DefaultDeviceCapabilityLabels.cs @@ -0,0 +1,32 @@ +using EnumsNET; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Snowflake.Input.Device +{ + /// + /// Implements a default label mapping using the name of the capability enum element. + /// + public sealed class DefaultDeviceCapabilityLabels : IDeviceCapabilityLabels + { + /// + /// Default labels using the name of the capability enum element. + /// + public static IDeviceCapabilityLabels DefaultLabels => _DefaultLabels; + + private static readonly DefaultDeviceCapabilityLabels _DefaultLabels = new DefaultDeviceCapabilityLabels(); + + public string this[DeviceCapability capability] => Enums.AsString(capability); + + public IEnumerator> GetEnumerator() + { + return Enums.GetMembers() + .Select(e => KeyValuePair.Create(e.Value, e.AsString())).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} diff --git a/src/Snowflake.Framework/Input/Device/DictionaryDeviceCapabilityLabels.cs b/src/Snowflake.Framework/Input/Device/DictionaryDeviceCapabilityLabels.cs new file mode 100644 index 000000000..5833cbe0c --- /dev/null +++ b/src/Snowflake.Framework/Input/Device/DictionaryDeviceCapabilityLabels.cs @@ -0,0 +1,37 @@ +using EnumsNET; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Snowflake.Input.Device +{ + /// + /// Implements a label mapping using a backing dictionary. Missing labels are replaced with + /// the empty string. + /// + public sealed class DictionaryDeviceCapabilityLabels : IDeviceCapabilityLabels + { + /// + /// Create a new using a backing dictionary. + /// + /// The backing dictionary to use. + public DictionaryDeviceCapabilityLabels(IDictionary labels) + { + this.Labels = labels; + } + + private IDictionary Labels { get; } + + public string this[DeviceCapability capability] => + this.Labels.TryGetValue(capability, out string? value) ? value : string.Empty; + + public IEnumerator> GetEnumerator() + { + return this.Labels.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} diff --git a/src/Snowflake.Framework/Input/Device/DirectInputDeviceInstance.cs b/src/Snowflake.Framework/Input/Device/DirectInputDeviceInstance.cs index 392840660..7cbc4c923 100644 --- a/src/Snowflake.Framework/Input/Device/DirectInputDeviceInstance.cs +++ b/src/Snowflake.Framework/Input/Device/DirectInputDeviceInstance.cs @@ -9,8 +9,9 @@ public sealed class DirectInputDeviceInstance : IInputDeviceInstance internal DirectInputDeviceInstance(int enumerationIndex, int classEnumerationIndex, int uniqueNameEnumerationIndex, int productEnumerationIndex, - IReadOnlyDictionary capabilities, - IDeviceLayoutMapping defaultLayout) + IReadOnlyDictionary capabilities, + IDeviceLayoutMapping defaultLayout, + IDeviceCapabilityLabels labels) { this.EnumerationIndex = enumerationIndex; this.ClassEnumerationIndex = classEnumerationIndex; @@ -18,6 +19,7 @@ internal DirectInputDeviceInstance(int enumerationIndex, this.ProductEnumerationIndex = productEnumerationIndex; this.DefaultLayout = defaultLayout; this.CapabilityMap = capabilities; + this.CapabilityLabels = labels; } private IReadOnlyDictionary CapabilityMap { get; } @@ -35,6 +37,8 @@ internal DirectInputDeviceInstance(int enumerationIndex, public int ProductEnumerationIndex { get; } + public IDeviceCapabilityLabels CapabilityLabels { get; } + public int GetObjectOffset(DeviceCapability capability) => this.CapabilityMap.TryGetValue(capability, out (int offset, int _) val) ? val.offset : -1; diff --git a/src/Snowflake.Framework/Input/Device/KeyboardDeviceInstance.cs b/src/Snowflake.Framework/Input/Device/KeyboardDeviceInstance.cs index ab9cb322f..9f62f7762 100644 --- a/src/Snowflake.Framework/Input/Device/KeyboardDeviceInstance.cs +++ b/src/Snowflake.Framework/Input/Device/KeyboardDeviceInstance.cs @@ -49,8 +49,8 @@ public KeyboardDeviceInstance() this.Capabilities = DeviceCapabilityClasses.Keyboard .Concat(DeviceCapabilityClasses.MouseButtons) .Concat(DeviceCapabilityClasses.MouseCursor) - .Concat(new[] { DeviceCapability.Axis0, - DeviceCapability.Axis0Negative, + .Concat(new[] { DeviceCapability.Axis0, + DeviceCapability.Axis0Negative, DeviceCapability.Axis0Positive }) .ToList(); @@ -69,5 +69,7 @@ public KeyboardDeviceInstance() public int NameEnumerationIndex => 0; public int ProductEnumerationIndex => 0; + + public IDeviceCapabilityLabels CapabilityLabels => DefaultDeviceCapabilityLabels.DefaultLabels; } } diff --git a/src/Snowflake.Framework/Input/Device/PassthroughDeviceInstance.cs b/src/Snowflake.Framework/Input/Device/PassthroughDeviceInstance.cs index 39dc95fac..82c563dac 100644 --- a/src/Snowflake.Framework/Input/Device/PassthroughDeviceInstance.cs +++ b/src/Snowflake.Framework/Input/Device/PassthroughDeviceInstance.cs @@ -20,5 +20,7 @@ public sealed class PassthroughDeviceInstance : IInputDeviceInstance public int NameEnumerationIndex => 0; public int ProductEnumerationIndex => 0; + + public IDeviceCapabilityLabels CapabilityLabels => DefaultDeviceCapabilityLabels.DefaultLabels; } } diff --git a/src/Snowflake.Framework/Input/Device/XInputDeviceInstance.cs b/src/Snowflake.Framework/Input/Device/XInputDeviceInstance.cs index fd9066c89..cd7f3ca2d 100644 --- a/src/Snowflake.Framework/Input/Device/XInputDeviceInstance.cs +++ b/src/Snowflake.Framework/Input/Device/XInputDeviceInstance.cs @@ -106,5 +106,8 @@ public XInputDeviceInstance(int enumerationIndex) public IDeviceLayoutMapping DefaultLayout { get; } public int ProductEnumerationIndex => this.EnumerationIndex; + + // todo: make XInput specific labels! + public IDeviceCapabilityLabels CapabilityLabels => DefaultDeviceCapabilityLabels.DefaultLabels; } } diff --git a/src/Snowflake.Framework/Snowflake.Framework.xml b/src/Snowflake.Framework/Snowflake.Framework.xml index 8a9b0f4a4..bd6bb4a55 100644 --- a/src/Snowflake.Framework/Snowflake.Framework.xml +++ b/src/Snowflake.Framework/Snowflake.Framework.xml @@ -932,6 +932,16 @@ The vendor ID of the physical device for this set of mappings. The device layout mapping provided by the device enumerator. + + + Implements a default label mapping using the name of the capability enum element. + + + + + Default labels using the name of the capability enum element. + + Gets the capability class of this @@ -939,6 +949,18 @@ The The capability class of the capability. + + + Implements a label mapping using a backing dictionary. Missing labels are replaced with + the empty string. + + + + + Create a new using a backing dictionary. + + The backing dictionary to use. + diff --git a/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelKeyValuePairGraphType.cs b/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelKeyValuePairGraphType.cs new file mode 100644 index 000000000..ebc66f74a --- /dev/null +++ b/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelKeyValuePairGraphType.cs @@ -0,0 +1,19 @@ +using GraphQL.Types; +using Snowflake.Input.Device; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Snowflake.Support.GraphQLFrameworkQueries.Types.InputDevice +{ + public class DeviceCapabilityLabelKeyValuePairGraphType : ObjectGraphType> + { + public DeviceCapabilityLabelKeyValuePairGraphType() + { + Name = "DeviceCapabilityLabelKeyValuePair"; + Description = "A label of a device capability"; + Field("capability", description: "The capability this label describes.", resolve: c => c.Source.Key); + Field("label", description: "The label of the capability.", resolve: c => c.Source.Value); + } + } +} diff --git a/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelsGraphType.cs b/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelsGraphType.cs new file mode 100644 index 000000000..989223bde --- /dev/null +++ b/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/DeviceCapabilityLabelsGraphType.cs @@ -0,0 +1,23 @@ +using GraphQL.Types; +using Snowflake.Input.Device; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Snowflake.Support.GraphQLFrameworkQueries.Types.InputDevice +{ + public class DeviceCapabilityLabelsGraphType : ObjectGraphType + { + public DeviceCapabilityLabelsGraphType() + { + Name = "DeviceCapabilityLabels"; + Description = "A mapping of device capabilities to friendly string labels."; + Field>("labels", description: "A list of labels in this device instance", + resolve: c => c.Source); + foreach (var (key, _) in DefaultDeviceCapabilityLabels.DefaultLabels) + { + Field(key.ToString(), resolve: c => c.Source[key] ?? key.ToString()); + } + } + } +} diff --git a/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/InputDeviceInstanceGraphType.cs b/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/InputDeviceInstanceGraphType.cs index c86468870..5b7d0a9a8 100644 --- a/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/InputDeviceInstanceGraphType.cs +++ b/src/Snowflake.Support.GraphQLFrameworkQueries/Types/InputDevice/InputDeviceInstanceGraphType.cs @@ -22,7 +22,7 @@ public InputDeviceInstanceGraphType() resolve: context => context.Source.Capabilities); Field>("defaultLayout", description: "The default, assumed natural mapping from capability to virtual element without regard for any specific layout.", - resolve: context => (IEnumerable)context.Source.DefaultLayout); + resolve: context => context.Source.DefaultLayout); Field("enumerationIndex", description: "When enumerating devices with a given driver, the index of enumeration for this driver.", resolve: context => context.Source.EnumerationIndex); @@ -41,6 +41,9 @@ public InputDeviceInstanceGraphType() "with regards to the specific type of device, as determined by unique VID/product name," + "if and only if the driver disambiguates between different devices.", resolve: context => context.Source.ProductEnumerationIndex); + Field("capabilityLabels", + description: "Friendly labels for the device capabilities of this instance", + resolve: context => context.Source.CapabilityLabels); } } } diff --git a/src/Snowflake.Support.InputEnumerators.Windows/WindowsDeviceEnumerator.cs b/src/Snowflake.Support.InputEnumerators.Windows/WindowsDeviceEnumerator.cs index 116008dad..23cc5d161 100644 --- a/src/Snowflake.Support.InputEnumerators.Windows/WindowsDeviceEnumerator.cs +++ b/src/Snowflake.Support.InputEnumerators.Windows/WindowsDeviceEnumerator.cs @@ -70,18 +70,16 @@ public IEnumerable QueryConnectedDevices() // todo add support for mapping overrides instances.Add(new DirectInputDeviceInstance(allOrder, classOrder, nameOrder, prodOrder, capabilities, - GenerateDefaultMapping(capabilities.Keys))); + GenerateDefaultMapping(capabilities.Keys), DefaultDeviceCapabilityLabels.DefaultLabels)); yield return new InputDevice(vid, pid, name, name, path, device.Information.InstanceGuid, instances.AsReadOnly()); } - } private static readonly IDictionary _hidMappings = new Dictionary() { - {DeviceCapability.Button0, ControllerElement.ButtonA}, {DeviceCapability.Button1, ControllerElement.ButtonB}, {DeviceCapability.Button2, ControllerElement.ButtonX},