Skip to content

omnipad-js/OmniPad

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

277 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐ŸŽฎ OmniPad (Web Virtual Gamepad)


npm version npm version license Vue3

Break the device barrier: Wrap any Web game for mobile with a single configuration.

OmniPad is a high-performance, headless virtual input engine specifically designed for Web-based games (HTML5 Canvas, Ruffle Flash Emulator, Godot Web exports, etc.).

It provides a comprehensive orchestration system that translates "Touch Gestures / Physical Gamepad Inputs" into "Native Browser Keyboard / Mouse Events." Without modifying a single line of your game's core source code, OmniPad empowers legacy web games with a native-mobile-like control experience.

๐Ÿšจ Live Demo: Try it on your Mobile Device ๐Ÿšจ


๐Ÿ†š Why OmniPad?

Most joystick libraries (like nipple.js) only provide raw UI and coordinate calculations. OmniPad is a complete input adaptation layer: We don't just provide the UI; we solve the "end-game" pain points of web game porting, such as Shadow DOM isolation, browser focus loss, and multi-touch conflicts.

โœจ Core Architecture & Features

  • ๐Ÿš€ Headless Design: The input state machine, gesture recognition, and core scheduling are fully encapsulated in the zero-dependency @omnipad/core. It is natively framework-agnostic. We currently provide @omnipad/vue, a production-ready Vue 3 adapter.
  • ๐ŸŒ Cross-Origin Iframe Bridge: A major feature in v0.6. Built-in secure IPC communication protocol based on postMessage. Automatically detects <iframe> containers on the page to enable real-time coordinate conversion and keyboard signal tunneling, allowing games โ€œbehind the Great Firewallโ€ to be controlled just like native web pages.
  • ๐ŸŒฒ Flat Profile & Forest: An innovative JSON parsing engine. It supports parsing a single flat configuration into multiple independent root nodes, allowing you to use native CSS Flex/Grid to build complex responsive layouts for both landscape and portrait modes.
  • ๐Ÿ‘ป Event Penetration & Focus Protection: Features high-fidelity synthetic event routing that elegantly penetrates the Shadow DOM boundaries of WebAssembly emulators (like Ruffle). Built-in "Auto-Focus Reclaim" logic ensures keys never get stuck or unresponsive.
  • ๐Ÿ“ Smart Sticky & Adaptive Layout: The LayoutBox constraint system not only natively supports various CSS units such as px, %, and vh/vw, but also enables component-level sticking via stickySelector. It automatically tracks position changes of any DOM element on the page and synchronizes coordinates in real time, making it the ultimate tool for developing game overlay UIs and browser extensions.
  • ๐ŸŽ›๏ธ Touch-to-Spawn (Dynamic Mounting): Supports spawning joysticks or buttons anywhere in an InputZone upon touch. This perfectly mimics the control habits of modern mobile action games.
  • โšก๏ธ Performance First: No expensive DOM reflows. Introduce a singleton ResizeObserver pool and the Rect caching mechanism. All displacement calculations happen in memory, with hardware acceleration forced via translate3d. Built-in rAF (requestAnimationFrame) throttling ensures perfect synchronization with high-refresh-rate screens.
  • ๐Ÿ”Œ Input Fusion: Managed by a unified underlying state machine, OmniPad supports simultaneous inputs from screen touch, mouse clicks, and Physical Gamepads, with real-time synchronized feedback on the virtual UI.

๐Ÿ“ฆ Installation

Ensure you have Vue 3 installed in your project (peerDependencies).

npm install @omnipad/core @omnipad/vue

โš ๏ธ Note: Don't forget to import the base styles in your entry file (e.g., main.ts or App.vue): import '@omnipad/vue/style.css';


๐Ÿš€ Quick Start

Pattern 1: Standalone Mode

Ideal for simple scenarios where you need to add fixed buttons to specific corners. No complex configuration required; just use them as standard UI components.

<script setup>
import { TargetZone, VirtualButton, VirtualJoystick } from '@omnipad/vue';
import '@omnipad/vue/style.css';
</script>

<template>
  <div class="game-container">
    <!-- Deploy an action button mapped to the W key -->
    <VirtualButton
      label="JUMP"
      target-stage-id="$stage"
      :mapping="{ code: 'KeyW' }"
      style="width: 80px; height: 80px; z-index: 100;"
    />

    <!-- Deploy an analog stick with 360ยฐ cursor displacement -->
    <VirtualJoystick
      :cursor-mode="true"
      :cursor-sensitivity="1.2"
      target-stage-id="$stage"
      :mapping="{ stick: { type: 'mouse', button: 0 } }"
      :layout="{ bottom: '120px', left: '120px', width: '150px', height: '150px', zIndex: 100 }"
    />

    <!-- Deploy a full-screen reception zone to handle simulated events -->
    <TargetZone
      widget-id="$stage"
      cursor-enabled
      :layout="{ left: 0, top: 0, height: '100%', width: '100%' }"
    />
  </div>
</template>

Pattern 2: Data-Driven Mode

Recommended for complex applications. Define screen partitions (Zones) and all key mappings via a flat JSON profile. Use RootLayer (or any OmniPad component) as the root node to carry the parsed ConfigTreeNode.

1. Define profile.json:

{
  "meta": { "name": "Action Layout" },
  "items": [
    {
      "id": "$ui-layer",
      "type": "root-layer"
    },
    {
      "id": "$game-canvas",
      "type": "target-zone",
      "parentId": "$ui-layer",
      "config": {
        "cursorEnabled": true,
        "layout": { "left": 0, "top": 0, "height": "100%", "width": "100%" }
      }
    },
    {
      "id": "movement",
      "type": "d-pad",
      "parentId": "$ui-layer",
      "config": {
        "mapping": {
          "up": "ArrowUp",
          "down": "ArrowDown",
          "left": "ArrowLeft",
          "right": "ArrowRight"
        },
        "layout": { "left": "10%", "bottom": "20%", "height": "20%", "isSquare": true }
      }
    },
    {
      "id": "btn-fire",
      "type": "button",
      "parentId": "$ui-layer",
      "config": {
        "label": "FIRE",
        "mapping": "Space",
        "layout": { "right": "10%", "bottom": "20%", "height": "10%", "isSquare": true }
      }
    }
  ]
}

2. Parse and Render in Vue:

<script setup>
import { computed } from 'vue';
import { parseProfileForest } from '@omnipad/core';
import { RootLayer } from '@omnipad/vue';
import profileRaw from './profile.json';

// Analyze flat configuration and build the runtime component forest
const forest = computed(() => parseProfileForest(profileRaw));
</script>

<template>
  <div class="viewport">
    <!-- Player element, replace with Ruffle / H5 player -->
    <canvas id="my-game"></canvas>
    <!-- Upon receiving the root node, the engine will automatically generate the entire interactive interface through recursion. -->
    <RootLayer
      class="ui-layer"
      v-if="forest.roots['$ui-layer']"
      :tree-node="forest.roots['$ui-layer']"
    />
  </div>
</template>

<style>
.viewport,
#my-game,
.ui-layer {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
}
</style>

๐Ÿ•น๏ธ Gamepad API Integration

Want to use an Xbox or PlayStation controller? Simply add a mapping table. OmniPad automatically handles controller polling. When you press a physical button, the corresponding virtual button on the screen will synchronously trigger its press animation, providing perfect haptic feedback.

import { GamepadManager } from '@omnipad/core/dom';

// Start global physical gamepad monitoring
GamepadManager.getInstance().setConfig(forest.value.runtimeGamepadMappings);
GamepadManager.getInstance().start();
// Add a mapping array at the root of profile.json:
"gamepadMappings": [
  {
    "buttons": { "RT": "btn-fire" }
  },
  {
    "buttons": { "A": "btn-jump" },
    "leftStick": "my-joystick"
  }
]

๐Ÿ’ก Tip: Supports array-based mapping for Local Co-op scenarios! Player 1 and Player 2 can play together without interference.


๐ŸŒ Iframe Penetration & Security Guide

OmniPad provides robust cross-origin iframe penetration capabilities. To prevent malicious scripts from hijacking input signals, we implement a "Double-Handshake + Whitelist Validation" security mechanism.

Step 1: Configure Whitelist in the Host Document

The Host is the main page where your virtual gamepad UI resides. For security reasons, the IframeManager will NOT send coordinates or key signals to unauthorized domains.

import { IframeManager } from '@omnipad/core/dom';

const iframeMgr = IframeManager.getInstance();

// 1. By default, IframeManager trusts the current origin (window.location.origin).
// 2. If the game runs on a different domain, explicitly add it to the whitelist:
iframeMgr.addTrustedOrigin('https://game-provider.com');

// โš ๏ธ WARNING: Using '*' (wildcard) in production is strongly discouraged. (Will be Rejected by IframeManager)

Step 2: Initialize the Receiver in the Guest (Iframe)

You must inject a lightweight receiver script into the environment where the game (Iframe) is running. To prevent unauthorized sites from controlling the game, the receiver also requires a whitelist.

// Script running INSIDE the game Iframe
import { initIframeReceiver } from '@omnipad/core/guest';

initIframeReceiver({
  // CORE SECURITY: Only accept signals from your main site.
  // Reject postMessage from any other sources.
  allowedOrigins: ['https://your-main-site.com'] 
});

Step 3: Configure CSP (Content Security Policy)

As a final layer of browser-level defense, it is recommended to set the frame-ancestors directive in the game server's response headers or via a Meta tag to restrict which sites can embed the game.

<!-- Only allow embedding by the current domain and your trusted host -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'self' https://your-main-site.com">

๐Ÿ”’ Security Deep Dive (FAQ)

Q: What are "Trusted Sources"?

A: In Web Security, an Origin consists of Protocol + Domain + Port.

  • Host-side Whitelist: Prevents gamepad coordinates (which may contain user interaction data) from being leaked to unrelated ad iframes or malicious third-party containers.
  • Guest-side Whitelist: Prevents unauthorized websites from masquerading as the host to send input commands (e.g., simulating clicks on "Buy Now" buttons) to the game.

Q: Why does the Iframe application need to verify a "Signature"?

A: A single web page may run dozens of extensions (translators, ad blockers, etc.), many of which use postMessage for communication. The OMNIPAD_IPC_SIGNATURE (e.g., __OMNIPAD_IPC_V1__) acts as a private key. It ensures the Guest receiver only processes valid OmniPad protocol data and ignores noise from other scripts, preventing logic errors or crashes.

Q: Why is the * wildcard prohibited?

A: Using * opens a door to the entire internet.

  1. Host side: If a malicious iframe is injected into your page, it could capture all your gamepad interactions.
  2. Guest side: Any website embedding your game could use simple scripts to take full control of the game character. In production, explicit whitelist configuration is a mandatory security obligation.

๐Ÿ› ๏ธ Troubleshooting

  1. Signals not being sent?
    • Verify the host page calls IframeManager.getInstance().addTrustedOrigin().
    • Ensure the string includes the full protocol (e.g., https://).
  2. No response inside the Iframe?
    • Ensure initIframeReceiver is executed correctly within the iframe.
    • Check the browser console for [OmniPad-Security] Blocking untrusted iframe from origin warnings.
  3. Coordinate offsets are incorrect?
    • Ensure the IframeManager can accurately retrieve the Iframe element's getBoundingClientRect. If the Iframe has complex CSS transforms (like scale), ensure you are using the Sticky Layout logic introduced in v0.5+.

๐Ÿ› ๏ธ Advanced Customization

OmniPadโ€™s core philosophy is "Logic Closed, UI Open."

1. CSS Theming

The library separates layout from style. The layout property handles physical coordinates, while visual aesthetics are managed by CSS variables.

/* Modify the global theme */
:root {
  --omnipad-btn-bg: rgba(0, 255, 100, 0.2);
  --omnipad-btn-border: 2px solid #00ff6a;
}

/* Use the className field in config for specific button styles */
.danger-btn {
  --omnipad-btn-bg: rgba(255, 0, 0, 0.4);
}

2. Factory Extension

You can write your own custom components and register them into the parsing engine seamlessly.

import { registerComponent } from '@omnipad/vue';
import CustomTrackpad from './components/CustomTrackpad.vue';

// Register the custom component as 'custom-trackpad'
registerComponent('custom-trackpad', CustomTrackpad);

After registration, you can directly use "type": "custom-trackpad" in your JSON configuration. The engine will automatically instantiate and bind the Core logic for you.


๐Ÿงฉ Widgets Overview

  • ๐Ÿ”˜ VirtualButton: Supports taps and long-presses. Maps to keyboard or mouse buttons.
  • ๐Ÿ–ฑ๏ธ VirtualTrackpad: Relative displacement trackpad with a built-in gesture engine supporting Double-tap & Hold.
  • โž• VirtualDPad: Authentic 8-way digital D-pad optimized for zero-latency in retro action games.
  • ๐Ÿ•น๏ธ VirtualJoystick: 360ยฐ analog stick with L3 support. Dual engines for discrete key mapping and continuous cursor velocity.
  • ๐ŸŽ›๏ธ TargetZone: The reception stage for events. Dispatches low-level DOM events and renders focus-return feedback.
  • ๐Ÿ“ฅ InputZone: A logic container that defines interactive regions and handles the "Touch-to-Spawn" dynamic widget logic.
  • ๐Ÿ—๏ธ RootLayer: The entry point. Manages the lifecycle of entities and provides the dependency injection context.

๐Ÿ—บ๏ธ Status & Vision

๐Ÿ“ข Current Status: Maintenance Mode
The core of OmniPad (v0.6) has fully achieved its design objectives, delivering an exceptionally robust underlying input state machine. Due to limited personal capacity, we will primarily focus on core bug fixes and stability maintenance at this time, with no plans for large-scale new feature development in the near future.

However, OmniPad's underlying architecture (Headless Core) inherently possesses limitless scalability potential. Below are evolutionary directions we consider highly valuable, and we warmly welcome community participation through PRs to build together:

  • Advanced Macro & Combo System
    • Turbo (Auto-fire) and Toggle mode support.
    • Custom sequences for "One-tap combos."
  • Framework Adapters
    • Launching @omnipad/react, @omnipad/svelte, and a dependency-free Web Components version. (Looking for Maintainers!)
  • OmniPad Studio
    • A visual drag-and-drop editor for creating and exporting profile.json files.
  • Universal Browser Extension
    • A Chrome/Edge extension to bring OmniPad to any gaming site (Poki, Newgrounds, etc.) instantly.

๐Ÿ“œ License

This project is licensed under the MIT License.


Built with โค๏ธ for the Web Gaming community.

About

A modern, headless, and high-performance virtual input engine for Web games. Features Shadow DOM penetration, multi-touch tracking, Gamepad API mapping and Iframe penetration.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors