# Global Shortcut and Key Hook Implementations in Fluxer Desktop

> Discover how Fluxer desktop implements global shortcuts with Electron and uIOhook-napi for efficient hotkey and input monitoring. Learn about key hook strategies.

- Repository: [Fluxer/fluxer](https://github.com/fluxerapp/fluxer)
- Tags: internals
- Published: 2026-03-17

---

**Fluxer implements global shortcuts using Electron's built-in `globalShortcut` API for accelerator-based hotkeys, while leveraging the `uIOhook-napi` native library for low-level global key hooks required for continuous input monitoring.**

The Fluxer desktop application repository (`fluxerapp/fluxer`) provides system-wide keyboard integration through two distinct mechanisms. These global shortcut and key hook implementations enable the app to capture keyboard events outside the browser context, powering features like quick-switchers and Push-to-Talk even when the application window is not focused.

## How Fluxer Registers Global Shortcuts

Global shortcuts in Fluxer use Electron's `globalShortcut` module to register system-wide accelerator keys. The implementation spans the main process, preload bridge, and renderer-side keybind manager.

### Main Process Registration in IpcHandlers.tsx

In [`fluxer_desktop/src/main/IpcHandlers.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/IpcHandlers.tsx) (around line 628), the main process handles IPC calls from the renderer to register accelerators with the operating system:

```typescript
// Simplified representation of the IPC handler
ipcMain.handle('register-global-shortcut', (event, accelerator, id) => {
  const success = globalShortcut.register(accelerator, () => {
    // When shortcut fires, notify renderer via global-shortcut-triggered
    mainWindow.webContents.send('global-shortcut-triggered', id);
  });
  registeredShortcuts.set(id, accelerator);
  return success;
});

```

The main process maintains a `registeredShortcuts` Map to track active registrations and handle cleanup.

### Preload Bridge API

The preload script at [`fluxer_desktop/src/preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/preload/index.tsx) (lines 8-18) exposes a safe bridge between the isolated renderer and main process:

```typescript
contextBridge.exposeInMainWorld('electron', {
  registerGlobalShortcut: (accelerator, id) => 
    ipcRenderer.invoke('register-global-shortcut', accelerator, id),
  onGlobalShortcut: (callback) => 
    ipcRenderer.on('global-shortcut-triggered', callback)
});

```

This abstraction allows the UI layer to work with a single `electron` object regardless of the underlying implementation.

### KeybindManager Coordination

In [`fluxer_app/src/lib/KeybindManager.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/lib/KeybindManager.tsx) (lines 950-1002), the UI layer orchestrates global shortcut registration. It builds lists of global keybinds, invokes `electron.registerGlobalShortcut`, and listens for `onGlobalShortcut` events to invoke matching command handlers.

## Low-Level Global Key Hooks with uIOhook

For features requiring raw key event streams (such as Push-to-Talk), Fluxer uses `uIOhook-napi` to capture low-level keyboard input outside Electron's standard accelerator system.

### Starting the Native Hook

The [`GlobalKeyHook.tsx`](https://github.com/fluxerapp/fluxer/blob/main/GlobalKeyHook.tsx) file in `fluxer_desktop/src/main/` (lines 27-133) initializes the native hook:

```typescript
import { UiohookKey, uIOhook } from 'uiohook-napi';

export function startHook() {
  uIOhook.on('keydown', (event) => {
    // Forward to renderer via global-key-event
    mainWindow.webContents.send('global-key-event', event);
  });
  uIOhook.on('keyup', (event) => {
    mainWindow.webContents.send('global-key-event', event);
  });
  uIOhook.start(); // Begins listening at OS level
}

```

IPC handlers defined in the same file expose `global-key-hook-start`, `global-key-hook-stop`, `global-key-hook-register`, and `global-key-hook-unregister` to the renderer.

### Renderer-Side Event Handling

The preload bridge exposes these capabilities in [`fluxer_desktop/src/preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/preload/index.tsx) (lines 28-45), including `globalKeyHookStart`, `onGlobalKeyEvent`, and `onGlobalMouseEvent`.

In [`fluxer_app/src/lib/KeybindManager.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/lib/KeybindManager.tsx) (lines 55-80 and 94-110), the manager decides whether to activate the hook based on configuration. When Push-to-Talk is enabled, it calls `electron.globalKeyHookStart()` and registers handlers for `onGlobalKeyEvent`, checking incoming keycodes against configured bindings to fire the `push_to_talk` command.

## Code Examples

### Registering a Global Shortcut

To register a system-wide shortcut like `Ctrl+Shift+K` for a quick-switcher:

```typescript
import { getElectronAPI } from '@app/utils/NativeUtils';

async function registerQuickSwitcher() {
  const accelerator = 'Control+Shift+K';
  const id = 'quick_switcher';
  const success = await getElectronAPI().registerGlobalShortcut(accelerator, id);
  
  if (success) {
    console.log('Global shortcut registered');
  }
}

```

Under the hood, this invokes the `register-global-shortcut` IPC channel, which calls `globalShortcut.register()` in the main process.

### Enabling Push-to-Talk with Global Key Hooks

For continuous key monitoring (Push-to-Talk), use the low-level hook:

```typescript
import { getElectronAPI } from '@app/utils/NativeUtils';
import { jsKeyToUiohookKeycode } from '@app/utils/UiohookKeycodes';

async function enablePushToTalk() {
  // Start the native hook (requires Input Monitoring permission on macOS)
  const started = await getElectronAPI().globalKeyHookStart();
  if (!started) throw new Error('Failed to start key hook');

  // Register specific keycode (e.g., KeyV)
  const keycode = jsKeyToUiohookKeycode('KeyV');
  await getElectronAPI().globalKeyHookRegister({
    id: 'ptt',
    keycode,
    ctrl: false,
    alt: false,
    shift: false,
    meta: false
  });

  // Listen for raw events
  const unsubscribe = getElectronAPI().onGlobalKeyEvent((evt) => {
    if (evt.keycode === keycode) {
      console.log('Push-to-Talk:', evt.type);
    }
  });

  // Cleanup function
  return () => {
    unsubscribe();
    getElectronAPI().globalKeyHookUnregister('ptt');
    getElectronAPI().globalKeyHookStop();
  };
}

```

### Unregistering All Global Shortcuts

To clear all registrations (e.g., on user logout):

```typescript
import { getElectronAPI } from '@app/utils/NativeUtils';

async function clearAllShortcuts() {
  await getElectronAPI().unregisterAllGlobalShortcuts();
  await getElectronAPI().globalKeyHookStop();
}

```

This triggers the `unregister-all-global-shortcuts` IPC handler, which calls `globalShortcut.unregisterAll()` in the main process.

## Summary

- **Fluxer uses two distinct systems**: Electron's `globalShortcut` for accelerator-based hotkeys and `uIOhook-napi` for raw key event streams.
- **Main process architecture**: [`IpcHandlers.tsx`](https://github.com/fluxerapp/fluxer/blob/main/IpcHandlers.tsx) manages global shortcuts while [`GlobalKeyHook.tsx`](https://github.com/fluxerapp/fluxer/blob/main/GlobalKeyHook.tsx) manages the native key hook.
- **Preload bridge**: [`preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/preload/index.tsx) exposes unified APIs (`registerGlobalShortcut`, `globalKeyHookStart`) to the isolated renderer.
- **UI coordination**: [`KeybindManager.tsx`](https://github.com/fluxerapp/fluxer/blob/main/KeybindManager.tsx) orchestrates both systems, determining which mechanism to use based on feature requirements.
- **Permission requirements**: Global shortcuts require standard OS accessibility permissions, while global key hooks on macOS require Input Monitoring permissions.

## Frequently Asked Questions

### What is the difference between global shortcuts and global key hooks in Fluxer?

**Global shortcuts** use Electron's built-in `globalShortcut` module and are designed for simple accelerator combinations like `Ctrl+Shift+K`. They trigger once when the combination is pressed. **Global key hooks** use the `uIOhook-napi` native library to capture raw `keydown` and `keyup` events continuously, which is necessary for features like Push-to-Talk that need to detect when a key is held down versus released.

### Where does Fluxer store the registered global shortcuts?

The main process stores registered shortcuts in a `registeredShortcuts` Map within [`fluxer_desktop/src/main/IpcHandlers.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/IpcHandlers.tsx) (around line 628). This Map tracks the relationship between command IDs (like `quick_switcher`) and their Electron accelerator strings, allowing the application to unregister specific shortcuts or clear all registrations on logout.

### Why does Push-to-Talk require a different implementation than regular shortcuts?

Push-to-Talk requires detecting the **duration** of key presses (held state versus released), which standard global shortcuts cannot provide. According to the source code in [`fluxer_desktop/src/main/GlobalKeyHook.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/GlobalKeyHook.tsx), the `uIOhook` implementation listens for continuous `keydown` and `keyup` events and forwards them to the renderer via the `global-key-event` IPC channel, enabling real-time voice activation that responds immediately to physical key state changes.

### How does the KeybindManager decide which system to use?

In [`fluxer_app/src/lib/KeybindManager.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/lib/KeybindManager.tsx) (lines 55-80), the manager checks the keybind configuration type. Standard accelerators route through `registerGlobalShortcut`, while bindings requiring raw input monitoring (like `push_to_talk`) trigger `globalKeyHookStart()` and register handlers via `onGlobalKeyEvent`. This abstraction allows the rest of the application to use a unified keybind API regardless of the underlying implementation.