diff --git a/Images/Screenshot.png b/Images/Screenshot.png deleted file mode 100644 index cdafab9..0000000 Binary files a/Images/Screenshot.png and /dev/null differ diff --git a/Images/screenshot-green.png b/Images/screenshot-green.png new file mode 100644 index 0000000..29d0a48 Binary files /dev/null and b/Images/screenshot-green.png differ diff --git a/Images/screenshot-red.png b/Images/screenshot-red.png new file mode 100644 index 0000000..cb15d0c Binary files /dev/null and b/Images/screenshot-red.png differ diff --git a/Images/screenshot-tray.png b/Images/screenshot-tray.png new file mode 100644 index 0000000..c351163 Binary files /dev/null and b/Images/screenshot-tray.png differ diff --git a/README.md b/README.md index 40f56ae..c797ded 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,23 @@ Sample to capture inputs from Precision Touchpad by [Raw Input](https://docs.mic ## Requirements -- .NET 5.0 +- .NET 9.0 -## Example +Originally built on .NET 5.0, and upgraded in 2025, but no changes were needed. So, it should easily run on older versions of .NET -When five fingers are touching the touchpad of Surface Pro 4 Type Cover, five contacts appear with each coordinates. +## Examples -![Screenshot](Images/Screenshot.png) +When there are no fingers touching the touchpad, the window turns red + +![Screenshot](Images/screenshot-red.png) + +When five fingers are touching the touchpad of Surface Pro 4 Type Cover, five contacts appear with each coordinates, and the window turns green + +![Screenshot](Images/screenshot-green.png) + +When minimized the program shows up as a circle icon in the tray which is red when no touch is detected. When touch is detected it turns green and shows the number of touches. + +![Screenshot](Images/screenshot-tray.png) ## License diff --git a/Source/RawInput.Touchpad/MainWindow.xaml b/Source/RawInput.Touchpad/MainWindow.xaml index 3ca5b68..a270fd5 100644 --- a/Source/RawInput.Touchpad/MainWindow.xaml +++ b/Source/RawInput.Touchpad/MainWindow.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="WindowRoot" Title="RawInput Touchpad" - Height="260" Width="400" ResizeMode="NoResize"> + Height="180" Width="300" ResizeMode="CanMinimize"> diff --git a/Source/RawInput.Touchpad/MainWindow.xaml.cs b/Source/RawInput.Touchpad/MainWindow.xaml.cs index 3af334c..c994603 100644 --- a/Source/RawInput.Touchpad/MainWindow.xaml.cs +++ b/Source/RawInput.Touchpad/MainWindow.xaml.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -14,69 +17,218 @@ namespace RawInput.Touchpad { - public partial class MainWindow : Window - { - public bool TouchpadExists - { - get { return (bool)GetValue(TouchpadExistsProperty); } - set { SetValue(TouchpadExistsProperty, value); } - } - public static readonly DependencyProperty TouchpadExistsProperty = - DependencyProperty.Register("TouchpadExists", typeof(bool), typeof(MainWindow), new PropertyMetadata(false)); - - public string TouchpadContacts - { - get { return (string)GetValue(TouchpadContactsProperty); } - set { SetValue(TouchpadContactsProperty, value); } - } - public static readonly DependencyProperty TouchpadContactsProperty = - DependencyProperty.Register("TouchpadContacts", typeof(string), typeof(MainWindow), new PropertyMetadata(null)); - - public MainWindow() - { - InitializeComponent(); - } - - private HwndSource _targetSource; - private readonly List _log = new(); - - protected override void OnSourceInitialized(EventArgs e) - { - base.OnSourceInitialized(e); - - _targetSource = PresentationSource.FromVisual(this) as HwndSource; - _targetSource?.AddHook(WndProc); - - TouchpadExists = TouchpadHelper.Exists(); - - _log.Add($"Precision touchpad exists: {TouchpadExists}"); - - if (TouchpadExists) - { - var success = TouchpadHelper.RegisterInput(_targetSource.Handle); - - _log.Add($"Precision touchpad registered: {success}"); - } - } - - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - switch (msg) - { - case TouchpadHelper.WM_INPUT: - var contacts = TouchpadHelper.ParseInput(lParam); - TouchpadContacts = string.Join(Environment.NewLine, contacts.Select(x => x.ToString())); - - _log.Add("---"); - _log.Add(TouchpadContacts); - break; - } - return IntPtr.Zero; - } - - private void Copy_Click(object sender, RoutedEventArgs e) - { - Clipboard.SetText(string.Join(Environment.NewLine, _log)); - } - } -} \ No newline at end of file + public partial class MainWindow : Window + { + [System.Runtime.InteropServices.DllImport("user32.dll")] + static extern bool DestroyIcon(IntPtr handle); + + public bool TouchpadExists + { + get { return (bool)GetValue(TouchpadExistsProperty); } + set { SetValue(TouchpadExistsProperty, value); } + } + + public static readonly DependencyProperty TouchpadExistsProperty = + DependencyProperty.Register( + "TouchpadExists", + typeof(bool), + typeof(MainWindow), + new PropertyMetadata(false) + ); + + public string TouchpadContacts + { + get { return (string)GetValue(TouchpadContactsProperty); } + set { SetValue(TouchpadContactsProperty, value); } + } + + public static readonly DependencyProperty TouchpadContactsProperty = + DependencyProperty.Register( + "TouchpadContacts", + typeof(string), + typeof(MainWindow), + new PropertyMetadata(null) + ); + + // reference to tray icon + private System.Windows.Forms.NotifyIcon _notifyIcon; + + // tracks if touch is showing, used to avoid updating the icon if it is already correct + private int _touchesShowing = 0; + + // when was the last touch event received + private long _lastTouchTime = 0; + + // timer to expire last touch event to detect when no one is touching the track pad anymore + private Timer _touchTimer; + + // list of all received touch events + private readonly List _log = new(); + + public MainWindow() + { + InitializeComponent(); + + // add tray icon + _notifyIcon = new System.Windows.Forms.NotifyIcon + { + Text = "Touchpad Monitor", + Visible = true, + }; + SetIconState(System.Drawing.Color.Red, ' '); + // restore window on tray icon click + _notifyIcon.Click += (s, e) => RestoreWindow(); + // keep window on top when showing + Topmost = true; + // start window in bottom right corner + var workingArea = SystemParameters.WorkArea; + Left = workingArea.Width - Width; + Top = workingArea.Height - Height; + // start timer to track expired touches + _touchTimer = new Timer(TouchTimerCallback, null, 0, 50); + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + _notifyIcon.Dispose(); + } + + protected override void OnStateChanged(EventArgs e) + { + base.OnStateChanged(e); + + if (WindowState == WindowState.Minimized) + { + Hide(); + _notifyIcon.Visible = true; + } + } + + protected override void OnSourceInitialized(EventArgs e) + { + base.OnSourceInitialized(e); + + var targetSource = PresentationSource.FromVisual(this) as HwndSource; + targetSource?.AddHook(WndProc); + + TouchpadExists = TouchpadHelper.Exists(); + + _log.Add($"Precision touchpad exists: {TouchpadExists}"); + + if (TouchpadExists) + { + var success = TouchpadHelper.RegisterInput(targetSource.Handle); + + _log.Add($"Precision touchpad registered: {success}"); + } + + Visibility = Visibility.Hidden; + } + + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + switch (msg) + { + case TouchpadHelper.WM_INPUT: + var contacts = TouchpadHelper.ParseInput(lParam); + _lastTouchTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + TouchpadContacts = string.Join( + Environment.NewLine, + contacts.Select(x => x.ToString()) + ); + + var touchCount = contacts.Length; + + if (touchCount != _touchesShowing) + { + _touchesShowing = touchCount; + Background = new SolidColorBrush( + System.Windows.Media.Color.FromRgb(144, 238, 144) + ); + SetIconState(System.Drawing.Color.Green, touchCount.ToString().ToCharArray()[0]); + } + + _log.Add("---"); + _log.Add(TouchpadContacts); + break; + } + return IntPtr.Zero; + } + + private void Copy_Click(object sender, RoutedEventArgs e) + { + Clipboard.SetText(string.Join(Environment.NewLine, _log)); + } + + private void TouchTimerCallback(object state) + { + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (now - _lastTouchTime > 100 && _touchesShowing > 0) + { + Dispatcher.Invoke(() => + { + _touchesShowing = 0; + TouchpadContacts = ""; + Background = new SolidColorBrush( + System.Windows.Media.Color.FromRgb(238, 144, 144) + ); + SetIconState(System.Drawing.Color.Red, ' '); + }); + } + } + + private void RestoreWindow() + { + Show(); + WindowState = WindowState.Normal; + Activate(); + _notifyIcon.Visible = false; + } + + private void SetIconState(System.Drawing.Color color, char label) + { + using (var icon = CreateCircleIcon(color, label)) + { + _notifyIcon.Icon = icon; + DestroyIcon(icon.Handle); + } + } + + private static Icon CreateCircleIcon(System.Drawing.Color color, char character) + { + int size = 32; + using (var bitmap = new Bitmap(size, size)) + { + using (var graphics = Graphics.FromImage(bitmap)) + { + graphics.Clear(System.Drawing.Color.Transparent); + + // Draw the circle with the specified color + using (var brush = new SolidBrush(color)) + { + graphics.FillEllipse(brush, 0, 0, size, size); + } + + // Set up the font and brush for the character + using var font = new Font(System.Drawing.FontFamily.GenericSansSerif, 6, System.Drawing.FontStyle.Bold); + using var textBrush = new SolidBrush(System.Drawing.Color.White); + // Measure the size of the character to center it + SizeF textSize = graphics.MeasureString(character.ToString(), font); + + // Calculate the position to center the character + float x = (size - textSize.Width) / 2; + float y = (size - textSize.Height) / 2; + + // Draw the character centered on the circle + graphics.DrawString(character.ToString(), font, textBrush, x, y); + } + + IntPtr hIcon = bitmap.GetHicon(); + return System.Drawing.Icon.FromHandle(hIcon); + } + } + + } +} diff --git a/Source/RawInput.Touchpad/RawInput.Touchpad.csproj b/Source/RawInput.Touchpad/RawInput.Touchpad.csproj index 7521ca4..d89911f 100644 --- a/Source/RawInput.Touchpad/RawInput.Touchpad.csproj +++ b/Source/RawInput.Touchpad/RawInput.Touchpad.csproj @@ -2,8 +2,9 @@ WinExe - net5.0-windows + net9.0-windows true + true emoacht Copyright © 2021 emoacht MIT diff --git a/Source/RawInput.Touchpad/TouchpadHelper.cs b/Source/RawInput.Touchpad/TouchpadHelper.cs index 494f8b4..7973985 100644 --- a/Source/RawInput.Touchpad/TouchpadHelper.cs +++ b/Source/RawInput.Touchpad/TouchpadHelper.cs @@ -297,7 +297,8 @@ public static bool RegisterInput(IntPtr windowHandle) { usUsagePage = 0x000D, usUsage = 0x0005, - dwFlags = 0, // WM_INPUT messages come only when the window is in the foreground. + dwFlags = 0x00000100, // WM_INPUT messages come regardless of what window is in focus + // dwFlags = 0, // WM_INPUT messages come only when the window is in the foreground. hwndTarget = windowHandle };