Mouse control via head tracking, as a cross platform desktop app and JS library. eViacam alternative.
90
stars
1,382
commits
JavaScript
primary language
Aug 31, 2026
updated
Tracky MouseControl your computer by moving your head.
Tracky Mouse is a desktop application providing hands-free universal computer access.
It's also embeddable in web applications as a JavaScript library. See the API docs.
Features include:
By building it as a desktop app and an embeddable web UI, users can try it out right away in their browser, and then install the desktop app for full computer control.
.msix file, right click on it, select Properties, go to the Digital Signatures tab, select the embedded signature from the list, click Details, click View Certificate, and install the certificate. Then double click the .msix file. Then click "Restart as administrator" to restart the installer (you do not need to restart your computer, don't worry). Say Yes to allow App Installer to make changes to the device. Then click Install.Pre-built binaries are not yet available for macOS, due to a couple issues: camera permissions, and the more powerful clicking modes not clicking properly. You can still run the app on macOS, if you follow the Development Setup instructions.
These instructions apply to using the desktop app or the web UI.
guvcview can magically fix a webcam not showing up. (source)Tracky Mouse comes with a command-line interface (CLI) which can be used to control the desktop app with a voice command system or other external programs. See CLI documentation for usage.
Tracky Mouse is available on npm:
npm install tracky-mouse
Read the API documentation for more information.
MIT-licensed, see LICENSE.txt
See CHANGELOG.md for project history and API changes.
Someone emailed me asking about how they might adjust the UI of JS Paint to work with eye tracking (enlarging the color palette, hiding other UI elements, etc.) and I decided to do them one better and build it as an official feature, with dwell clicking and everything.
To test these accessibility features properly, I needed a facial mouse, but eye trackers are expensive, so I tried looking for head tracking software, and found eViacam, but... either it didn't work, or at some point it stopped working on my computer.
This is a monorepo containing packages for the library (core), the desktop app (desktop-app), and the website (website).
I tried npm workspaces, but it doesn't work with Electron Forge packaging. See electron/forge#2306.
The core library uses the following third-party libraries:
Some dependencies are versioned with npm and copied into core/lib/ with npm run in-core -- npm run copy-deps
Others are just stored in core/lib/ without npm versioning.
To avoid the need for unsafe-eval in the Content Security Policy, I had to eliminate the use of eval (and Function construction) in clmtrackr.js.
The file no-eval.js overrides eval with a function that handles the specific cases of eval usage in clmtrackr.js.
I made a tool to generate this file by running clmtrackr.js while instrumenting eval to collect the code it tries to evaluate.
This tool is located in eval-is-evil.html.
Unfortunately, when upgrading the facemesh library, I had to add back the unsafe-eval requirement, as it uses WebAssembly.
WebAssembly is not the same as eval, but browsers grouped them together under the same CSP directive.
Even if browsers widely support the more fine-grained wasm-unsafe-eval, old browsers would still be blocked from using the library if unsafe-eval is not included in the CSP.
The website uses symlinks to reference the library (core) and shared resources (images) during development.
When deploying with npm run in-website -- npm run deploy, it will prompt when there are any new files not that are not defined as included or excluded in website/globs-for-deploy.js.
It will then be deployed to GitHub Pages using the gh-pages npm package.
Deploys can be rolled back by force-pushing to the gh-pages branch. The command npm run in-website -- npm run deploy:rollback rolls back the last deploy.
The desktop application's architecture is kind of amusing...
I will explain. First, some groundwork. Electron apps are multi-process programs. They have a main process, which creates browser windows, and renderer processes, which render the content of the browser windows.
In this app, there are two renderer processes, one for the main application window, and one for a screen overlay window.
The overlay window is transparent, always-on-top, and intangible. It's used to preview dwell clicks with a shrinking circle.
Now we get to the good stuff...
In a "sane" architecture, the overlay window, which can't receive any input directly, would be purely a visual output. The state would be kept in either the main process or the main renderer process, and it would only send messages to the overlay to draw the circle.
But I already had code for the dwell clicker, you see. I want it to behave similarly between the library and the desktop app, so I want the same timing logic and circle drawing to work in both.
Keeping the state in a separate process from where the circle is rendered would mean tearing apart and rewriting my code for the dwell clicker.
So instead I simply embed the dwell clicker into the screen overlay window, business logic and all. It was already going to be an entire webpage just to render the circle, since this is Electron. It was never going to be efficient.
So I ended up with an architecture where the application window controls mouse movement, and the screen overlay window controls mouse clicking, which I think is pretty epic. 😎
It genuinely was a good way to reuse the code for the dwell clicker.
Oh also I made a big, screen-sized, invisible button, so that the dwell clicker thinks there's something to click on. Pretty silly, but also pretty simple. 🆒
Not pictured: the renderer processes each have preload scripts which are more privileged code than the rest of the renderer's code. Access to system functionality passes through the preload scripts.
The architecture for normal usage of the library is much simpler.
Ooh, but the diagram for the desktop app interacting with web pages (including pages using the library) through the browser extension would be interesting. That's all theoretical for now though.
P.S. There is a script to list IPC events: node scripts/list-ipc-events.js
Also, I do plan to reign in this madness, see issue #72
git config --global core.symlinks true set, or you may have issues with symbolic links..nvmrcnpm install to install project-wide dependencies.[!NOTE] There's also
npm run install-allas a shortcut to install dependencies for all packages.
For the website:
npm run in-website -- npm install to install the website's dependencies. (-- allows passing arguments to the script, which is just a simple wrapper to run a command within the directory of the package.)npm run website to start a web server that will automatically reload when files change.For the desktop app:
sudo apt-get install libxtst-devsudo yum install libXtst-develsudo yum install libXi-develnpm run in-desktop-app -- npm install to install dependencies.npm run desktop-app to start the app.npx tracky-mouse --help.
npm link to make tracky-mouse available globally, but note that it may conflict with the installed app.npm run desktop-app -- -- -- --help (Yes it's a lot of dashes. It's going through npm, then npm within a subfolder, and then Electron Forge. Each tool has its own --help flag, but supports -- to pass on any following arguments as-is.)npm run in-desktop-app -- npm run make to build the app for distribution. Look in the desktop-app/out/ directory for build artifacts.For the core library:
core/lib/; you don't need to run npm run in-core -- npm install unless you plan to modify the library's dependencies and run npm run in-core -- npm run copy-deps to copy them into core/lib/.VS Code launch configurations are provided to debug the web version in Chrome, and to debug the Electron main process.
For the screen overlay window, you can use View > Toggle Developer Tools (Screen Overlay) from the main window's menu bar.
npm run lint to check for spelling, code, and localization issues.(()=>{ const backup = localStorage["tracky-mouse-settings"]; delete localStorage["tracky-mouse-settings"]; localStorage["tracky-mouse-settings-backup"] = backup; })()
(()=>{ const toRestore = localStorage["tracky-mouse-settings-backup"]; delete localStorage["tracky-mouse-settings-backup"]; localStorage["tracky-mouse-settings"] = toRestore; })()move %AppData%\"Tracky Mouse"\tracky-mouse-settings.json %AppData%\"Tracky Mouse"\tracky-mouse-settings.json.bak
move %AppData%\"Tracky Mouse"\tracky-mouse-settings.json.bak %AppData%\"Tracky Mouse"\tracky-mouse-settings.jsonThis section outlines the steps for releasing a new version of Tracky Mouse.
In CHANGELOG.md, first make sure all important changes are noted in the Unreleased section, by looking over the commit history.
The following command takes care of linting, and bumping version numbers for each package and in the changelog. It also creates a git commit and tag for the new version, and pushes the tag to GitHub, triggering the GitHub Actions workflow to create a release draft.
npm run release -- $VERSION
Download and install from the GitHub release draft, and test the installed desktop app.
[!WARNING] "Point of no return" (spooky)
Final steps:
# Push to main
git push
# Deploy the website
# (this may be done at any time, but it's important to update the homepage download link)
# (technically this should be the last step since the new download link will only work once the release draft is published...)
npm run in-website -- npm run deploy
Publish the GitHub release. This should trigger a GitHub Actions workflow which publishes the core package to npm.
Sound effects:
1,382 commits
JavaScript
97.0%
HTML
2.0%
Mouse control via head tracking, as a cross platform desktop app and JS library. eViacam alternative.
90
stars
1,382
commits
JavaScript
primary language
Aug 31, 2026
updated
Tracky MouseControl your computer by moving your head.
Tracky Mouse is a desktop application providing hands-free universal computer access.
It's also embeddable in web applications as a JavaScript library. See the API docs.
Features include:
By building it as a desktop app and an embeddable web UI, users can try it out right away in their browser, and then install the desktop app for full computer control.
.msix file, right click on it, select Properties, go to the Digital Signatures tab, select the embedded signature from the list, click Details, click View Certificate, and install the certificate. Then double click the .msix file. Then click "Restart as administrator" to restart the installer (you do not need to restart your computer, don't worry). Say Yes to allow App Installer to make changes to the device. Then click Install.Pre-built binaries are not yet available for macOS, due to a couple issues: camera permissions, and the more powerful clicking modes not clicking properly. You can still run the app on macOS, if you follow the Development Setup instructions.
These instructions apply to using the desktop app or the web UI.
guvcview can magically fix a webcam not showing up. (source)Tracky Mouse comes with a command-line interface (CLI) which can be used to control the desktop app with a voice command system or other external programs. See CLI documentation for usage.
Tracky Mouse is available on npm:
npm install tracky-mouse
Read the API documentation for more information.
MIT-licensed, see LICENSE.txt
See CHANGELOG.md for project history and API changes.
Someone emailed me asking about how they might adjust the UI of JS Paint to work with eye tracking (enlarging the color palette, hiding other UI elements, etc.) and I decided to do them one better and build it as an official feature, with dwell clicking and everything.
To test these accessibility features properly, I needed a facial mouse, but eye trackers are expensive, so I tried looking for head tracking software, and found eViacam, but... either it didn't work, or at some point it stopped working on my computer.
This is a monorepo containing packages for the library (core), the desktop app (desktop-app), and the website (website).
I tried npm workspaces, but it doesn't work with Electron Forge packaging. See electron/forge#2306.
The core library uses the following third-party libraries:
Some dependencies are versioned with npm and copied into core/lib/ with npm run in-core -- npm run copy-deps
Others are just stored in core/lib/ without npm versioning.
To avoid the need for unsafe-eval in the Content Security Policy, I had to eliminate the use of eval (and Function construction) in clmtrackr.js.
The file no-eval.js overrides eval with a function that handles the specific cases of eval usage in clmtrackr.js.
I made a tool to generate this file by running clmtrackr.js while instrumenting eval to collect the code it tries to evaluate.
This tool is located in eval-is-evil.html.
Unfortunately, when upgrading the facemesh library, I had to add back the unsafe-eval requirement, as it uses WebAssembly.
WebAssembly is not the same as eval, but browsers grouped them together under the same CSP directive.
Even if browsers widely support the more fine-grained wasm-unsafe-eval, old browsers would still be blocked from using the library if unsafe-eval is not included in the CSP.
The website uses symlinks to reference the library (core) and shared resources (images) during development.
When deploying with npm run in-website -- npm run deploy, it will prompt when there are any new files not that are not defined as included or excluded in website/globs-for-deploy.js.
It will then be deployed to GitHub Pages using the gh-pages npm package.
Deploys can be rolled back by force-pushing to the gh-pages branch. The command npm run in-website -- npm run deploy:rollback rolls back the last deploy.
The desktop application's architecture is kind of amusing...
I will explain. First, some groundwork. Electron apps are multi-process programs. They have a main process, which creates browser windows, and renderer processes, which render the content of the browser windows.
In this app, there are two renderer processes, one for the main application window, and one for a screen overlay window.
The overlay window is transparent, always-on-top, and intangible. It's used to preview dwell clicks with a shrinking circle.
Now we get to the good stuff...
In a "sane" architecture, the overlay window, which can't receive any input directly, would be purely a visual output. The state would be kept in either the main process or the main renderer process, and it would only send messages to the overlay to draw the circle.
But I already had code for the dwell clicker, you see. I want it to behave similarly between the library and the desktop app, so I want the same timing logic and circle drawing to work in both.
Keeping the state in a separate process from where the circle is rendered would mean tearing apart and rewriting my code for the dwell clicker.
So instead I simply embed the dwell clicker into the screen overlay window, business logic and all. It was already going to be an entire webpage just to render the circle, since this is Electron. It was never going to be efficient.
So I ended up with an architecture where the application window controls mouse movement, and the screen overlay window controls mouse clicking, which I think is pretty epic. 😎
It genuinely was a good way to reuse the code for the dwell clicker.
Oh also I made a big, screen-sized, invisible button, so that the dwell clicker thinks there's something to click on. Pretty silly, but also pretty simple. 🆒
Not pictured: the renderer processes each have preload scripts which are more privileged code than the rest of the renderer's code. Access to system functionality passes through the preload scripts.
The architecture for normal usage of the library is much simpler.
Ooh, but the diagram for the desktop app interacting with web pages (including pages using the library) through the browser extension would be interesting. That's all theoretical for now though.
P.S. There is a script to list IPC events: node scripts/list-ipc-events.js
Also, I do plan to reign in this madness, see issue #72
git config --global core.symlinks true set, or you may have issues with symbolic links..nvmrcnpm install to install project-wide dependencies.[!NOTE] There's also
npm run install-allas a shortcut to install dependencies for all packages.
For the website:
npm run in-website -- npm install to install the website's dependencies. (-- allows passing arguments to the script, which is just a simple wrapper to run a command within the directory of the package.)npm run website to start a web server that will automatically reload when files change.For the desktop app:
sudo apt-get install libxtst-devsudo yum install libXtst-develsudo yum install libXi-develnpm run in-desktop-app -- npm install to install dependencies.npm run desktop-app to start the app.npx tracky-mouse --help.
npm link to make tracky-mouse available globally, but note that it may conflict with the installed app.npm run desktop-app -- -- -- --help (Yes it's a lot of dashes. It's going through npm, then npm within a subfolder, and then Electron Forge. Each tool has its own --help flag, but supports -- to pass on any following arguments as-is.)npm run in-desktop-app -- npm run make to build the app for distribution. Look in the desktop-app/out/ directory for build artifacts.For the core library:
core/lib/; you don't need to run npm run in-core -- npm install unless you plan to modify the library's dependencies and run npm run in-core -- npm run copy-deps to copy them into core/lib/.VS Code launch configurations are provided to debug the web version in Chrome, and to debug the Electron main process.
For the screen overlay window, you can use View > Toggle Developer Tools (Screen Overlay) from the main window's menu bar.
npm run lint to check for spelling, code, and localization issues.(()=>{ const backup = localStorage["tracky-mouse-settings"]; delete localStorage["tracky-mouse-settings"]; localStorage["tracky-mouse-settings-backup"] = backup; })()
(()=>{ const toRestore = localStorage["tracky-mouse-settings-backup"]; delete localStorage["tracky-mouse-settings-backup"]; localStorage["tracky-mouse-settings"] = toRestore; })()move %AppData%\"Tracky Mouse"\tracky-mouse-settings.json %AppData%\"Tracky Mouse"\tracky-mouse-settings.json.bak
move %AppData%\"Tracky Mouse"\tracky-mouse-settings.json.bak %AppData%\"Tracky Mouse"\tracky-mouse-settings.jsonThis section outlines the steps for releasing a new version of Tracky Mouse.
In CHANGELOG.md, first make sure all important changes are noted in the Unreleased section, by looking over the commit history.
The following command takes care of linting, and bumping version numbers for each package and in the changelog. It also creates a git commit and tag for the new version, and pushes the tag to GitHub, triggering the GitHub Actions workflow to create a release draft.
npm run release -- $VERSION
Download and install from the GitHub release draft, and test the installed desktop app.
[!WARNING] "Point of no return" (spooky)
Final steps:
# Push to main
git push
# Deploy the website
# (this may be done at any time, but it's important to update the homepage download link)
# (technically this should be the last step since the new download link will only work once the release draft is published...)
npm run in-website -- npm run deploy
Publish the GitHub release. This should trigger a GitHub Actions workflow which publishes the core package to npm.
Sound effects:
1,382 commits
JavaScript
97.0%
HTML
2.0%