Skip to content
 
 

Repository files navigation

FocusRetro

FocusRetro logo

A lightweight desktop app that auto-focuses Dofus Retro game windows when it's your character's turn in combat, and lets you switch accounts via a radial overlay. Built for multi-account players.

How it works

  1. Dofus Retro sends a system notification when a game event occurs (turn start, group invite, trade request, private message)
  2. FocusRetro intercepts the notification banner via OS APIs (Accessibility on macOS, WinRT/notification database on Windows)
  3. The character name is extracted and matched to the corresponding game window
  4. The game window is brought to the foreground automatically

No game client modification is needed.

Features

  • Auto-focus on turn: Switches to the correct game window when your turn starts
  • Group invite handling: Focuses the recipient's window when one of your characters sends a group invite (restricted to known accounts)
  • Trade request handling: Focuses the recipient's window when one of your characters sends a trade request (restricted to known accounts)
  • Private messages: Captures incoming PMs and displays them in a dedicated Messages tab with HTML item links cleaned up
  • Account detection: Detects all open Dofus Retro windows by parsing window titles
  • Account management: Reorder accounts via drag & drop, assign icons and colors, designate a principal account
  • Radial character selector: Hold a configurable hotkey to open a radial overlay centered on the cursor — move the mouse to the desired character slice and release to focus that account. Works on both macOS and Windows
  • Configurable global hotkeys: Default bindings — customizable in Settings:
    • F1 — Focus previous account
    • F2 — Focus next account
    • F3 — Focus principal account
    • Arrow keys also supported for previous/next navigation
  • Hotkeys work everywhere: Uses low-level event hooks (CGEventTap on macOS, WH_KEYBOARD_LL on Windows) so hotkeys fire even when Dofus (Wine) has focus
  • System tray: Dynamic icon (active/paused), shows principal account name, account count, toggle autoswitch, show/hide window, quit
  • Translations: English, French, Spanish — auto-detects system language on first launch
  • Persistent settings: All preferences (toggles, hotkeys, language, account profiles/order) saved to ~/Library/Application Support/com.focusretro.desktop/config.json (macOS) or %APPDATA%\com.focusretro.desktop\config.json (Windows)
  • Independent toggles: Each feature (autoswitch, group invite, trade, PM) can be enabled/disabled independently

Requirements

macOS

  • macOS 12+ (Monterey or later)
  • Accessibility permission must be granted:
    • System Settings → Privacy & Security → Accessibility → Enable FocusRetro

Windows

  • Windows 10 1903+ or Windows 11
  • Windows 11 / Windows 10 with notification access granted: uses WinRT UserNotificationListener (event-driven + poll backup). You will be prompted on first launch.
  • Windows 10 fallback: if UserNotificationListener access is not available or not granted, FocusRetro automatically falls back to polling the Windows notification database (wpndatabase.db) directly — no permission required.

Build tools

Build from source

# Clone the repository
git clone https://github.com/alacroix/focusretro.git
cd focusretro

# Install dependencies
npm install

# Run in development mode
npm run tauri dev

# Build for production
npm run tauri build

# Format all code (Prettier + cargo fmt)
npm run format

# Lint all code (ESLint + prettier --check + cargo clippy)
npm run lint

The built application will be in src-tauri/target/release/bundle/.

Offline variant

The offline variant disables the auto-updater entirely — no outbound HTTP connections at all:

VITE_UPDATER=false npx tauri build --config src-tauri/offline.conf.json -- --no-default-features

Key differences vs the standard build:

  • offline.conf.json sets plugins.updater = null and bundle.createUpdaterArtifacts = false
  • --no-default-features disables the auto_update Cargo feature (removes tauri-plugin-updater)
  • VITE_UPDATER=false hides update-related UI in the frontend

Permissions

macOS

FocusRetro requires Accessibility permission. This is used to:

  1. Read notification banners — An AXObserver watches the NotificationCenter process for new notification windows and reads their text content
  2. Focus game windowsAXUIElement APIs raise specific Dofus Retro windows
  3. Simulate keypressesCGEvent APIs simulate key events (e.g. Enter key for hotkey actions)
  4. Global hotkeys — A CGEventTap listens for keyboard events system-wide so hotkeys work even when another app is focused

When you first launch, you'll be prompted to grant Accessibility permission. If not, go to:

System Settings → Privacy & Security → Accessibility

Windows

FocusRetro reads toast notifications from Dofus Retro using one of three modes, selected automatically at runtime:

Mode When How
event Win11 / Win10 with access granted WinRT UserNotificationListener event-driven + 200ms poll backup
poll Access granted, events unavailable (unpackaged app) WinRT GetNotificationsAsync polled every 100ms
poll-db Win10, access not granted Polls %LOCALAPPDATA%\Microsoft\Windows\Notifications\wpndatabase.db (SQLite) every 200ms — no permission required

On first launch on Windows 11 (or Windows 10 with a compatible build), you will be prompted to grant notification access. If you decline or the API is unavailable (Windows 10), the DB fallback kicks in automatically.

No other special permissions are needed — window focus and key simulation use standard Win32 APIs.

Architecture

src-tauri/src/
├── lib.rs                  # App setup, tray icon, tray menu
├── commands.rs             # Tauri IPC commands
├── state.rs                # Shared app state (toggles, accounts, messages, hotkeys, language, persistence)
├── radial.rs               # Radial overlay geometry (segment hit-test, selection resolution, focus dispatch)
├── core/
│   ├── autoswitch.rs       # Main autoswitch controller
│   ├── accounts.rs         # Account detection
│   └── parser.rs           # Notification parser (turn, invite, trade, PM)
└── platform/
    ├── mod.rs              # Platform traits (WindowManager, NotificationListener)
    ├── macos/
    │   ├── hotkeys.rs      # CGEventTap global hotkey listener
    │   ├── window.rs       # CGWindowList + AXUIElement + CGEvent
    │   ├── notifications.rs # AXObserver on NotificationCenter
    │   └── permissions.rs  # Accessibility permission checks
    └── windows/
    │   ├── hotkeys.rs      # WH_KEYBOARD_LL global hotkey listener
    │   ├── window.rs       # EnumWindows + HWND-direct focus (AttachThreadInput/SetActiveWindow) + SendInput
    │   └── notifications.rs # WinRT UserNotificationListener (event/poll) + wpndatabase.db poll fallback (Win10)

src/
├── i18n.ts                 # i18next initialization (en/fr/es)
├── locales/                # Translation JSON files
├── App.tsx                 # Main layout with tabs
├── components/
│   ├── AccountList.tsx     # Account list with drag & drop, icons, colors
│   ├── MessageList.tsx     # PM display
│   ├── RadialSelector.tsx  # Radial account picker overlay (SVG wheel)
│   └── Settings.tsx        # Toggles, hotkey config, language selector
└── lib/
    └── commands.ts         # Typed Tauri invoke wrappers

Supported notification formats

Event Body pattern Action
Turn FR: C'est à <name> de jouer / EN: <name> 's turn to play / ES: le toca jugar a <name> Focus the named character's window
Group invite FR: <name> t'invite à rejoindre son groupe / EN: You are invited to join <name>'s group / ES: <name> te invita a unirte a su grupo Focus the receiver's window (inviter must be a known account)
Trade FR: <name> te propose de faire un échange / EN: <name> offers a trade / ES: <name> te propone realizar un intercambio Focus the receiver's window (requester must be a known account)
Private message FR: de <name> : <msg> / EN: from <name> : <msg> / ES: desde <name> : <msg> Store and display in Messages tab (no focus)

Supported platforms

Platform Status
macOS Supported
Windows Supported

Release variants

Each release ships two variants per platform:

Variant File suffix Auto-updater
Standard (none) Yes — checks GitHub on launch
Offline _offline No — zero outbound HTTP

The offline variant is built with --no-default-features (disables tauri-plugin-updater). It makes no network connections whatsoever. Both variants are attested by GitHub Actions.

Code signing (macOS)

macOS binaries are signed with an ad-hoc identity (-). They are not notarized, so Gatekeeper will block the app when downloaded from the internet. To run it:

xattr -d com.apple.quarantine /Applications/Focus\ Retro.app

Or right-click the app → Open → Open anyway.

TODO: set up proper Apple Developer signing + notarization for a smoother install experience.

Code signing (Windows)

Windows binaries are currently unsigned. SmartScreen will warn on first install — click "More info → Run anyway".

TODO: set up signing via Azure Artifact Signing (~$10/month, requires registered organization) or SignPath Foundation (free for OSS). See .github/workflows/release.yml and src-tauri/tauri.conf.json — the signCommand config is already in place, just needs credentials.

Security

Only download FocusRetro from the official repository: github.com/alacroix/focusretro. Third-party sites have been known to redistribute modified Dofus community tools containing malware.

Every release asset is attested by GitHub Actions. You can verify a binary before running it:

gh attestation verify <file> --repo alacroix/focusretro

Example:

gh attestation verify Focus.Retro_<version>_aarch64.dmg --repo alacroix/focusretro

A valid attestation confirms the file was built automatically from source — no human intervention.

Disclaimer

FocusRetro is an open-source utility for players of Dofus Retro.

This project is not affiliated with or endorsed by Ankama. Dofus is a trademark of Ankama.

License

MIT

Releases

Packages

Contributors

Languages