Three Ways to Give a Web App Real Keyboard Shortcuts on Windows

Three Ways to Give a Web App Real Keyboard Shortcuts on Windows


I’ve been moving a bunch of my little Windows tools into the browser lately. Progressive Web Apps, WebAssembly, the whole deal. No installer, works on my phone, updates itself. Pretty great.

Then I hit a wall.

I built a Pomodoro timer as a web app and wanted to hit a key to pause it while I was buried in Excel. Nope. The browser only hears the keyboard when the browser is the window in front. Switch to Excel and your web app is deaf.

That’s the sandbox doing its job. Web pages are NOT supposed to spy on your keyboard from the background. Good policy. Annoying for me.

Have you ever built something 95% of the way and then found out the last 5% was the part you actually cared about? Yeah, that.

So I spent a morning on it, asked a handful of AI models to poke holes in my plan, and ended up with three working approaches. Each one trades convenience for power. Now on with the show…

First, the plain-English picture

Think of your web app as someone in a soundproof room. They can hear you fine when you’re in the room with them (the browser is in front). Step outside and they hear nothing.

Unlock Global Hotkeys for Progressive Web Apps 🚀


Unlock Global Hotkeys for Progressive Web Apps 🚀


The three approaches are three different ways of getting a message into that room from the hallway:

  • Approach 1: Only talk when you’re already in the room. (Shortcuts that work while the browser is active.)
  • Approach 2: Have the building manager relay a message. (A Chrome extension registers the shortcut.)
  • Approach 3: Install a doorbell. (A tiny AutoHotkey helper on the PC listens and rings the page.)

Approach 1: Shortcuts while the browser is in front

This is the easy one and it’s free. A few lines of JavaScript listen for keys and call your app’s code. Ctrl+P, Alt+S, F2, whatever you like. If your app is WebAssembly, the JavaScript just passes the event along.

// Works only while the page is the active window
document.addEventListener(“keydown”, e => {
if (e.ctrlKey && e.key === “p”) {
e.preventDefault();
Timer.Toggle();
}
});

Good: nothing to install, any key combo, works in every browser on every device.

Bad: useless the moment you click into another program. For a timer you want to control from anywhere, that’s a deal breaker.

Approach 2: A Chrome extension with global shortcuts

This one surprised me. Chrome extensions get one privilege that web pages never get: they can ask Chrome to register a shortcut with Windows that fires even when Chrome is in the background. Chrome does the registering. No exe, nothing to download outside the browser.

You declare the shortcuts in the extension’s manifest and flag them as global:

// manifest.json (the part that matters)
“commands”: {
“toggle”: {
“suggested_key”: { “default”: “Ctrl+Shift+1” },
“description”: “Start or pause the timer”,
“global”: true
}
}

When the key fires, the extension’s background script sends a one-word message (“toggle”) to your app’s tab. If no tab is open, it opens one. Your app just listens for messages from that extension and runs the matching function.

Here’s the catch, and it’s a big one. Chrome only allows global shortcuts on Ctrl+Shift plus a digit, 0 through 9. Not Ctrl+P. Not Ctrl+Alt+anything. Ctrl+Shift+1, Ctrl+Shift+2, and so on. That’s a hard rule in Chrome. Ten shortcuts, take it or leave it.

A few more limits:

  • Only four suggested shortcuts per extension. Users can add more by hand in Chrome’s shortcut settings.
  • Chrome (or Edge) has to be running. Close the browser and the shortcuts die with it.
  • Firefox has the extension API but not the global part. Safari doesn’t play at all.
  • It’s a doorbell only. The extension can’t tell your app which program you were in, can’t paste text into Excel for you, can’t touch the clipboard.

Good: one-click install from the Chrome Web Store, no scary “Windows protected your PC” screen, updates automatically, one extension can serve all of your web apps.

Bad: Ctrl+Shift+digit or nothing.

Putting an extension in the Chrome Web Store costs a one-time $5 developer fee and a review that takes a few days. For testing you can load it unpacked on your own machine in about a minute.

Approach 3: A tiny AutoHotkey helper (the doorbell)

This is the one I actually shipped first, because global hotkeys are what AutoHotkey was born to do.

The idea: a very small program sits in the Windows tray. Its ONLY job is to listen for hotkeys and forward a one-word command to the web app over a local connection on the PC itself. The helper knows nothing about timers or tasks. It’s a wire. The web app owns all the logic.

The heart of it is embarrassingly short:

; Register a global hotkey and push a command to the web page
Hotkey(“^+p”, (*) => SendToPage(“toggle”))SendToPage(Cmd) {
; Snapshot the window the user was in, so the page can act on it later
Hwnd := WinGetID(“A”)
Json := ‘{“cmd”:”‘ Cmd ‘”,”hwnd”:’ Hwnd ‘}’
PushToBrowser(Json) ; local connection, 127.0.0.1 only
}

The real script is longer (pairing, a settings cache, a tray menu), but that’s the whole concept.

Some things I learned building it:

  • No AutoHotkey install required. The helper compiles to a single 1.3 MB exe with the engine baked in. Users double-click it once.
  • Pairing is one click. The web app opens a special link (like a mailto: link, but for the helper). Windows launches the helper, Chrome asks “Open HotkeyBridge?” once, and you’re connected. No typing codes.
  • One helper serves every app. My timer, my recorder, whatever comes next. Each site pairs separately and gets its own hotkeys.
  • The page picks the keys. There’s a “Change” button in the app’s settings. Press the new combo and the helper re-registers it live. Even the Win key works, which browsers can’t normally see.
  • The helper remembers. Hotkeys stay registered between sessions. If a key fires and the app isn’t open, the helper opens it for you.
  • Bonus power. Because it’s AutoHotkey, the helper can tell the page which window you were in. The page can later say “paste this into that window.” A pure web app can never do that.

Good: any key combo at all, works with the browser closed, can act on other programs, and it’s tiny.

Bad: it’s an exe. Unsigned exes get the SmartScreen warning on first run, and it’s Windows only.

Watch out for hotkey collisions. My first default was Ctrl+Alt+P, which I already use in another AutoHotkey script. Whoever registers a key first wins in Windows, so the web app silently lost. I switched the default to Ctrl+Shift+P and added a check that reports “that key is already taken” instead of failing quietly.

Side by side

Browser in frontChrome extensionAutoHotkey helper
Works from other programsNoYes, while Chrome is runningYes, always
Key combos allowedAnythingCtrl+Shift+0 to 9 onlyAnything, including Win key
User has to installNothingAn extension (one click)A small exe (one download)
BrowsersAllChrome, EdgeAll (Windows only)
Can act on other windowsNoNoYes
Best forEveryone, as a baselineCasual users, a few actionsPower users, Windows automation

What I’d actually do

All three. They stack.

Approach 1 costs nothing, so every app gets it. Approach 2 is the easy on-ramp for people who just want to pause a timer without hunting for the window. Approach 3 is for the folks (like me) who want Win+F2 to do something useful in three different apps and don’t mind a tray icon.

The cool thing is the app itself doesn’t care which path the key came from. All three end up calling the same function. The plumbing changes, the app doesn’t.

One thing I’ve learned from this: the “browser can’t do that” wall is usually more like a fence. A tiny bit of native code on the right side of the fence, and suddenly a web app can do most of what a desktop app can. And you keep all the good parts of the web app: no installer for the main thing, phone support, instant updates.

How about you? Are you building things in the browser and hitting walls like this? Hit reply and tell me which wall. There’s a decent chance a small AutoHotkey script is the ladder. 😊

P.S. The helper approach is where I see the real opportunity. Keep the app in the browser, keep the native piece tiny, and let AutoHotkey do what it’s best at. If you want to learn AutoHotkey well enough to build these bridges yourself, our courses at the-Automator.com are the fastest way I know.

My ⛓️🪚Rocks❗ News:2026-09-10

AutoHotkey Newsletter

Howdy %Name%,

Last week I noticed I had a branch down in front of my shed. 

Yesterday I towed it over near the burn pile and got out one of my favorite (albeit super dangerous) automation tools:  my Chainsaw❗ ⛓️💥

Chainsaw aftermath

In a matter of a few minutes I had the limb cut up into dozens of pieces to easily fit in my burn barrel.

It got me thinking about how long something like that would have taken me if I didn’t have the chainsaw.  

Just like how long much of what I do on a daily basis would take me if I hadn’t automated them!

How about you❓    What tool have you created that saves you so much time that you would cry if you could no longer use it

Now on with the show…

Learn how to Master VSCode Make the switch to v2 Intro to AutoHotkey Intermediate AutoHotkey Intermediate ObjectsGUIs are Easy w/AutoHotkey Access all our Courses; Quit when you want

selfimprovement


Are you 🔥Burning through your AI Tokens
A lot of us these days are feeling like drunken sailors, getting Astra, Fable 5, etc. to fulfill our wildest scripting desires.

I’m constantly watching my “gas gauge” go down as Claude burns through my tokens faster than Jeff Spicoli would a bag of weed.

If you’re experiencing token anxiety you should check out this video

Cut Context Costs 99% Using Local AI Memory 💰
Cut Context Costs 99% Using Local AI Memory 💰


Dude, Where’s my car❓

Are you like everyone else and at times struggle to remember where you parked❓

Google Maps has a built-in way to save your parking space but it is way too complicated to do when you arrive at your destination and want to get moving.

So I asked Claude to build a simple PWA leveraging my phone and Google Maps.

Now I can basically hit a button to mark my position and share it with others.  You can get a quick look at PinMySpot in this video.

AI Built Location Saving PWA on Phone | Hero Extract
AI Built Location Saving PWA on Phone | Hero Extract


Clean up Noisy Audio Files

Do you have noisy audio files that need to be cleaned up❓ 

I was using ElevenLabs but their pricing annoyed me so I switched to a model at Replicate

Check out how amazing our noise removal tool cleaned up my audio in this video.

Want the free route instead❓  Here’s how we removed audio hum with FFMPEG.

AI Cleans Noisy Audio Instantly With Replicate 🔊 | Hero Extract
AI Cleans Noisy Audio Instantly With Replicate 🔊 | Hero Extract


Improving People’s Lives: Story #113

During a client call our client mentioned he had a $20 a month subscription to Chat GPT because he liked to create images.

I told him I could create a tool that would still let him use the Chat GPT models (even the new 2.5 model) and other image models (~52 right now) in OpenRouter.

If you want to wire OpenRouter into your own scripts, grab our AHK OpenRouter class.

I then had Claude build a simple JavaScript tool which takes an OpenRouter token and creates pictures.  

The great thing about this approach is you only pay when you use it

No monthly subscription needed❗

You can grab it here for a 1-time fee of $5.99


📚 What we’re reading


🤖 AI News that you Need to Know

10 NEW Github Repos Every Claude User Must Use

10 NEW Github Repos Every Claude User Must Use


⚡️Productivity tips:

Remove all distractions and use a Pomodoro timer to focus and crank out things faster than you can possibly imagine!


🤣 A spot of Humor:  Ricky is quite hilarious❗

The REAL Reason Behind ‘Acts of God’ | Out Of England

The REAL Reason Behind 'Acts of God' | Out Of England


What we 🅰utomated this week with AutoHotkey #147

This week was a pile of small tools that all solve annoying little problems.

I built a WebAssembly file renamer that runs right in the browser, played with AnyDoc for turning files like PDFs, Excel, and MS Word into Markdown (with OCR fallback when the text isn’t there), and set up a local concept search over my Markdown files.  While we’re on Markdown, we also built a Markdown previewer in 5 minutes.

I also got Claude relaying between my phone and PC over Tailscale using my own PWA and the Claude CLI.

In this week’s video I walk you through all of them, plus updates to Text Under Mouse and the AHK taskbar, and I let Fable run in the background chewing on tasks while I talk.

What we Automated this week #147
What we Automated this week #147


Clients say…

Thanks for all your help Joe. Folks are already excited about getting these updated. — Ron S.


AHK Hero 🦸

Consider joining the 🦸Hero club! Members receive 25% off courses, consultations, tutoring, done for you project work, etc.    Currently we have over 607 hours of recordings teaching AutoHotkey that is exclusive to Hero members

During this Friday’s call we’re planning to discuss:

  • Help Hero Members with their Questions & Code
  • AHK Tools
    • OpenRouter Provisioning API
    • Updated TextUnderMouse 
    • Borrowing Firefox Cookies
    • Dropbox Sync (enable/disable)
    • Removing Mark of Internet flag
    • AutoHotkey.dll
    • WatchFolders example 

They also have access to exclusive & pre-released content and 2 hours a week where we help people with their automation issues.


🗣️ 💭 Quotable quotes

  • If you think education is expensive, try ignorance — Derek Bok
  • The amateur does not know what to do. The master knows what not to do — James Clear
  • The secret to getting ahead is getting started. The secret of getting started is breaking your complex, overwhelming tasks into small manageable tasks, and then starting on the first one — Mark Twain

AHK Newsletter⏩SHARE WITH A FRIEND⏩

Do you know someone that would benefit from learning useful automation tips like the gems in today’s issue?  Take a second and forward this newsletter and bask in the glow of their undying gratitude.  Was this email forwarded to you?

Sign up for this Newsletter hereWe never share your email with anyone.  Ever!


the-Automator  Dedicated to Multiplying your Income

Joe, Isaias, Irfan and staff


P.S. Mankind has been using tools to work smarter, not harder throughout history.

What have you made that has saved you time❓   

If you’re new to this and want help, by all means schedule a call with us today

 

Your Browser Has a Hard Drive: OPFS and 4 Other Things I Missed

Your Browser Has a Hard Drive (And Four Other Things I Wish I’d Known Sooner)

I’ve been building a bunch of browser-based tools lately. Audio converters, video reducers, watermarkers, document converters. All of them run entirely in your browser, nothing uploaded to a server, and the engine underneath most of them is WebAssembly.

WebAssembly (Wasm for short) is what lets you take a program written in C or Rust, something like ffmpeg, and run it in a browser tab at near-native speed. It’s genuinely amazing. And I leaned on it HARD.

Then I got curious. Was I actually using the right tool, or had I just found one hammer I liked? So I did what I always do when I want a reality check: I fanned the question out to five different AI models and asked them to tear my approach apart.

They agreed on something I did NOT want to hear. 😊

Have you ever gotten really good at one tool and then quietly started using it for everything?

That’s what was happening. Let me show you what I found.

The thing every single model told me first

WebAssembly is not an architecture. It’s one part.

Here’s the analogy that made it click for me. Wasm is a really fast engine. But an engine is not a car. You still need wheels, a fuel tank, and a steering wheel. Bolting a bigger engine onto something that needed better tires doesn’t help.

One of the models put it bluntly: the mistake is moving work into Wasm just because the work involves computing something. That stung a little, because that is EXACTLY the reasoning I’d been using.

So here are the four other pieces, what each one is actually for, and when it beats reaching for Wasm.


1. OPFS: your browser has a hard drive ⚡

This is the one I’m most annoyed I didn’t know about sooner, so it goes first.

OPFS stands for Origin Private File System. Terrible name. What it actually means: your web page gets its own private hard drive inside the browser.

Here’s the analogy. The old way of storing data in a browser (called IndexedDB) is like a filing cabinet with a clerk. You hand the clerk a whole box, they label it and file it away. Want one page out of that box? You ask the clerk, they haul out the ENTIRE box, you dig through it, then hand the whole thing back.

Fine for small boxes. Awful when the box is a 400 MB video.

OPFS hands you the keys to your own storage unit. Walk in, go to shelf 3, grab exactly what you need, put it back, leave. That’s the whole difference: you can read and write specific chunks of a file instead of the whole thing at once.

Why this matters so much for Wasm specifically:

Programs written in C and Rust over the last forty years all assume reading a file is instant. You ask for the bytes, the bytes are there on the very next line. That assumption is baked into SQLite, into ffmpeg, into basically everything.

Browsers don’t work that way. So when you compile that code to Wasm, the tooling has to fake it with a workaround that’s slow and fragile. One of the models called the standard in-memory approach “a toy,” and honestly, that’s the generous read.

OPFS has a mode that gives those programs the instant file access they expect. No faking. That’s why the official browser version of SQLite uses it, and it’s why sites like StackBlitz can run a whole development environment in a tab.

Two things OPFS is NOT

  • It’s not your real filesystem. Nothing written to OPFS shows up in File Explorer. The user can’t see it or open it in another program. It’s invisible scratch space that belongs to the page.
  • It’s not permanent. It lives under a storage quota, and the browser can clear it out if disk space gets tight. Anything the user would be upset to lose needs an export button or a copy on a server.

One catch worth knowing: the fast mode only works in a background thread, never on the main page. That’s deliberate, since a slow disk read on the main thread would freeze the whole UI. Which conveniently forces your heavy work off the main thread, where it belonged anyway. 😃


2. WebGPU: when to hand the work to your graphics card 🚀

Wasm runs on your CPU. Your CPU has maybe 8 or 16 cores. Your graphics card has thousands.

The analogy I like: the CPU is a handful of PhDs. Give them a complicated problem with lots of decisions and they’ll work through it beautifully. The GPU is ten thousand grade schoolers. Give any one of them a hard problem and you get nothing. Give all ten thousand the same simple addition problem at once and they’ll finish before the PhDs have picked up a pencil.

So GPU wins when the work is:

  • The same operation repeated over a LOT of data
  • Heavy on math, light on decision-making
  • Big enough to be worth the setup cost

Image filters, AI model inference, physics, 3D rendering. That’s GPU territory. This is how AI models now run directly in a browser tab with no server involved.

But here’s the trap. Getting data to the graphics card and back costs time. If your job is small, you’ll spend more time shipping the data over than you saved. A tight loop over ten thousand items in Wasm beats a GPU round trip every time.

These two aren’t rivals. The pattern everyone recommended: Wasm is the brain, the GPU is the muscle. Wasm handles the logic, the file format, the undo history. The GPU crunches the pixels. Figma works exactly this way.


3. Plain old JavaScript, running in parallel

This one genuinely surprised me.

I’d assumed Wasm just beats JavaScript, full stop. It’s compiled, it’s low-level, it should win. One model gave me an actual number: well-written JavaScript working on the right kind of data typically lands within 1.5x to 2x of Wasm speed, and sometimes matches it outright.

Then it dropped the example that really got me: Photopea, the free Photoshop clone in your browser, is overwhelmingly hand-tuned JavaScript. So are Excalidraw and tldraw.

And there’s a better point buried in here. Browsers can run several background workers at once. A pool of workers splitting up a job in ordinary JavaScript will beat a single-threaded Wasm module, even though each individual Wasm operation is faster.

Doing four things at once beats doing one thing quickly. That’s not a browser lesson, that’s just a lesson.

There is one real gotcha here. If you want those workers to share memory directly (the fastest way to pass data between them), you have to turn on some strict security headers. Those headers can break third-party embeds and login popups. Every single model flagged this independently, which tells me it bites people constantly.

Worth knowing before you’re debugging why your login stopped working. 😊


4. Just keep it on the server 🤖

This is the unglamorous one, and it’s right more often than any of the above.

If your tool is forms, dashboards, lists, or reports, the server should do the work. You get:

  • One place where the truth lives, so no syncing bugs
  • Google can actually read your pages
  • Analytics that ad blockers can’t hide from you
  • You push a fix and it’s live, no waiting for anyone to reload a cached file
  • It runs fine on a $50 phone

One model estimated developer speed is genuinely 2x to 3x faster for this kind of app. That’s a HUGE amount of time back.

Where it falls apart is anything needing instant feedback. Dragging a slider, drawing, editing audio. A 60 millisecond round trip is invisible when you submit a form and completely unusable when you’re dragging a handle.

The line is simple: can the interaction survive a trip to the server and back? If yes, send it. If no, it has to run locally.


The sneaky good idea nobody else mentioned

One model surfaced something the other four missed, and I think it’s the most interesting thing in the whole pile.

Several server platforms (Cloudflare Workers among them) run WebAssembly too.

Which means the exact same module can run in the browser for an instant preview, AND run on the server for the official version that search engines see. One codebase, two places, no duplicated logic.

I already deploy my tools on Cloudflare. That one’s going on my list. 🚀


The cheat sheet

If you need to…Reach for
Run existing C or Rust code fastWebAssembly
Do the same math over tons of dataWebGPU
Handle big files without eating all the memoryOPFS
Keep the page responsive during heavy workWeb Workers
Build forms, dashboards, or anything SEO matters forKeep it on the server

What I actually took away from this

One of the models said something that stuck with me. The skill isn’t knowing all these technologies. It’s correctly figuring out which 10% of your app is genuinely performance-critical, and only reaching for the heavy tools there.

That’s the same lesson automation keeps teaching me. The win almost never comes from the fanciest possible solution. It comes from correctly identifying the ONE thing that’s actually slow and fixing that.

Don’t get me wrong, I’m not abandoning WebAssembly. For my audio and video tools it’s exactly right. But I was reaching for it out of habit instead of measurement, and OPFS is a real gap I need to go fix.

One more thing worth mentioning about the process itself. Asking five competing AI models the same question and comparing the answers was FAR more useful than asking one. Where they all agreed, I could trust it. Where they disagreed (and they did, sharply, on whether JavaScript can keep up with Wasm) that disagreement told me it’s genuinely situational rather than settled.

That’s a technique I’d recommend for any decision that matters. Don’t ask one AI. Ask several and watch where they split.

How about you? Have you found a tool you’re reaching for out of habit rather than because it’s the right fit? I’d bet you have. I certainly did. ❓


P.S. A quick honesty note. These models quoted specific browser version numbers and support details pretty confidently, and that stuff drifts fast. I’ve deliberately left the version specifics out of this post. If you’re about to build on any of it, go verify current browser support yourself before you commit. Trust, but verify. 😊