{"id":51636984,"url":"https://github.com/qdot/mstaint","last_synced_at":"2026-07-13T15:04:20.420Z","repository":{"id":364858971,"uuid":"1268829740","full_name":"qdot/mstaint","owner":"qdot","description":"Tablet Pen pressure rerouting to Intiface Central","archived":false,"fork":false,"pushed_at":"2026-06-14T20:27:48.000Z","size":56,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T21:21:34.648Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qdot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T01:39:27.000Z","updated_at":"2026-06-14T20:27:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/qdot/mstaint","commit_stats":null,"previous_names":["qdot/mstaint"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/qdot/mstaint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qdot%2Fmstaint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qdot%2Fmstaint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qdot%2Fmstaint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qdot%2Fmstaint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qdot","download_url":"https://codeload.github.com/qdot/mstaint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qdot%2Fmstaint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35426086,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-13T15:04:19.656Z","updated_at":"2026-07-13T15:04:20.410Z","avatar_url":"https://github.com/qdot.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MSTaint\n\nMSTaint is a C#/.NET tray utility prototype for controlling Buttplug devices from pen tablet telemetry.\n\nThe current version targets Windows, connects to an external Intiface Central/Engine instance over WebSocket, and maps pen pressure to vibration intensity. The core mapping and safety logic is cross-platform and covered by tests; Win32 pen capture needs to be tested on Windows hardware.\n\n## Run On Windows\n\n1. Run the [installer, available in our releases section.](https://github.com/qdot/mstaint/releases/latest).\n2. Start Intiface Central or Intiface Engine with the WebSocket server listening on `ws://127.0.0.1:12345`.\n3. Run `MSTaint` from the Start Menu.\n4. Program opens as a tray app (currently with no icon, it'll just look like a window), there will be no App on the start bar. Open the settings window from the tray menu or by double-clicking the tray icon. The settings window and tray menu can both connect Intiface, scan for devices, arm/disarm output, open the pen test window, or trigger emergency stop.\n\n## Screenshots\n\n### Tray Menu\n\n![](docs/tray.png)\n\n### Settings Menu (May require resizing window to be larger)\n\n![](docs/settings.png)\n\n## Paint Program Setup\n\nSet paint programs to use Windows Ink, Windows 8+ Pointer Input, or the program's equivalent modern pointer input mode for tablet pressure. MSTaint's current capture model is designed around the paint program keeping its own Windows Ink stream while MSTaint listens passively to driver-exposed Raw Input HID or WinTab pressure samples.\n\nDo not configure paint programs to require an exclusive WinTab stream if pressure stops reaching MSTaint or the paint program while the canvas is focused.\n\n**HOW TO TELL IF YOU ARE ON THE WRONG API**: If your paint program seems to only have off/on versus varying pressure, this usually means it is set to WinTab instead of Windows Ink. If you are using a program that cannot set to Windows Ink, please leave an issue here and we'll consider rebuilding this as a mod instead of a tee-style watcher, which should work with all systems. However, that solution may be brittle, see `Possible WinTab Injection Path` below.\n\n### Specific Paint Program Instructions\n\n- MS Paint (v11, the newer one that comes with Win11): Uses Windows Ink by default, no reconfig needed\n- Paint.Net: Uses Windows Ink by default, no reconfig needed\n- IbisPaint: Uses Windows Ink by default, no reconfig needed\n- Krita: use the Windows Ink/Windows 8+ pointer tablet input mode (in Settings)\n- Clip Studio Paint: use Tablet PC/Windows Ink path when available (in Settings)\n- Fire Alpaca: Use \"Tablet PC\" in \"Stylus Pressure API\", even if on PC + Tablet\n- ArtRage: ArtRage v6 Tested, Vitae requires payment and I don't wanna. ArtRage v6 seems to use Windows Ink by default, so things should _just work_. If pressure routing does not seem to be working, make sure \"WinTab\" has an X in `Settings -\u003e Tablet Options`, not a checkmark.\n- MyPaint: Currently not usable.\n\n## LLM Usage\n\nSince this is made to be used with paint programs, development methods bear mentioning as many potential users may see this as a showstopper.\n\nWhile this project is derived from a project I originaly wrote and never released ('cause it never worked enough for me to want to deal with the support on it), this implementation uses GPT-5.5 w/ GLM-5.2 review to patch some of the issues I was having and handle a lot of the P/Invoke reworking and Win32 drudgery (I was writing win32 in the 90s and am officially too old for that shit).\n\nIf this implementation method bothers you, do not use this program. If you would like to reimplement it yourself, see the Injection Path section and create a project the good ol' open source way, by copying the BSD-Licensed GHR code I wrote by hand.\n\n## Capture Notes\n\nThe app starts a hidden capture window and enables passive capture paths:\n\n- Raw Input with a background digitizer HID sink for installed tablet drivers that route paint apps through driver APIs such as WinTab.\n- A minimal WinTab pressure context for driver stacks where Krita/other paint programs stop mirroring usable pressure through Windows pointer or HID reports.\n\nThe working split is not a single Windows Ink queue that MSTaint receives and replays. The focused drawing app keeps its own Windows Ink/pointer stream, while MSTaint listens to passive driver-exposed streams and maps those pressure samples to Intiface:\n\n```mermaid\nflowchart LR\n    Tablet[\"Pen tablet + installed driver\"] --\u003e Ink[\"Windows Ink / pointer stream\"]\n    Tablet --\u003e Raw[\"Raw Input HID reports\"]\n    Tablet --\u003e WinTab[\"WinTab packets\"]\n\n    Ink --\u003e Paint[\"Focused paint app\u003cbr/\u003eKrita / Clip Studio Paint\"]\n    Raw --\u003e WPC[\"MSTaint\u003cbr/\u003epassive capture\"]\n    WinTab --\u003e WPC\n\n    WPC --\u003e Mapping[\"Pressure mapping\"]\n    Mapping --\u003e Intiface[\"Intiface Central / Engine\"]\n    Intiface --\u003e Devices[\"Haptics devices\"]\n\n    Redirect[\"RegisterPointerInputTarget\u003cbr/\u003edisabled by default\"] -. \"would redirect Windows Ink to us\" .-\u003e WPC\n```\n\nThe Raw Input path reads HID `Tip Pressure` reports and normalizes each device's logical pressure range into the existing `0..1024` pressure model. The WinTab path opens the default system context and requests only `X`, `Y`, `BUTTONS`, and `NORMAL_PRESSURE`. The capture service also gates samples by active input source so a zero-pressure or pen-up packet from one source cannot immediately stop output driven by pressure packets from another source.\n\n`RegisterPointerInputTarget` is intentionally disabled by default because it redirects all pen pointer input to this app instead of teeing it; that breaks Windows Ink pressure in focused drawing apps such as Krita or Clip Studio Paint. For diagnostics only, set `WPC_ENABLE_POINTER_REDIRECT=1` before launching the app to enable Windows pointer redirection.\n\nKeep the drawing app on its Windows Ink pressure API, such as Windows Ink/Windows 8+ Pointer Input for Krita. MSTaint should receive pressure from the passive Raw HID or WinTab paths while the focused drawing app receives its own pressure stream.\n\nRaw Input registration should work without UIAccess. If passive global paths fail, use the pen test window to validate local `WM_POINTER*` pressure/tilt/button telemetry.\n\nThe manifest currently sets `uiAccess=\"true\"`, so release binaries must be signed and installed from a trusted location such as Program Files.\n\n## Possible WinTab Injection Path\n\nIf Windows Ink compatibility is not enough for a target workflow, a future WinTab-specific tee could use the same general model as `intiface-game-haptics-router`: inject a small payload into the focused paint process with EasyHook, hook only the WinTab functions that expose context and queue reads, and report copied pressure samples back to MSTaint over IPC.\n\nThis avoids shipping a proxy `wintab32.dll` and avoids recreating the full WinTab export surface. The paint program would still load the real tablet driver `wintab32.dll`; the injected payload would detour selected functions in-process:\n\n- `WTOpenA/W` to record each `HCTX` and its requested `LOGCONTEXT`.\n- `WTClose` to discard context state.\n- `WTPacket`, `WTPacketsGet`, `WTDataGet`, and `WTDataPeek` to copy packets after the real WinTab call returns.\n- `WTInfoA/W` to query pressure axis metadata and logical ranges when needed.\n\nThe hook must not independently read from the WinTab queue. It should forward to the real function first, let the paint app receive its packets unchanged, then copy the returned packet data into a local queue. A payload loop can batch those samples back to MSTaint, keeping IPC work out of the paint app's input thread.\n\nThe hard part is still generic packet decoding. WinTab packet layout depends on the `LOGCONTEXT.lcPktData` requested by each app, so the payload needs per-context decoding logic rather than assuming a fixed packet structure. Supporting both 32-bit and 64-bit paint apps would also require matching hook payload builds.\n\n## Build\n\n```powershell\ndotnet build MSTaint.slnx\ndotnet test tests/MSTaint.Core.Tests/MSTaint.Core.Tests.csproj\n```\n\n## Installer\n\nThe Inno Setup installer packages the Release publish output from `artifacts/publish/MSTaint` and installs to `C:\\Program Files\\MSTaint`.\n\n```powershell\ndotnet publish src/MSTaint/MSTaint.csproj -c Release -f net10.0-windows -r win-x64 --self-contained false -o artifacts/publish/MSTaint\n\u0026 \"$env:LOCALAPPDATA\\Programs\\Inno Setup 6\\ISCC.exe\" installer/MSTaint.iss\n```\n\nThe installer output is written to `artifacts/installer/MSTaintSetup.exe`.\n\n## Distribution And Signing\n\n_AKA Why you probably shouldn't try to fork and build this yourself unless you want to deal with a ton of bullshit._\n\nDistribution builds require code signing. The app manifest currently sets `uiAccess=\"true\"`, and Windows only grants UIAccess to signed binaries installed from a trusted location such as Program Files. For release builds, sign the published app binaries before compiling the installer, then sign the installer itself.\n\nUnsigned local builds are still useful for development and diagnostics, but they are not suitable for packaged distribution and should not be expected to behave the same as the signed Program Files install.\n\nThe Forgejo Windows workflow expects these signing secrets, matching the token-backed SignTool setup used by other Intiface Windows builds:\n\n- `WINDOWS_CODESIGN_CERT_BASE64`\n- `WINDOWS_CODESIGN_KEY_CONTAINER`\n- `WINDOWS_CODESIGN_TOKEN_PASSWORD`\n- `WINDOWS_CODESIGN_CSP` (optional, defaults to `eToken Base Cryptographic Provider`)\n- `WINDOWS_CODESIGN_TIMESTAMP_URL` (optional, defaults to `http://timestamp.digicert.com`)\n\n## License\n\nMSTaint is BSD 3-Clause licensed.\n\nCopyright (c) 2026, Nonpolynomial, LLC\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of buttplug nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqdot%2Fmstaint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqdot%2Fmstaint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqdot%2Fmstaint/lists"}