[WIP] Implement Windows Ink in user-mode

This commit is contained in:
X9VoiD 2020-08-05 17:32:49 +08:00
parent 218b03ed16
commit c51d350aea
No known key found for this signature in database
GPG key ID: 3AC5DBB8A2717CFF
5 changed files with 451 additions and 0 deletions

View file

@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MLFilter", "MLFilter\MLFilt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OemKill", "OemKill\OemKill.csproj", "{4EC8FA2D-0CCC-4FC3-A32A-BC063924A803}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInk", "WindowsInk\WindowsInk.csproj", "{FC135341-4870-4555-8917-7248CFE07FCC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -21,6 +23,10 @@ Global
{4EC8FA2D-0CCC-4FC3-A32A-BC063924A803}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EC8FA2D-0CCC-4FC3-A32A-BC063924A803}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EC8FA2D-0CCC-4FC3-A32A-BC063924A803}.Release|Any CPU.Build.0 = Release|Any CPU
{FC135341-4870-4555-8917-7248CFE07FCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC135341-4870-4555-8917-7248CFE07FCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC135341-4870-4555-8917-7248CFE07FCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC135341-4870-4555-8917-7248CFE07FCC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

143
WindowsInk/NativeMethods.cs Normal file
View file

@ -0,0 +1,143 @@
using System;
using System.Runtime.InteropServices;
namespace WindowsInk
{
using HSYNTHETICPOINTERDEVICE = IntPtr;
using HANDLE = IntPtr;
using HWND = IntPtr;
using DWORD = UInt32;
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
}
public enum POINTER_INPUT_TYPE
{
PT_POINTER,
PT_TOUCH,
PT_PEN,
PT_MOUSE,
PT_TOUCHPAD
}
public enum POINTER_FEEDBACK_MODE
{
DEFAULT,
INDIRECT,
NONE
}
public enum POINTER_BUTTON_CHANGE_TYPE
{
NONE,
FIRSTBUTTON_DOWN,
FIRSTBUTTON_UP,
SECONDBUTTON_DOWN,
SECONDBUTTON_UP,
THIRDBUTTON_DOWN,
THIRDBUTTON_UP,
FOURTHBUTTON_DOWN,
FOURTHBUTTON_UP,
FIFTHBUTTON_DOWN,
FIFTHBUTTON_UP
}
public enum POINTER_FLAGS
{
POINTER_FLAG_NONE = 0x00000000,
POINTER_FLAG_NEW = 0x00000001,
POINTER_FLAG_INRANGE = 0x00000002,
POINTER_FLAG_INCONTACT = 0x00000004,
POINTER_FLAG_FIRSTBUTTON = 0x00000010,
POINTER_FLAG_SECONDBUTTON = 0x00000020,
POINTER_FLAG_THIRDBUTTON = 0x00000040,
POINTER_FLAG_FOURTHBUTTON = 0x00000080,
POINTER_FLAG_FIFTHBUTTON = 0x00000100,
POINTER_FLAG_PRIMARY = 0x00002000,
POINTER_FLAG_CONFIDENCE = 0x000004000,
POINTER_FLAG_CANCELED = 0x000008000,
POINTER_FLAG_DOWN = 0x00010000,
POINTER_FLAG_UPDATE = 0x00020000,
POINTER_FLAG_UP = 0x00040000,
POINTER_FLAG_WHEEL = 0x00080000,
POINTER_FLAG_HWHEEL = 0x00100000,
POINTER_FLAG_CAPTURECHANGED = 0x00200000,
POINTER_FLAG_HASTRANSFORM = 0x00400000,
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTER_INFO
{
public POINTER_INPUT_TYPE pointerType;
public uint pointerId;
public uint frameId;
public POINTER_FLAGS pointerFlags;
public HANDLE sourceDevice;
public HWND hwndTarget;
public POINT ptPixelLocation;
public POINT ptHimetricLocation;
public POINT ptPixelLocationRaw;
public POINT ptHimetricLocationRaw;
public DWORD dwTime;
public uint historyCount;
public int InputData;
public DWORD dwKeyStates;
public ulong PerformanceCount;
public POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
}
public enum PEN_FLAGS
{
NONE = 0x00000000,
BARREL = 0x00000001,
INVERTED = 0x00000002,
ERASER = 0x00000004
}
public enum PEN_MASK
{
NONE = 0x00000000,
PRESSURE = 0x00000001,
ROTATION = 0x00000002,
TILT_X = 0x00000004,
TILT_Y = 0x00000008
}
public struct POINTER_PEN_INFO
{
public POINTER_INFO pointerInfo;
public PEN_FLAGS pointerFlags;
public PEN_MASK penMask;
public uint pressure;
public uint rotation;
public int tiltX;
public int tiltY;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTER_TYPE_INFO
{
public POINTER_INPUT_TYPE type;
public POINTER_PEN_INFO penInfo;
}
public static class NativeMethods
{
[DllImport("user32.dll")]
public static extern HSYNTHETICPOINTERDEVICE CreateSyntheticPointerDevice(POINTER_INPUT_TYPE pointerType,
UInt32 maxCount, POINTER_FEEDBACK_MODE mode);
[DllImport("user32.dll")]
public static extern bool InjectSyntheticPointerInput(HSYNTHETICPOINTERDEVICE device,
[In, MarshalAs(UnmanagedType.LPArray)] POINTER_TYPE_INFO[] pointerInfo, UInt32 count);
}
}

102
WindowsInk/Pen.cs Normal file
View file

@ -0,0 +1,102 @@
using System;
using TabletDriverPlugin;
namespace WindowsInk
{
using HSYNTHETICPOINTERDEVICE = IntPtr;
public class Pen
{
private POINTER_PEN_INFO _penInfo;
private POINTER_INFO _pointerInfo;
private HSYNTHETICPOINTERDEVICE _penHandle;
private bool inContact = false;
private POINTER_FLAGS pointerFlags;
public Pen()
{
_pointerInfo = new POINTER_INFO
{
pointerType = POINTER_INPUT_TYPE.PT_PEN,
pointerId = 1,
frameId = 0,
pointerFlags = POINTER_FLAGS.POINTER_FLAG_NONE,
sourceDevice = IntPtr.Zero,
hwndTarget = IntPtr.Zero,
ptPixelLocation = new POINT(),
ptHimetricLocation = new POINT(),
ptPixelLocationRaw = new POINT(),
ptHimetricLocationRaw = new POINT(),
dwTime = 0,
historyCount = 1,
dwKeyStates = 0,
PerformanceCount = 0,
ButtonChangeType = POINTER_BUTTON_CHANGE_TYPE.NONE
};
_penInfo = new POINTER_PEN_INFO
{
pointerInfo = _pointerInfo,
pointerFlags = PEN_FLAGS.NONE,
penMask = PEN_MASK.NONE,
pressure = 0,
rotation = 0,
tiltX = 0,
tiltY = 0
};
// Retrieve handle to custom pen
_penHandle = NativeMethods.CreateSyntheticPointerDevice(POINTER_INPUT_TYPE.PT_PEN, 1, POINTER_FEEDBACK_MODE.INDIRECT);
// Notify WindowsInk
_pointerInfo.pointerFlags = POINTER_FLAGS.POINTER_FLAG_NEW;
Inject();
// Back to normal state
_pointerInfo.pointerFlags = POINTER_FLAGS.POINTER_FLAG_INRANGE | POINTER_FLAGS.POINTER_FLAG_PRIMARY;
}
public void Inject()
{
var pointer = new POINTER_TYPE_INFO
{
type = POINTER_INPUT_TYPE.PT_PEN,
penInfo = _penInfo
};
if (!NativeMethods.InjectSyntheticPointerInput(_penHandle, new POINTER_TYPE_INFO[] { pointer }, 1))
{
Log.Write("WindowsInk", "Injection Failed");
}
}
public void SetPosition(POINT point)
{
_pointerInfo.ptPixelLocation = point;
_pointerInfo.ptPixelLocationRaw = point;
_pointerInfo.ptHimetricLocation = point;
_pointerInfo.ptHimetricLocationRaw = point;
}
public void SetPressure(uint pressure)
{
if (pressure > 0)
inContact = true;
_penInfo.pressure = pressure;
}
public void SetPointerFlags(POINTER_FLAGS flags)
{
pointerFlags |= flags;
}
public void UnsetPointerFlags(POINTER_FLAGS flags)
{
pointerFlags &= ~flags;
}
public void ClearPointerFlags()
{
pointerFlags = 0;
}
}
}

189
WindowsInk/WindowsInk.cs Normal file
View file

@ -0,0 +1,189 @@
using System.Collections.Generic;
using System.Linq;
using TabletDriverPlugin;
using TabletDriverPlugin.Attributes;
using TabletDriverPlugin.Output;
using TabletDriverPlugin.Platform.Display;
using TabletDriverPlugin.Platform.Pointer;
using TabletDriverPlugin.Tablet;
namespace WindowsInk
{
[PluginName("Windows Ink")]
public class InkOutputMode : IOutputMode
{
private float[] _rotationMatrix;
private float _halfDisplayWidth, _halfDisplayHeight, _halfTabletWidth, _halfTabletHeight;
private float _minX, _maxX, _minY, _maxY;
private List<IFilter> _filters, _preFilters = new List<IFilter>(), _postFilters = new List<IFilter>();
public IEnumerable<IFilter> Filters
{
set
{
_filters = value.ToList();
_preFilters.Clear();
_postFilters.Clear();
foreach (IFilter filter in _filters)
if (filter.FilterStage == FilterStage.PreTranspose)
_preFilters.Add(filter);
else if (filter.FilterStage == FilterStage.PostTranspose)
_postFilters.Add(filter);
}
get => _filters;
}
private TabletProperties _tabletProperties;
public TabletProperties TabletProperties
{
set
{
_tabletProperties = value;
UpdateCache();
}
get => _tabletProperties;
}
private Area _displayArea, _tabletArea;
public Area Input
{
set
{
_tabletArea = value;
UpdateCache();
}
get => _tabletArea;
}
public Area Output
{
set
{
_displayArea = value;
UpdateCache();
}
get => _displayArea;
}
public IVirtualScreen VirtualScreen { set; get; }
public IPointerHandler PointerHandler => new InkPointerHandler();
public bool AreaClipping { set; get; }
internal void UpdateCache()
{
_rotationMatrix = Input?.GetRotationMatrix();
_halfDisplayWidth = Output?.Width / 2 ?? 0;
_halfDisplayHeight = Output?.Height / 2 ?? 0;
_halfTabletWidth = Input?.Width / 2 ?? 0;
_halfTabletHeight = Input?.Height / 2 ?? 0;
_minX = Output?.Position.X - _halfDisplayWidth ?? 0;
_maxX = Output?.Position.X + Output?.Width - _halfDisplayWidth ?? 0;
_minY = Output?.Position.Y - _halfDisplayHeight ?? 0;
_maxY = Output?.Position.Y + Output?.Height - _halfDisplayHeight ?? 0;
}
public void Read(IDeviceReport report)
{
if (report is ITabletReport tabletReport)
{
if (TabletProperties.ActiveReportID.IsInRange(tabletReport.ReportID))
{
if (PointerHandler is IPressureHandler pressureHandler)
pressureHandler.SetPressure((float)tabletReport.Pressure / (float)TabletProperties.MaxPressure);
var pos = Transpose(tabletReport);
PointerHandler.SetPosition(pos);
}
}
// HandleBinding(report);
}
internal Point Transpose(ITabletReport report)
{
var pos = new Point(report.Position.X, report.Position.Y);
// Pre Filter
foreach (IFilter filter in _preFilters)
pos = filter.Filter(pos);
// Normalize (ratio of 1)
pos.X /= TabletProperties.MaxX;
pos.Y /= TabletProperties.MaxY;
// Scale to tablet dimensions (mm)
pos.X *= TabletProperties.Width;
pos.Y *= TabletProperties.Height;
// Adjust area to set origin to 0,0
pos -= Input.Position;
// Rotation
if (Input.Rotation != 0f)
{
var tempCopy = new Point(pos.X, pos.Y);
pos.X = (tempCopy.X * _rotationMatrix[0]) + (tempCopy.Y * _rotationMatrix[1]);
pos.Y = (tempCopy.X * _rotationMatrix[2]) + (tempCopy.Y * _rotationMatrix[3]);
}
// Move area back
pos.X += _halfTabletWidth;
pos.Y += _halfTabletHeight;
// Scale to tablet area (ratio of 1)
pos.X /= Input.Width;
pos.Y /= Input.Height;
// Scale to display area
pos.X *= Output.Width;
pos.Y *= Output.Height;
// Adjust display offset by center
pos.X += Output.Position.X - _halfDisplayWidth;
pos.Y += Output.Position.Y - _halfDisplayHeight;
// Clipping to display bounds
if (AreaClipping)
{
if (pos.X < _minX)
pos.X = _minX;
if (pos.X > _maxX)
pos.X = _maxX;
if (pos.Y < _minY)
pos.Y = _minY;
if (pos.Y > _maxY)
pos.Y = _maxY;
}
// Post Filter
foreach (IFilter filter in _postFilters)
pos = filter.Filter(pos);
return pos;
}
}
public class InkPointerHandler : IPointerHandler, IPressureHandler
{
private Pen pen = new Pen();
private Point _lastPos;
public Point GetPosition()
{
return _lastPos;
}
public void SetPosition(Point pos)
{
pen.SetPosition(new POINT((int)pos.X, (int)pos.Y));
pen.Inject();
_lastPos = pos;
}
public void SetPressure(float percentage)
{
pen.SetPressure((uint)(percentage * 1024));
}
}
}

View file

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net5.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TabletDriverPlugin" Version="0.3.2" />
</ItemGroup>
</Project>