Keeps the mouse cursor where it is when you touch a touchscreen.
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.
- Works regardless of whether the application supports touch natively.
- Works in fullscreen and windowed mode, with any number of displays.
- Touch input stays fully intact — tap, drag, scroll, double-tap and press-and-hold (right-click emulation) all keep working unchanged.
- Invisible in daily use, apart from the tray icon.
- 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.
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.
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.
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.
- Restore after touch (default) — the state machine above.
- Block mouse movement — suppresses only the touch-generated
WM_MOUSEMOVEwhile 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.
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.
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 —
AppLogpushes messages onto aConcurrentQueueand 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_XVIRTUALSCREENthroughSM_CYVIRTUALSCREEN), not the primary monitor. - INPUT struct layout. On x64,
LayoutKind.Sequentialinserts the required 4-byte padding before the union (nativeINPUTis 40 bytes). AFieldOffset(4)variant overlaysmionto that padding andSendInputthen reads garbage coordinates.InputLayoutCheckverifies 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).
- Download
TouchCursorRestore-v2.0.0.zipfrom the latest release. - Extract anywhere. No installer, no registry changes until you enable autostart.
- Run
TouchCursorRestore.exe. The icon appears in the system tray.
Requires Windows 10/11 x64 and the .NET 10 Desktop Runtime (download).
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.
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/).
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.
- 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.
MIT — see LICENSE.
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.
- Werkt ongeacht of de applicatie native touch ondersteunt.
- Werkt in fullscreen én windowed, bij elk aantal schermen.
- Touchbediening blijft volledig intact — tikken, slepen, scrollen, dubbeltikken en press-and-hold (rechtsklik-emulatie) werken onveranderd.
- Onzichtbaar in dagelijks gebruik, op het systeemvak-pictogram na.
- Tijdelijk uitschakelbaar zonder de applicatie af te sluiten.
Punt 3 is de belangrijkste randvoorwaarde: hij bepaalt wanneer de cursor hersteld mag worden.
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.
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.
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.
- 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.
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.
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 —
AppLogzet berichten in eenConcurrentQueueen 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_XVIRTUALSCREENt/mSM_CYVIRTUALSCREEN), niet van de primaire monitor. - INPUT-structlayout. Op x64 zet
LayoutKind.Sequentialde vereiste 4-byte padding vóór de union (nativeINPUTis 40 bytes). EenFieldOffset(4)-variant legtmiover die padding heen enSendInputleest dan onzin-coördinaten.InputLayoutCheckverifieert 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).
- Download
TouchCursorRestore-v2.0.0.zipbij de laatste release. - Pak uit in een map naar keuze. Geen installatie, geen registerwijziging tot je autostart aanzet.
- Start
TouchCursorRestore.exe. Het pictogram verschijnt in het systeemvak.
Vereist Windows 10/11 x64 en de .NET 10 Desktop Runtime (download).
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.
dotnet publish TouchCursorRestore/TouchCursorRestore.csproj -c Release -o publish --self-contained false
of build-release.bat (stopt een draaiende instantie en bouwt naar publish/).
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.
- 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.
MIT — zie LICENSE.