Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TouchCursorRestore

Keeps the mouse cursor where it is when you touch a touchscreen.

English · Nederlands


English

The problem

Windows turns every touchscreen tap into a simulated mouse move and click at that location ("touch-to-mouse promotion"). That mechanism exists so applications without native touch support still respond to touch — but the side effect is that the cursor jumps away from the monitor you were working on to the touchscreen. On a desk where you constantly switch between mouse and touchscreen, that costs you a moment of hunting for the cursor every single time.

TouchCursorRestore is a system-tray application (~160 KB, no window, no installer) that fixes this without breaking touch input.

Design goals

  1. Works regardless of whether the application supports touch natively.
  2. Works in fullscreen and windowed mode, with any number of displays.
  3. Touch input stays fully intact — tap, drag, scroll, double-tap and press-and-hold (right-click emulation) all keep working unchanged.
  4. Invisible in daily use, apart from the tray icon.
  5. Can be switched off temporarily without quitting the application.

Goal 3 is the key constraint: it determines when the cursor is allowed to be restored.

How it works

Detecting touch events

The application installs a low-level mouse hook (SetWindowsHookEx with WH_MOUSE_LL). Windows tags mouse events originating from touch or pen with a recognisable signature in dwExtraInfo. It must be compared with a mask, not for exact equality — the low byte carries a cursor id and differs per event:

const uint MI_WP_SIGNATURE = 0xFF515700;
const uint SIGNATURE_MASK  = 0xFFFFFF00;

uint extra = (uint)hookStruct.dwExtraInfo;
bool isTouchOrPen = (extra & SIGNATURE_MASK) == MI_WP_SIGNATURE;

The simulated mouse click is deliberately allowed through, not blocked. Blocking it would mean applications without native touch support stop responding to touch entirely.

Avoiding a feedback loop

The restore move is performed with SendInput carrying a unique value in dwExtraInfo (0x54435231, "TCR1"). The hook recognises that value and leaves the event alone — otherwise the application would overwrite its own reference point. SetCursorPos offers no such tagging and is used only as a fallback when SendInput fails or lands off target.

When to restore — the state machine

Restoring immediately at the start of a touch is technically possible but breaks touch input: many applications decide their drag and scroll behaviour from the current cursor position. Hence:

State Behaviour
Mouse active Cursor position tracked continuously. No intervention.
Touch in progress (touch-down to touch-up) All events pass through untouched. Cursor stays on the touchscreen.
Cooldown (after touch-up, 200 ms by default) A new touch within the window returns to the previous state, so double-tap and compound gestures stay intact.
Restore If the window expires without a new touch, the cursor returns to the last known mouse position.

If the physical mouse moves during the cooldown, the pending restore is cancelled — the user has already taken the cursor back.

Two working modes

  • Restore after touch (default) — the state machine above.
  • Block mouse movement — suppresses only the touch-generated WM_MOUSEMOVE while letting button-down and button-up through. The cursor then never jumps at all. The risk is that Windows partly assigns the click to a window based on cursor position, which would land the touch in the wrong place. Try it per application; switching is live from the tray menu.

Raw Input as a second signal

The application also registers for Raw Input on mouse devices (RIDEV_INPUTSINK). Touch digitizers typically report absolute mouse movement; those are explicitly ignored so the saved mouse position is never overwritten with the touch point. Only relative movement counts as the physical mouse.

Technical notes

These determine reliability in production and are in the code from the start:

  • Hook stability. The callback delegate is held in a field; otherwise the garbage collector reclaims it and the hook stops without any notice.
  • Hook response time. Windows removes a low-level hook when the callback responds too slowly (roughly 300 ms). The callback therefore does no logging, no disk access and no blocking work — AppLog pushes messages onto a ConcurrentQueue and processes them on a timer thread.
  • DPI scaling. The application is declared Per Monitor V2 DPI aware, so coordinates are not virtualised by Windows and the cursor is restored exactly.
  • Negative coordinates. With the touchscreen left of or above the primary monitor, coordinates are negative. All maths works off the virtual desktop (SM_XVIRTUALSCREEN through SM_CYVIRTUALSCREEN), not the primary monitor.
  • INPUT struct layout. On x64, LayoutKind.Sequential inserts the required 4-byte padding before the union (native INPUT is 40 bytes). A FieldOffset(4) variant overlays mi onto that padding and SendInput then reads garbage coordinates. InputLayoutCheck verifies this at startup and fails the process if the layout drifts.
  • Session and display changes. The hook is reinstalled after a session switch and after a display configuration change.
  • Privilege level. A hook at standard integrity level does not see input going to processes running elevated. If those applications run elevated, TouchCursorRestore must too (and should then autostart via Task Scheduler rather than the Run key, to avoid a UAC prompt at logon).

Installation

  1. Download TouchCursorRestore-v2.0.0.zip from the latest release.
  2. Extract anywhere. No installer, no registry changes until you enable autostart.
  3. Run TouchCursorRestore.exe. The icon appears in the system tray.

Requires Windows 10/11 x64 and the .NET 10 Desktop Runtime (download).

Usage

Right-click the tray icon:

Menu item Effect
Ingeschakeld (Enabled) Temporarily stops restoring without quitting.
Modus (Mode) Switches between "restore after touch" and "block mouse movement".
Herstelvertraging (Restore delay) Cooldown window: 100 / 200 / 300 / 500 ms.
Met Windows opstarten (Start with Windows) Writes an entry to HKCU\...\CurrentVersion\Run.
Diagnostisch logbestand (Diagnostic log file) Writes to %LOCALAPPDATA%\TouchCursorRestore\diagnostic.log. Off by default.
Logboek tonen (Show log) Live log window with a copy button.
Herstel nu (test) (Restore now) Performs one restore move immediately.
Status naar log (Dump status) Dumps counters: touch events, blocked moves, restores, saved position.

Blue icon = active, grey = disabled. The user interface is in Dutch.

Building it yourself

dotnet publish TouchCursorRestore/TouchCursorRestore.csproj -c Release -o publish --self-contained false

or run build-release.bat (stops a running instance and builds into publish/).

Project layout

TouchCursorRestore/
├── Core/
│   ├── InputHookManager.cs     WH_MOUSE_LL hook, touch/pen detection, self-injected filter
│   ├── RawInputTracker.cs      Raw Input on mouse devices, ignores absolute motion
│   └── TouchStateMachine.cs    State machine, cooldown, restore logic, counters
├── Native/
│   ├── Win32Api.cs             P/Invoke layer + RestoreCursorPosition with virtual-desktop normalisation
│   └── InputLayoutCheck.cs     Fails at startup if the INPUT layout drifts
├── Services/
│   ├── AppLog.cs               Hook-safe logger (queue + timer flush)
│   └── AutoStartManager.cs     Run key in the registry
├── UI/
│   └── TrayApplicationContext.cs  Tray menu, log window, session/display recovery
└── Program.cs

Plan.md holds the original design document (in Dutch) with the full validation plan.

Known limitations

  • In the default mode the cursor is briefly visible on the touchscreen during the touch itself; the restore follows shortly after. This disappears in block mode.
  • Input on the logon screen and the secure UAC desktop is out of reach for any user-mode application — that is a Windows design decision.
  • Applications that drive the cursor position themselves could in theory conflict with this logic; use the temporary disable for those.
  • Settings are not persisted between sessions yet; everything returns to defaults after a restart.
  • The executable is not code-signed. Heuristic antivirus scanners may flag a tray application with a global mouse hook as suspicious.

Licence

MIT — see LICENSE.


Nederlands

Het probleem

Windows vertaalt elke aanraking op een touchscreen naar een gesimuleerde muisbeweging en muisklik op die locatie ("touch-to-mouse promotion"). Dat mechanisme bestaat zodat programma's zonder native touch-ondersteuning toch op aanraking reageren — maar het neveneffect is dat de muiscursor van de monitor waarop je werkt naar het touchscreen verspringt. Op een werkplek waar continu tussen muis en touchscreen wordt gewisseld, kost dat elke keer opnieuw tijd om de cursor terug te vinden.

TouchCursorRestore is een systeemvak-applicatie (~160 KB, geen venster, geen installatie) die dat oplost zonder de touchbediening kapot te maken.

Uitgangspunten

  1. Werkt ongeacht of de applicatie native touch ondersteunt.
  2. Werkt in fullscreen én windowed, bij elk aantal schermen.
  3. Touchbediening blijft volledig intact — tikken, slepen, scrollen, dubbeltikken en press-and-hold (rechtsklik-emulatie) werken onveranderd.
  4. Onzichtbaar in dagelijks gebruik, op het systeemvak-pictogram na.
  5. Tijdelijk uitschakelbaar zonder de applicatie af te sluiten.

Punt 3 is de belangrijkste randvoorwaarde: hij bepaalt wanneer de cursor hersteld mag worden.

Hoe het werkt

Touch-signalen herkennen

De applicatie installeert een low-level muishook (SetWindowsHookEx met WH_MOUSE_LL). Windows voorziet muisgebeurtenissen die uit een aanraking of pen voortkomen van een herkenbare handtekening in dwExtraInfo. Die moet met een masker worden vergeleken, niet op exacte gelijkheid — de onderste byte bevat een cursor-identificatie en verschilt per gebeurtenis:

const uint MI_WP_SIGNATURE = 0xFF515700;
const uint SIGNATURE_MASK  = 0xFFFFFF00;

uint extra = (uint)hookStruct.dwExtraInfo;
bool isTouchOrPen = (extra & SIGNATURE_MASK) == MI_WP_SIGNATURE;

De gesimuleerde muisklik wordt bewust toegestaan, niet geblokkeerd. Blokkeren zou betekenen dat applicaties zonder native touch-ondersteuning niet meer op aanraking reageren.

Terugkoppellus vermijden

De herstelbeweging gebeurt met SendInput en een eigen, unieke waarde in dwExtraInfo (0x54435231, "TCR1"). De hook herkent die waarde en laat de gebeurtenis ongemoeid — anders zou de applicatie haar eigen referentiepunt overschrijven. SetCursorPos biedt die mogelijkheid niet en dient alleen als terugvaloptie wanneer SendInput faalt of naast het doel landt.

Het herstelmoment — toestandsmachine

Onmiddellijk herstellen bij het begin van een aanraking is technisch mogelijk, maar breekt de touchbediening: veel applicaties bepalen tijdens slepen en scrollen hun gedrag op basis van de actuele cursorpositie. Vandaar:

Toestand Gedrag
Muis actief Cursorpositie wordt continu bijgehouden. Geen ingrepen.
Aanraking bezig (touch-down tot touch-up) Alle gebeurtenissen ongemoeid doorgelaten. Cursor blijft op het touchscreen.
Nabewaking (na touch-up, standaard 200 ms) Volgt een nieuwe aanraking, dan terug naar de vorige toestand. Dubbeltik en samengestelde gebaren blijven intact.
Herstel Verstrijkt het venster zonder nieuwe aanraking, dan gaat de cursor terug naar de laatst bekende muispositie.

Beweegt de fysieke muis tijdens de nabewaking, dan wordt het geplande herstel afgebroken — de gebruiker heeft de cursor dan zelf al overgenomen.

Twee werkmodi

  • Herstellen na aanraking (standaard) — bovenstaande toestandsmachine.
  • Muis-beweging blokkeren — onderdrukt uitsluitend de door touch gegenereerde WM_MOUSEMOVE, terwijl druk- en losgebeurtenissen wél worden doorgelaten. De cursor verspringt dan helemaal niet. Het risico is dat Windows de klik deels op basis van de cursorpositie aan een venster toewijst, waardoor de aanraking op de verkeerde plek landt. Per applicatie uitproberen; omschakelen kan live via het systeemvakmenu.

Raw Input als tweede signaal

De applicatie registreert zich daarnaast voor Raw Input op muisapparaten (RIDEV_INPUTSINK). Touch- digitizers rapporteren doorgaans absolute muisbewegingen; die worden expliciet genegeerd zodat de opgeslagen muispositie niet met het aanraakpunt wordt overschreven. Alleen relatieve beweging telt als fysieke muis.

Technische aandachtspunten

Deze punten zijn bepalend voor de betrouwbaarheid en zitten vanaf het begin in de code:

  • Stabiliteit van de hook. De callback-delegate wordt in een veld vastgehouden; anders ruimt de garbage collector hem op en stopt de hook zonder melding.
  • Reactietijd van de hook. Windows verwijdert een low-level hook wanneer de callback te traag reageert (circa 300 ms). In de callback vindt daarom geen logging, schijftoegang of blokkerende bewerking plaats — AppLog zet berichten in een ConcurrentQueue en verwerkt ze op een timer-thread.
  • DPI-schaling. De applicatie is Per Monitor V2 DPI-aware, zodat coördinaten niet door Windows worden gevirtualiseerd en de cursor exact wordt teruggezet.
  • Negatieve coördinaten. Staat het touchscreen links van of boven de primaire monitor, dan zijn de coördinaten negatief. Alle berekeningen gaan uit van het virtuele bureaublad (SM_XVIRTUALSCREEN t/m SM_CYVIRTUALSCREEN), niet van de primaire monitor.
  • INPUT-structlayout. Op x64 zet LayoutKind.Sequential de vereiste 4-byte padding vóór de union (native INPUT is 40 bytes). Een FieldOffset(4)-variant legt mi over die padding heen en SendInput leest dan onzin-coördinaten. InputLayoutCheck verifieert dit bij het opstarten en laat het proces falen als de layout afwijkt.
  • Sessie- en beeldschermwijzigingen. De hook wordt opnieuw geïnstalleerd na sessiewissel en na wijziging van de beeldschermconfiguratie.
  • Rechtenniveau. Een hook op standaard integriteitsniveau ziet geen invoer die naar processen met beheerdersrechten gaat. Draaien die applicaties verhoogd, dan moet TouchCursorRestore dat eveneens doen (dan via de Taakplanner autostarten, niet via de Run-sleutel — anders krijg je een UAC-melding bij het aanmelden).

Installatie

  1. Download TouchCursorRestore-v2.0.0.zip bij de laatste release.
  2. Pak uit in een map naar keuze. Geen installatie, geen registerwijziging tot je autostart aanzet.
  3. Start TouchCursorRestore.exe. Het pictogram verschijnt in het systeemvak.

Vereist Windows 10/11 x64 en de .NET 10 Desktop Runtime (download).

Gebruik

Rechtsklik op het systeemvak-pictogram:

Menu-item Werking
Ingeschakeld Zet het herstellen tijdelijk uit zonder af te sluiten.
Modus Wisselt tussen "Herstellen na aanraking" en "Muis-beweging blokkeren".
Herstelvertraging Nabewakingsvenster: 100 / 200 / 300 / 500 ms.
Met Windows opstarten Zet een verwijzing in HKCU\...\CurrentVersion\Run.
Diagnostisch logbestand Schrijft naar %LOCALAPPDATA%\TouchCursorRestore\diagnostic.log. Standaard uit.
Logboek tonen Live logvenster met kopieerknop.
Herstel nu (test) Voert direct één herstelbeweging uit.
Status naar log Dumpt tellers: touch-events, geblokkeerde bewegingen, herstelacties, opgeslagen positie.

Blauw pictogram = actief, grijs = uitgeschakeld.

Zelf bouwen

dotnet publish TouchCursorRestore/TouchCursorRestore.csproj -c Release -o publish --self-contained false

of build-release.bat (stopt een draaiende instantie en bouwt naar publish/).

Projectstructuur

TouchCursorRestore/
├── Core/
│   ├── InputHookManager.cs     WH_MOUSE_LL hook, touch/pen-detectie, self-injected filter
│   ├── RawInputTracker.cs      Raw Input op muisapparaten, negeert absolute beweging
│   └── TouchStateMachine.cs    Toestandsmachine, nabewaking, herstellogica, tellers
├── Native/
│   ├── Win32Api.cs             P/Invoke-laag + RestoreCursorPosition met virtueel-bureaublad-normalisatie
│   └── InputLayoutCheck.cs     Faalt bij opstarten als de INPUT-layout afwijkt
├── Services/
│   ├── AppLog.cs               Hook-veilige logger (queue + timer-flush)
│   └── AutoStartManager.cs     Run-sleutel in het register
├── UI/
│   └── TrayApplicationContext.cs  Systeemvakmenu, logvenster, sessie-/beeldschermherstel
└── Program.cs

Plan.md bevat het oorspronkelijke ontwerpdocument met het volledige validatieplan.

Bekende beperkingen

  • In de standaardmodus staat de cursor tijdens de aanraking zelf zichtbaar op het touchscreen; het herstel volgt kort daarna. In de blokkeermodus vervalt dit.
  • Invoer op het aanmeldscherm en op het beveiligde bureaublad van UAC valt buiten bereik van elke gebruikersapplicatie — dat is een ontwerpkeuze van Windows.
  • Applicaties die de cursorpositie zelf actief sturen kunnen in theorie met deze logica conflicteren; gebruik dan tijdelijk uitschakelen.
  • Instellingen worden nog niet bewaard tussen sessies; na herstart staat alles weer op standaard.
  • Het uitvoerbare bestand is niet digitaal ondertekend. Heuristische virusscanners kunnen een systeemvak- applicatie met een globale muishook als verdacht aanmerken.

Licentie

MIT — zie LICENSE.

About

Windows tray app that keeps the mouse cursor in place when you touch a touchscreen, without breaking touch gestures.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages