Documentation

Technical
Archive.

Deep dives into the architecture, implementation, and core systems powering the IsoKey automation engine.

🙋 Frequently Asked Questions

How does the driver interface work?

IsoKey utilizes a custom-signed kernel-mode driver that hooks directly into the Windows Human Interface Device (HID) stack. By intercepting raw input packets at the lowest level, we bypass the standard message queue, allowing for absolute isolation of secondary hardware from your primary system input.

Is there any input latency?

Because the interception occurs at the driver level rather than the user-mode hook level, latency is effectively sub-millisecond. In benchmarking, IsoKey overhead is statistically insignificant compared to native Windows input processing.

Is IsoKey safe for Anti-Cheat?

While IsoKey uses kernel drivers, it does not use "cheat" techniques like code injection. It is a legitimate productivity tool. However, some highly aggressive anti-cheats may flag the driver signature. We provide a native uninstaller to completely remove all hooks when not in use.

🧩 Dynamic Actions Overview

IsoKey features a highly modular plugin infrastructure that allows developers to implement, test, and register custom hotkey actions dynamically without altering the host code. By placing compiled assemblies (DLLs) in the Actions/ subdirectory of the runtime, IsoKey automatically discovers, categorizes, lists, configures, and dispatches keystroke handlers on execution.

The SDK exposes two developer-facing interfaces depending on execution complexity:

  • IDynamicActionPlugin: Used for advanced, stateful actions. It encapsulates metadata details (custom themes, names, descriptions) and yields a custom WPF UserControl to support real-time configuration.
  • IActionPlugin: Used for static built-in system mappings (e.g. launching standard applications).

Capabilities & Permissions

Custom action plugins have the exact same execution capabilities and access permissions as native host actions. Because they load directly into the host application domain at runtime, they run under the same process context and privileges. If the main application runs elevated (as Administrator), the plugin also runs with administrative permissions, granting it full capability to interact with Win32 APIs, execute system commands, manipulate the filesystem, control hardware inputs, and manage memory.

🚀 Quickstart Guide

Get a custom dynamic action plugin running inside IsoKey in a few easy steps:

  1. Create a Class Library Project: Create a new Class Library project targeting .NET 9.0 in Visual Studio or Rider. Ensure you target the Windows SDK variant (e.g., net9.0-windows10.0.17763.0).
  2. Configure the Project File (.csproj): Enable WPF and Windows Forms support in your project settings so you can design UI elements, and reference KeyboardRemapper.csproj (or the compiled KeyboardRemapper.dll).
  3. Implement the Interfaces: Create a class implementing IDynamicActionPlugin. Optionally implement IGlobalSettingsProvider for persistent global setup configurations.
  4. Build and Deploy: Build the project in Release mode, and copy the generated DLL assembly directly to the Actions/ folder inside the IsoKey execution folder.
  5. Verify and Test: Launch IsoKey. Check the Action Studio interface to confirm your plugin is loaded and available for keyboard remapping assignments.

⚙️ IDynamicActionPlugin Specification

This interface defines the entry point for custom modular plugins. Implementations are loaded and instantiated dynamically via reflection.

using System;
using System.Windows.Controls;
using KeyboardRemapper.Core;

namespace KeyboardRemapper.Models
{
    public interface IDynamicActionPlugin
    {
        string PluginId { get; }
        string DisplayName { get; }
        string Description { get; }
        string Category { get; }
        string Color { get; }
        string Icon { get; }

        UserControl CreateConfigControl(KeyboardAction action, Action onDirty);
        bool SaveSettings(KeyboardAction action);
        void Execute(KeyboardAction action, InputManager manager, int vk, int chainDepth);
        bool IsDefaultOrSuggestedName(string name) => string.IsNullOrEmpty(name) || name.StartsWith(DisplayName, StringComparison.OrdinalIgnoreCase);
    }
}

Property & Method Descriptions

Member Type Description
PluginId string A unique string identifier for the action type (e.g. "MacroRecorder"). Unique per plugin.
DisplayName string Readable header text displayed in the Action Studio selector (e.g. "Macro Recorder").
Description string A short description of the plugin shown under the header details inside Action Studio.
Category string Target section filter category (e.g. "Workspace Productivity"). Custom strings dynamically generate new categories in the UI layout.
Color string Hex code defining the plugin theme's highlight color (e.g. "#8E24AA").
Icon string Emoji character (e.g. "🎙️") or resource index mapping from IsoKey_Icons.dll using the format dll:index (e.g. "dll:25").
CreateConfigControl UserControl Instantiates and returns the custom WPF configuration component layout.
SaveSettings bool Callback executed on saving. Lets the plugin inspect UI inputs and persist options into the action model.
Execute void Runtime hotkey callback containing key interception and action logic.
IsDefaultOrSuggestedName bool Checks if the action's current name is a default/suggested name that should be auto-updated when settings change. Has a default implementation checking if the name starts with the plugin's DisplayName.

🌐 IGlobalSettingsProvider Specification

Implement this interface alongside IDynamicActionPlugin to inject custom global configuration tabs directly into the main Settings Window sidebar. This is ideal for plugins requiring shared configuration, credentials, or API tokens rather than per-action configurations.

using System;
using System.Windows.Controls;
using KeyboardRemapper.Core;

namespace KeyboardRemapper.Models
{
    public interface IGlobalSettingsProvider
    {
        string SettingsTabHeader { get; }
        string SettingsTabTag { get; }

        UserControl CreateSettingsControl(InputManager manager, Action onDirty);
        void SaveSettings(InputManager manager);
    }
}

⚡ IActionPlugin Specification

For simple execution scripts mapped to built-in system events, the static interface handles dispatching through standard mapped action enum tags.

namespace KeyboardRemapper.Core
{
    public interface IActionPlugin
    {
        ActionType ActionType { get; }
        void Execute(KeyboardAction action, InputManager manager, int vk, int chainDepth);
    }
}

📖 API Reference & Core Types

To interface with the host environment, plugins use core models and services from the KeyboardRemapper namespace. The main classes and types are defined below.

1. KeyboardAction Class

Represents a mapping configuration instance. Use this object to load, modify, and store configuration details.

Property Type Description
Id Guid Unique identifier of this specific key mapping configuration.
Name string User-defined descriptive label for the hotkey mapping (e.g. "Evaluate Math Inline").
Target string Descriptive string summarizing the action behavior, shown directly in host tables.
Arguments string A serialized JSON payload containing instance-specific configuration details. Store settings here in SaveSettings.
PluginId string Matches the identifier string exported by your plugin's PluginId getter.

2. InputManager Class

The core execution controller engine. Provides methods to update host status, trigger secondary actions, and retrieve environmental values.

Member Signature / Type Description
Instance static InputManager Global singleton instance to access host manager capabilities anywhere.
UpdateStatus void UpdateStatus(string message) Prints a status text message in the footer bar of the main Dashboard.
Actions ObservableCollection<KeyboardAction> The collection of all registered user actions in the current configuration profile.
Audio AudioService System Audio controller service for setting volumes, adjusting mutes, and routing devices.

3. System Logging Integration

Plugins can write real-time log entries to the host application's debug console ring buffer. This output appears inside the host's System Log Window.

using KeyboardRemapper.UI.Windows;

// Log general system information
SystemLogWindow.AppLog("My custom plugin initialized.", LogType.System);

// Log action events
SystemLogWindow.AppLog("Failed to query local database.", LogType.Action);

The second parameter is the LogType enum from the KeyboardRemapper.UI.Windows namespace, supporting: System, Key, and Action.

🎨 Configuration UI Integration

When implementing CreateConfigControl, the plugin must return a WPF UserControl. The control is dynamically hosted in the Action Studio editing window. Follow these guidelines to build your control:

  1. State Restoration: On loading, the control should deserialize configuration states stored in action.Arguments (typically using JSON).
  2. Interactive Modifiers: Invoke the provided onDirty action callback whenever the user updates fields, which toggles the Action Studio's unsaved changes indicator.
  3. Saving Payload: Write all configuration fields to action.Arguments inside SaveSettings, or update them in real-time as state modifications occur.
  4. Target Summary & Auto-naming: Update action.Target to describe the configured behavior dynamically. Check if action.Name starts with "New " or matches other default template prefixes, and if so, automatically override it with a descriptive name representing the specific action settings.
  5. Dynamic Custom Icons: Set action.CustomIconPath = "Glyph:<Segoe_MDL2_Glyph>" dynamically when selection changes or in SaveSettings. Setting this path allows your action to dynamically update its visual style on the Dashboard tiles and layout keys based on the active operation mode.

🖌️ WPF Styling & Shared Native Resources

Custom user controls do not need to redefine dark-mode themes, buttons, textboxes, or sliders. Because the WPF control is added directly to the visual tree of the IsoKey host, it automatically inherits all host application resources.

1. Reusable Host Style Keys

Apply these styles to standard WPF components using the Style="{StaticResource StyleKey}" property format to match native visual aesthetics:

Target Control Style Resource Key Visual Description
TextBox ModernTextBox Dark background, subtle borders, focused purple highlight, Segoe UI typography.
Slider ModernSliderStyle Modern track slider bar with customized thumb handles.
ComboBox ModernCombo Dark dropdown style matching the settings panel combos.
RadioButton ModernRadioButton Accent colored circular indicator list buttons.
Separator ModernSeparator A thin horizontal border divider matching card dividers.

2. Layout Guidelines

  • The available content space is dynamic, but typically fits inside a standard width of 400px. Use vertical stacking layout (like a StackPanel or Grid with auto-sizing rows).
  • Design your user control elements to look compact. Use clean font sizes: 11 or 12 for checkboxes and descriptions, and 10 bold with dark gray foreground headers (e.g. #555) for settings groups.

3. Reference XAML Code Example

<UserControl x:Class="KeyboardRemapper.Plugins.MyCustomConfigControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Margin="0,10,0,0">
        <!-- Label Header -->
        <TextBlock Text="CONFIGURATION SETTINGS" FontSize="10" Foreground="#555" FontWeight="Bold" Margin="0,0,0,8"/>
        
        <!-- Shared Native TextBox -->
        <TextBox Style="{StaticResource ModernTextBox}" Height="38" FontSize="12" Padding="10,0,10,0" 
                 VerticalContentAlignment="Center" TextChanged="TextBox_TextChanged"/>
    </StackPanel>
</UserControl>

💾 Data Persistence

For stateful action plugins, settings specific to an individual key mapping should be stored inside the KeyboardAction.Arguments JSON string. However, plugins often require shared global state, credentials, or API keys (e.g. OpenAI or Gemini keys) that apply to all instances of the action. To avoid writing to ad-hoc flat JSON files which can cause pathing or file lock conflicts, the host provides a unified database persistence interface: PluginPersistenceService.

All data managed through this service is serialized to JSON and persisted within the shared database file config_a.db under a dedicated "plugin_data" collection.

Persistence Strategy Comparison

Feature Per-Action Instance (JSON) Global Shared State (config_a.db)
Storage Target KeyboardAction.Arguments string LiteDB file config_a.db
Use Case Instance-specific parameters (e.g. macro text, webhook endpoints, local action variables) Shared credentials, access tokens, API keys (e.g. OpenAI key, Gemini key)
Scope Bound to a single mapped key config instance Shared globally across all instances of this action
Access Mechanism Direct property access via action.Arguments PluginPersistenceService.Save() / Load()

Using PluginPersistenceService

using KeyboardRemapper.Core.Services;

// Define a model for your settings
public class MyPluginSettings
{
    public string ApiKey { get; set; } = "";
    public bool EnableFeature { get; set; } = true;
}

// 1. Save settings using a unique plugin settings key
var config = new MyPluginSettings { ApiKey = "XYZ-123" };
PluginPersistenceService.Save("MyPlugin_GlobalSettings", config);

// 2. Load settings back (returns default/null if not yet saved)
var loaded = PluginPersistenceService.Load<MyPluginSettings>("MyPlugin_GlobalSettings");
if (loaded != null)
{
    string key = loaded.ApiKey;
}

💡 Development Guidelines & Best Practices

To ensure high performance, stability, and seamless host application execution, dynamic plugins should adhere to the following architectural design patterns:

1. Threading & WPF Dispatcher Integration

Hotkeys intercepted during runtime invoke the Execute() handler on a background listener hook thread. Direct modifications of UI elements or access to APIs requiring Single Thread Apartment (STA) state (such as the Windows Clipboard) will fail or throw cross-thread exceptions if performed directly on the hook thread.

Use the global WPF Dispatcher to marshal UI operations safely onto the main application thread:

// Execute UI or Clipboard operations safely from background hook thread
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
    // Write clipboard text safely
    System.Windows.Clipboard.SetText("Transformed Output Text");
});

2. Exception Handling & Host Protection

Unhandled exceptions originating inside a plugin's Execute(), SaveSettings(), or configuration controls can crash the main IsoKey host process. Always wrap execution logic inside structured try-catch blocks to capture faults gracefully.

try
{
    // Run your main execution logic here
}
catch (Exception ex)
{
    // Notify the user via the host status bar
    manager.UpdateStatus($"Plugin execution failed: {ex.Message}");
    System.Diagnostics.Debug.WriteLine($"Plugin Error: {ex}");
}

🐞 Debugging & Troubleshooting

To inspect values and step through your plugin code dynamically, you can configure your IDE to attach to the host application process.

1. Attaching Debugger in Visual Studio / Rider

  1. Open project properties and navigate to the Debug / Run Configuration settings.
  2. Set the Start Action to Start External Program.
  3. Browse and select the compiled IsoKey.exe binary executable from your local repository build folder.
  4. Set the Working Directory to the directory containing IsoKey.exe.
  5. Press F5 to build your library, copy the output DLL assembly to the Actions/ directory, launch the host application, and begin debugging.

2. Dynamic DLL File Locking

Warning: The .NET runtime locks dynamic assemblies when they are loaded into the application domain. Because the host loads your plugin on startup, you will not be able to rebuild the plugin DLL while IsoKey is running. You must close IsoKey before rebuilding.

To streamline development, you can add a pre-build command to automatically terminate any active IsoKey process instances before building your project:

taskkill /f /im IsoKey.exe 2>nul || exit 0

3. Method Invocation Lifecycle

  • On Host Launch: The host scans the Actions/ subdirectory, instantiates one singleton instance of your plugin class, and calls the global configuration loader (if IGlobalSettingsProvider is implemented).
  • On Action Configuration: When a user edits a mapping configuration for your action in Action Studio, the host invokes CreateConfigControl() to load the configuration panel view, and triggers SaveSettings() when the user clicks save.
  • On Key Press (Triggered): Whenever the mapped keystroke is pressed, the host dispatches the hook event to your Execute() handler.

📦 Compiling & Loading

Plugin libraries must target the same target framework as the host application, which is net9.0-windows10.0.17763.0, and must enable both WPF and Windows Forms support.

Sample Project Configuration (.csproj)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0-windows10.0.17763.0</TargetFramework>
    <UseWPF>true</UseWPF>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\KeyboardRemapper.csproj" />
  </ItemGroup>
</Project>

Distribution Pipeline

Compiled plugin DLLs must be placed directly inside the Actions/ directory relative to the IsoKey.exe executable directory. The application's automatic assembly loading engine handles mapping resolving without requiring manual registry operations.

📋 Example Implementations

1. Macro Recorder Plugin

Below is a skeletal implementation of the MacroRecorderPlugin demonstrating dynamic configuration injection and input playback under the Workspace Productivity category.

using System;
using System.Windows.Controls;
using KeyboardRemapper.Core;
using KeyboardRemapper.Models;

namespace KeyboardRemapper.Plugins
{
    public class MacroRecorderPlugin : IDynamicActionPlugin
    {
        public string PluginId => "MacroRecorder";
        public string DisplayName => "Macro Recorder";
        public string Description => "Record and playback mouse and key events.";
        public string Category => "Workspace Productivity";
        public string Color => "#8E24AA";
        public string Icon => "🎙️";

        public UserControl CreateConfigControl(KeyboardAction action, Action onDirty)
        {
            return new MacroRecorderConfigControl(action, onDirty);
        }

        public bool SaveSettings(KeyboardAction action)
        {
            return true;
        }

        public void Execute(KeyboardAction action, InputManager manager, int vk, int chainDepth)
        {
            // Execute playback using Win32 SendInput API...
        }
    }
}

2. Text Transformer Plugin

Below is a skeletal implementation of the standalone TextTransformerPlugin demonstrating global text manipulation via simulated clipboard copy-paste events under the Workspace Productivity category.

using System;
using System.Windows;
using System.Windows.Controls;
using KeyboardRemapper.Core;
using KeyboardRemapper.Models;

namespace KeyboardRemapper.Plugins
{
    public class TextTransformerPlugin : IDynamicActionPlugin
    {
        public string PluginId => "TextTransformer";
        public string DisplayName => "Text Transformer";
        public string Description => "Transform selected text dynamically with a hotkey.";
        public string Category => "Workspace Productivity";
        public string Color => "#00BCD4";
        public string Icon => "🔤";

        public UserControl CreateConfigControl(KeyboardAction action, Action onDirty)
        {
            return new TextTransformerConfigControl(action, onDirty);
        }

        public bool SaveSettings(KeyboardAction action)
        {
            return true;
        }

        public void Execute(KeyboardAction action, InputManager manager, int vk, int chainDepth)
        {
            // Must run in STA thread context to access clipboard
            Application.Current.Dispatcher.Invoke(() =>
            {
                // 1. Simulate Ctrl+C to copy current text selection
                // 2. Transform the text on the Clipboard
                // 3. Simulate Ctrl+V to paste the transformed text back
            });
        }
    }
}

3. Connection Manager Plugin

Below is a skeletal implementation of the ConnectionManagerPlugin demonstrating complex system network status changes, custom dynamic icon generation, and Win32 interop operations under the System Tools category.

using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Controls;
using KeyboardRemapper.Core;
using KeyboardRemapper.Models;

namespace KeyboardRemapper.Plugins
{
    public enum ConnectionMode
    {
        WiFiToggle,
        WiFiConnect,
        WiFiDisconnect,
        BluetoothToggle,
        BluetoothDeviceConnect,
        BluetoothDeviceDisconnect,
        AirplaneModeToggle,
        HotspotToggle,
        VpnConnect,
        VpnDisconnect,
        ProxyToggle,
        FlushDns,
        RenewIp
    }

    public class ConnectionConfigData
    {
        public ConnectionMode Mode { get; set; } = ConnectionMode.WiFiToggle;
        public string WiFiSsid { get; set; } = "";
        public string WiFiPassword { get; set; } = "";
        public string BluetoothDeviceAddress { get; set; } = "";
        public string BluetoothDeviceName { get; set; } = "";
        public string VpnName { get; set; } = "";
        public string ProxyServer { get; set; } = "";
    }

    public class ConnectionManagerPlugin : IDynamicActionPlugin
    {
        public string PluginId => "ConnectionManager";
        public string DisplayName => "Connection Manager";
        public string Description => "Control network connections: Wi-Fi, Bluetooth, Airplane Mode, VPN, and System Proxy.";
        public string Category => "System Tools";
        public string Color => "#FF5722";
        public string Icon => "\uE701"; // Default Wi-Fi Glyph

        public UserControl CreateConfigControl(KeyboardAction action, Action onDirty)
        {
            return new ConnectionManagerConfigControl(action, onDirty);
        }

        public bool SaveSettings(KeyboardAction action)
        {
            try
            {
                if (!string.IsNullOrEmpty(action.Arguments))
                {
                    var data = JsonSerializer.Deserialize<ConnectionConfigData>(action.Arguments);
                    if (data != null)
                    {
                        // Dynamically update custom Segoe MDL2 icon glyph based on selected mode
                        string iconGlyph = data.Mode switch
                        {
                            ConnectionMode.WiFiToggle or ConnectionMode.WiFiConnect or ConnectionMode.WiFiDisconnect => "\uE701",
                            ConnectionMode.BluetoothToggle or ConnectionMode.BluetoothDeviceConnect or ConnectionMode.BluetoothDeviceDisconnect => "\uE702",
                            ConnectionMode.AirplaneModeToggle => "\uE709",
                            ConnectionMode.HotspotToggle => "\uE704",
                            ConnectionMode.VpnConnect or ConnectionMode.VpnDisconnect => "\uE727",
                            ConnectionMode.ProxyToggle => "\uF13D",
                            ConnectionMode.FlushDns or ConnectionMode.RenewIp => "\uE756",
                            _ => "\uE701"
                        };
                        action.CustomIconPath = "Glyph:" + iconGlyph;
                    }
                }
            }
            catch { }
            return true;
        }

        public void Execute(KeyboardAction action, InputManager manager, int vk, int chainDepth)
        {
            // Parse arguments and asynchronously trigger target network/device operations
            Task.Run(async () =>
            {
                try
                {
                    var config = JsonSerializer.Deserialize<ConnectionConfigData>(action.Arguments) ?? new ConnectionConfigData();
                    switch (config.Mode)
                    {
                        case ConnectionMode.WiFiToggle:
                            // Toggle Wi-Fi radio state...
                            break;
                        case ConnectionMode.BluetoothDeviceConnect:
                            // Connect paired Bluetooth device via Windows.Devices.Bluetooth API...
                            break;
                        // Additional network/system control handlers...
                    }
                }
                catch (Exception ex)
                {
                    manager.UpdateStatus($"Connection Manager failed: {ex.Message}");
                }
            });
        }
    }
}
Web API & Telemetry

Overlay Studio SDK

Build real-time custom overlay cards, custom hardware widgets, gauges, and audio mixers inside the Web Deck wrapper.

🖼️ Overview & Setup

Custom Script elements allow you to inject vanilla JavaScript directly into your Overlay Panel. You can write custom UI layouts, construct canvas graphics, and synchronize them in real time with hardware monitoring statistics (telemetry) via clean, sandboxed lifecycle hooks.

💡 Quick Start

Create a JavaScript file under Scripts/CustomElements/, define an ES6 class, register it to the SDK via window.CustomElementRegistry.register('ClassName', ClassRef), and point your CustomScript overlay element to it in the Overlay Studio.

⚙️ Custom Widget Settings on the Element Properties Tab

IsoKey supports a powerful dynamic settings system. Developers can expose custom, configurable parameters for their custom scripts directly in the Overlay Studio's Element Properties tab. By adding special comment-based metadata directives at the top of a JavaScript widget file, the host designer automatically reads the file and builds visual editing controls (like checkboxes, text boxes, and interactive color pickers) inside the element properties sidebar for the user to configure.

🎨 How It Appears in Overlay Studio

When you select your custom widget in the Overlay Studio canvas, the designer inspects the top of your JS script file. If it finds any @setting annotations, it dynamically generates custom controls under the Widget Custom Settings header in the Element Properties tab on the right sidebar. Any changes made to these controls are saved to the active panel profile and instantly updated on the designer preview.

Declare each custom setting at the top of your JavaScript file using the following metadata comment format:

// @setting: [key]|[type]|[label]|[defaultValue]

Parameter Breakdown:

  • key: The variable key used to access this setting in the style object passed to onCreate(container, style) (e.g. showControls).
  • type: The editor control type to generate. Supported types:
    • boolean or bool: Generates a CheckBox.
    • color: Generates a color preview block and button that launches the top-level, DPI-aware **IsoColorPickerWindow** (supporting real-time canvas preview updates and automatic cancel-to-revert actions if dismissed).
    • choice[opt1,opt2,...] or select[opt1,opt2,...]: Generates a dropdown ComboBox featuring the listed choices, automatically capitalized for user display (e.g. choice[vinyl,compact,retro]).
    • text or string or number: Generates a standard TextBox.
  • label: The user-friendly label displayed in the Element Properties sidebar (e.g. Show Controls).
  • defaultValue (optional): The default value assigned if the user hasn't explicitly set one.

Example Script Declaration:

// @setting: accentColor|color|Accent Color|#00ffcc
// @setting: showLocation|boolean|Show Location Name|true
// @setting: customText|text|Custom Title|WEATHER SPOT

class WeatherWidget {
    onCreate(container, style) {
        // Retrieve the user settings from the style parameter:
        const accent = style.accentColor || '#00ffcc';
        const showLoc = style.showLocation !== false && style.showLocation !== 'false';
        const title = style.customText || 'WEATHER SPOT';
        
        // Build your widget UI using these dynamic settings...
    }
}

🔄 Lifecycle Hooks

Custom scripts must implement a standard ES6 class structure. The SDK automatically calls these lifecycle methods during runtime:

1. onCreate(container, style)

Called when the element is instantiated in the Web Deck. Use this to construct your DOM nodes, set up canvas contexts, and bind initial layouts.

  • container: The shadow-root host DOM element. Draw all visual nodes inside this wrapper.
  • style: A raw CSS config string passed from the overlay panel settings.

2. onStatsUpdate(container, stats)

Called automatically when a new hardware stats or telemetry packet is received via the WebSocket server connection (typically every 1 second).

  • container: The host wrapper DOM element.
  • stats: A parsed JSON packet containing active hardware statistics.

3. onDestroy(container)

Called when swapping active overlays or closing the dashboard layout. Use this to cancel animation loops, clear interval timers, and prevent memory leaks.

📊 Telemetry Payload

The stats parameter inside onStatsUpdate contains the following real-time data attributes:

Metric Type Description
Cpu number CPU utilization percentage (0 - 100).
CpuTemp number CPU package temperature in °C.
Gpu number GPU core utilization percentage (0 - 100).
GpuTemp number GPU core temperature in °C.
Ram number Total RAM usage percentage (0 - 100).
TotalRamGb number Total physically available RAM capacity in GB.
UsedRamGb number Currently consumed physical RAM in GB.
Disk number Active disk queue length / access percentage.
NetDown number Network download bandwidth consumption in KB/s.
NetUp number Network upload bandwidth consumption in KB/s.
Ping number Network latency time to host probe in milliseconds.
Volume number Master audio level output percentage (0 - 100). Negative value represents muted.
BatteryPercent number Device battery charge level percentage (0 - 100).
IsBatteryCharging boolean True if active battery is connected to a power supply.
MediaTitle string Name of the active audio/video file currently playing.
MediaArtist string Artist / source of the active playing media session.
MediaStatus number Playback status enum (e.g., 4 represents playing, 0 is stopped/idle).
MediaAppPath string Local executable path of the application currently playing media.
MediaPosition number Current playback position in seconds (e.g. 142.5) at the timestamp specified by MediaLastUpdated.
MediaDuration number Total track duration in seconds (e.g. 240.0).
MediaLastUpdated number Unix epoch milliseconds when the media position snapshot was taken by the player. Used for high-precision playhead updates.

🔗 Bidirectional API Reference

Custom scripts can interactively trigger remap operations, launch applications, or toggle audio sliders by utilizing the native window.IsoKeyAPI helper namespace:

Method Arguments Description
window.IsoKeyAPI.executeAction(actionId) actionId: string (Guid) Sends a trigger command back to the C# remap engine to execute an action (e.g. standard remap keys, volume controls, mute states, media play/pause, app launcher).
window.IsoKeyAPI.updateSlider(elementId, value) elementId: string, value: number Broadcasts a slider adjustment value to align other remote dashboards and trigger systemic changes (e.g. brightness or volume adjustments).
window.IsoKeyAPI.getLayout() None Returns a complete read-only JSON object representing the layout configuration including all dynamic elements and registered action mappings.

🖼️ Local HTTP Media & Asset APIs

Custom HTML elements can fetch local dynamic media files and asset icons from the loopback web server using the following HTTP API endpoints:

Endpoint Parameters Description
/api/media-thumbnail None Serves the active Windows SMTC cover art image bytes (PNG or JPEG). Returns 404 if no artwork is available.
/api/icon path: string (URL Encoded), index: number (Optional) Serves a PNG icon extracted from a running process or executable file.

📐 Sizing & Responsive Widget Scaling

To ensure custom script elements look consistent across multiple remote device resolutions (e.g. mobile screens, tablets) and the XAML overlay editor canvas, the SDK implements a unified size-independent scaling engine.

1. Always Use Relative Units (em)

Avoid absolute sizing units like px for layouts, border-radii, padding, and text. Instead, use relative em units. The SDK dynamically controls the container's base font-size depending on the element's actual dimensions:

  • The container's base font size automatically adjusts relative to the smallest dimension using min(width, height).
  • In the HTML remote web deck, the base font size is synced via ResizeObserver at 5% of the smallest dimension (capped at a 6px minimum).
  • In the XAML studio preview control, a 2.5x layout scale compensation is automatically applied to guarantee that zoomXaml: 1.0 matches the remote HTML scale perfectly.

2. High-DPI Canvas Sizing

If utilizing an HTML5 <canvas> element for graphics (e.g. analog gauges, battery rings), clear pixelation on high-DPI retina mobile screens by adjusting the canvas rendering resolution using window.devicePixelRatio:

// Inside draw/render tick:
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
const w = rect.width;
const h = rect.height;

if (canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr)) {
    canvas.width = Math.round(w * dpr);
    canvas.height = Math.round(h * dpr);
}

ctx.save();
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, w, h);

// Draw your elements using base client sizes (w, h) and relative radius scale...
ctx.restore();

📝 Digital Clock Example

Below is a complete class template for creating a sweeping Digital Clock showing real-time CPU stats:

class DigitalClock {
    onCreate(container, style) {
        // Initialize visual DOM nodes
        const wrapper = document.createElement('div');
        wrapper.style.cssText = 'width:100%; height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; color:#00ffcc;';
        
        const timeEl = document.createElement('div');
        timeEl.style.cssText = 'font-size:2.4em; font-weight:bold; text-shadow:0 0 0.3em rgba(0,255,204,0.5);';
        wrapper.appendChild(timeEl);
        
        const subEl = document.createElement('div');
        subEl.style.cssText = 'font-size:1.1em; margin-top:0.4em; opacity:0.7;';
        subEl.innerText = 'CPU Load: --%';
        wrapper.appendChild(subEl);
        
        container.appendChild(wrapper);
        
        this.timeEl = timeEl;
        this.subEl = subEl;
        
        // Sweep clock update ticks
        const updateTime = () => {
            const d = new Date();
            this.timeEl.innerText = d.toTimeString().split(' ')[0];
        };
        updateTime();
        this.timer = setInterval(updateTime, 1000);
    }
    
    onStatsUpdate(container, stats) {
        // Update telemetry data elements
        if (this.subEl && stats.Cpu !== undefined) {
            this.subEl.innerText = `CPU Load: ${Math.round(stats.Cpu)}%`;
        }
    }
    
    onDestroy(container) {
        // Prevent memory leaks
        clearInterval(this.timer);
    }
}

// Register the class with the IsoKey SDK
window.CustomElementRegistry.register('DigitalClock', DigitalClock);
External Integrations & Addons

Addon Integrations

Connect your keyboard overlays and custom script widgets with third-party software, including high-performance Discord Voice controls and OBS Studio production broadcast switches.

🎙️ Discord Voice IPC Bridge

The Web Deck includes robust Discord integration which connects directly to the local Discord client via high-performance Named Pipes (discord-ipc-0), avoiding typical origin-policy and CORS constraints of web browser pages.

Named Pipe IPC Architecture

Discord Voice integration uses a high-performance Named Pipe transport to bypass browser origin security restrictions. On first run, it initiates an OAuth2 token handshake requesting scopes rpc, rpc.voice.read, and rpc.voice.write. The authenticated credential is saved inside discord_token.json in the application root directory.

Outgoing API Methods

Method Arguments Description
window.IsoKeyAPI.discordToggleMute() None Toggles local microphone mute in Discord via Local Named Pipe bridge. Needs rpc.voice.write scope.
window.IsoKeyAPI.discordToggleDeafen() None Toggles local deafen state in Discord. Needs rpc.voice.write scope.
window.IsoKeyAPI.discordSetInputVolume(volume) volume: number (0 - 100) Sets local microphone input volume in Discord.
window.IsoKeyAPI.discordSetOutputVolume(volume) volume: number (0 - 200) Sets local speaker output volume in Discord.
window.IsoKeyAPI.discordSetUserVolume(userId, volume) userId: string, volume: number (0 - 200) Adjusts the output volume for a specific user in the current voice channel.
window.IsoKeyAPI.discordLeaveChannel() None Leaves the currently active voice channel.
window.IsoKeyAPI.discordSetActivity(details, state, largeImgKey, largeText) strings Sets the Discord client rich presence activity.
window.IsoKeyAPI.discordToggleScreenshare(keybindOrPid) keybindOrPid: string / number Toggles client screensharing. If a process PID is supplied, shares that specific window.
window.IsoKeyAPI.discordToggleVideo() None Toggles local camera video status in the voice channel.
window.IsoKeyAPI.discordGetRunningProcesses() None Triggers an asynchronous query for running desktop processes (used to select windows for screensharing).
window.IsoKeyAPI.discordSetVoiceSettings(settings) settings: object Configures client voice parameter overrides (e.g. echo cancellation, noise suppression).

Real-Time Discord Telemetry Properties

Stats update broadcasts (onStatsUpdate) populate the following Discord voice telemetry variables:

  • DiscordConnected (boolean) - Connection health status to the local Discord client.
  • DiscordMuted (boolean) - Active microphone mute status.
  • DiscordDeafened (boolean) - Active client deafen (speaker mute) status.
  • DiscordActiveSpeakers (string[]) - Array of nicknames/usernames currently speaking.
  • DiscordChannelName (string) - Name of the currently joined voice channel.
  • DiscordGuildName (string) - Name of the server guild for the voice channel.
  • DiscordVoiceUsers (array) - Array of voice channel user objects.

📡 OBS Studio Control

Control and monitor OBS Studio in real time from custom overlay elements.

Outgoing API Methods

Method Arguments Description
window.IsoKeyAPI.obsSetScene(sceneName) sceneName: string Changes the active program scene in OBS.
window.IsoKeyAPI.obsToggleMute(inputName) inputName: string Toggles mute state for the specified audio input.
window.IsoKeyAPI.obsSetVolume(inputName, volume) inputName: string, volume: float (0.0 - 1.0) Sets the volume level multiplier for the specified input.
window.IsoKeyAPI.obsToggleStream() None Toggles the live stream status.
window.IsoKeyAPI.obsToggleRecord() None Toggles the video recording status.
window.IsoKeyAPI.obsSetSceneItemEnabled(sceneName, itemId, enabled) strings / boolean Sets scene item visibility state in the specified scene.
window.IsoKeyAPI.obsSetProfile(profileName) profileName: string Switches the active user profile in OBS Studio.
window.IsoKeyAPI.obsSetSceneCollection(collectionName) collectionName: string Switches the active scene collection in OBS Studio.
window.IsoKeyAPI.obsToggleVirtualCam() None Toggles the OBS virtual camera status.
window.IsoKeyAPI.obsToggleStudioMode() None Toggles OBS Studio Mode (splits the display into preview and program scenes).

Real-Time OBS Telemetry Properties

Stats update broadcasts (onStatsUpdate) populate these real-time OBS telemetry variables:

  • ObsConnected (boolean) - Connection status to OBS Studio.
  • ObsCurrentScene (string) - The active program scene.
  • ObsIsStreaming (boolean) - Stream active flag.
  • ObsIsRecording (boolean) - Record active flag.
  • ObsMutedInputs (object) - Key-value pair of input name to mute state (boolean).
  • ObsInputVolumes (object) - Key-value pair of input name to volume multiplier (number).

🔊 Audio Mixer Scene Transition Behavior

When the active program scene changes, ObsInputVolumes and ObsMutedInputs are not cleared. The backend retains existing audio data while asynchronously re-querying volumes and mute states for the new scene's inputs, special/global inputs, and the full input list. This prevents the UI from briefly showing an empty mixer during scene transitions. The OBS controller widget includes a 3-second grace window that keeps the existing mixer faders visible while new audio data loads.