Skip to content

Various small updates #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed Images/Screenshot.png
Binary file not shown.
Binary file added Images/screenshot-green.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/screenshot-red.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/screenshot-tray.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Source/RawInput.Touchpad/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
Expand Down
284 changes: 218 additions & 66 deletions Source/RawInput.Touchpad/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<string> _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));
}
}
}
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<string> _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);
}
}

}
}
3 changes: 2 additions & 1 deletion Source/RawInput.Touchpad/RawInput.Touchpad.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<Authors>emoacht</Authors>
<Copyright>Copyright © 2021 emoacht</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
3 changes: 2 additions & 1 deletion Source/RawInput.Touchpad/TouchpadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down