FlexDesigner SDK

A comprehensive SDK for developing plugins for FlexDesigner, providing seamless integration with Flexbar devices and rich interaction capabilities.

Installation

Prerequisites

  • Node.js version 20 or higher

  • FlexDesigner version 1.0.0 or higher.

Setup

Add FlexDesigner SDK to your project

In typical cases, you only need to create a plugin project using flexcli, and the FlexDesigner SDK will be automatically installed.

npm install @eniactech/flexdesigner-sdk

Quick Start

const { plugin, logger, pluginPath, resourcesPath } = require("@eniactech/flexdesigner-sdk")

// Start the plugin
plugin.start()

// Handle plugin events
plugin.on('plugin.alive', (payload) => {
    logger.info('Plugin loaded:', payload)
})

plugin.on('plugin.data', (payload) => {
    logger.info('User interaction:', payload)
    return 'Response from plugin backend!'
})

Core Concepts

Plugin Lifecycle

  1. Initialization: Plugin starts and connects to FlexDesigner

  2. Alive Event: Triggered when keys are loaded and ready

  3. Data Events: Triggered when users interact with keys

  4. Configuration: Plugin can read/write configuration data

Key Types

  • Standard Keys: Basic button functionality

  • Multi-State Keys: Cycle through different states

  • Slider Keys: Continuous value adjustment

  • Dynamic Keys: Dynamically managed key collections

  • Wheel Keys: Rotary encoder support

API Reference

Built-in Modules

To simplify the plugin development process, we have pre-integrated several modules that can be used directly:

  1. @napi-rs/canvas

  2. express

  3. axios

If you need integration of other modules, please let us know

Core Plugin APIs

plugin.start()

Starts the WebSocket connection and initializes the plugin.

plugin.start()

plugin.on(event, handler)

Registers an event handler for specific events.

Parameters:

  • event (string): Event type

  • handler (function): Event handler function

plugin.on('plugin.data', (payload) => {
    const { data, serialNumber } = payload
    logger.info('Key pressed:', data.key.title)
    return { status: 'success', message: 'Key handled!' }
})

plugin.off(event)

Unregisters an event handler.

plugin.off('plugin.data')

Drawing and Visual Updates

plugin.draw(serialNumber, key, type?, base64?)

Updates the visual appearance of a key.

Parameters:

  • serialNumber (string): Device serial number

  • key (object): Key object from events

  • type (string): ‘draw’ (default) or ‘base64’

  • base64 (string): Base64 image data (when type is ‘base64’)

// Update key based on key.style properties
plugin.draw(serialNumber, key, 'draw')

// Draw custom image
const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
plugin.draw(serialNumber, key, 'base64', base64Image)

Key State Management

plugin.setMultiState(serialNumber, key, state, message?)

Sets the state for multi-state keys.

Parameters:

  • serialNumber (string): Device serial number

  • key (object): Key object

  • state (number): Target state index

  • message (string, optional): Display message

plugin.setMultiState(serialNumber, key, 2, 'State Changed')

plugin.setSlider(serialNumber, key, value)

Sets the value for slider keys.

Parameters:

  • serialNumber (string): Device serial number

  • key (object): Key object

  • value (number): Slider value

plugin.setSlider(serialNumber, key, 75)

Device Configuration & Control

plugin.sendControlCommand(serialNumber, command)

Sends control commands to the device

Parameters:

  • serialNumber (string): Device serial number

  • command (string): The command to send. One of the following:

    • “sys.sleep”: Put the device to sleep

    • “sys.wake”: Wake up the device

    • “hapic.click”: Trigger a click vibration

plugin.sendControlCommand(serialNumber, 'sys.sleep') // Put the device to sleep
plugin.sendControlCommand(serialNumber, 'sys.wake') // Wake up the device
plugin.sendControlCommand(serialNumber, 'hapic.click') // Trigger a click vibration

plugin.setDeviceConfig(serialNumber, config)

Configures device settings.

Parameters:

  • serialNumber (string): Device serial number

  • config (object): Configuration object

plugin.setDeviceConfig(serialNumber, {
    sleepTime: 1000,
    brightness: 100,
    screenFlip: true,
    vibrate: 'full',
    autoSleep: true,
    deviceName: 'My Flexbar',
    cdcMode: true,
    color: 'space black'
})

Messaging and Notifications

plugin.showFlexbarSnackbarMessage(serialNumber, msg, level, icon?, timeout?, waitUser?)

Displays a message on the Flexbar device.

Parameters:

  • serialNumber (string): Device serial number

  • msg (string): Message content (max 64 characters)

  • level (string): Message level (‘info’, ‘warning’, ‘error’, ‘success’)

  • icon (string, optional): Icon name

  • timeout (number, optional): Duration in ms (500-10000, default: 2000)

  • waitUser (boolean, optional): Wait for user interaction

plugin.showFlexbarSnackbarMessage(
    serialNumber, 
    'Hello from plugin!', 
    'info', 
    'bell', 
    3000
)

plugin.showSnackbarMessage(color, message, timeout?)

Shows a message in the FlexDesigner application.

plugin.showSnackbarMessage('success', 'Operation completed!', 3000)

System Integration

plugin.electronAPI(api, …args)

Calls Electron APIs for system integration.

Supported APIs:

  • dialog.showOpenDialog

  • dialog.showSaveDialog

  • dialog.showMessageBox

  • dialog.showErrorBox

  • app.getAppPath

  • app.getPath

  • screen.getCursorScreenPoint

  • screen.getPrimaryDisplay

  • screen.getAllDisplays

// Show file dialog
const result = await plugin.electronAPI('dialog.showOpenDialog', {
    properties: ['openFile'],
    filters: [{ name: 'Images', extensions: ['png', 'jpg'] }]
})

// Get cursor position
const cursorPos = await plugin.electronAPI('screen.getCursorScreenPoint')

File Operations

plugin.openFile(path)

Reads a file from the filesystem.

const content = await plugin.openFile('/path/to/file.txt')

plugin.saveFile(path, data)

Saves data to a file.

await plugin.saveFile('/path/to/output.txt', 'Hello World!')

Application Information

plugin.getAppInfo()

Gets FlexDesigner application information.

const appInfo = await plugin.getAppInfo()
// Returns: { version: "v1.0.0", platform: "win32" }

plugin.getOpenedWindows()

Gets list of opened windows on the system.

const windows = await plugin.getOpenedWindows()
windows.forEach(win => {
    console.log(`Window: ${win.title} (${win.bounds.width}x${win.bounds.height})`)
})

plugin.getDeviceStatus()

Gets status of connected devices.

const devices = await plugin.getDeviceStatus()
devices.forEach(device => {
    console.log(`Device: ${device.serialNumber}, Status: ${device.status}`)
})

Configuration Management

plugin.getConfig()

Retrieves plugin configuration.

const config = await plugin.getConfig()
console.log('Current config:', config)

plugin.setConfig(config)

Updates plugin configuration.

await plugin.setConfig({ theme: 'dark', autoUpdate: true })

Performance Monitoring

plugin.sendChartData(chartDataArray)

Sends performance metrics for display in FlexDesigner.

Parameters:

  • chartDataArray (array): Array of chart data objects

plugin.sendChartData([
    {
        label: 'CPU Usage',
        value: 45.2,
        unit: '%',
        baseUnit: '%',
        baseVal: 45.2,
        maxLen: 2,
        category: 'system',
        key: 'cpu'
    },
    {
        label: 'Memory',
        value: 2.1,
        unit: 'GB',
        baseUnit: 'MB',
        baseVal: 2048,
        maxLen: 3,
        category: 'system',
        key: 'memory'
    }
])

Shortcuts Management

plugin.updateShortcuts(shortcuts)

Registers or unregisters keyboard shortcuts.

Parameters:

  • shortcuts (array): Array of shortcut objects

plugin.updateShortcuts([
    {
        shortcut: 'CommandOrControl+F1',
        action: 'register'
    },
    {
        shortcut: 'CommandOrControl+F2',
        action: 'unregister'
    }
])

Dynamic Key Management

The plugin.dynamickey object provides advanced key management capabilities.

plugin.dynamickey.clear(serialNumber, key)

Removes all dynamic keys from a container.

plugin.dynamickey.clear(serialNumber, key)

plugin.dynamickey.add(serialNumber, key, index, backgroundType, backgroundData, width, userData)

Adds a new dynamic key.

Parameters:

  • index (number): Position to insert the key

  • backgroundType (string): ‘base64’ or ‘draw’

  • backgroundData (string): Image data or key object

  • width (number): Key width in pixels (60-1000)

  • userData (object): Custom data associated with the key

plugin.dynamickey.add(
    serialNumber, 
    key, 
    0, 
    'base64', 
    'data:image/png;base64,iVBORw0...', 
    200, 
    { name: 'Dynamic Key 1', action: 'custom' }
)

plugin.dynamickey.remove(serialNumber, key, index)

Removes a dynamic key at the specified index.

plugin.dynamickey.remove(serialNumber, key, 2)

plugin.dynamickey.move(serialNumber, key, srcIndex, dstIndex)

Moves a dynamic key from one position to another.

plugin.dynamickey.move(serialNumber, key, 0, 3)

plugin.dynamickey.setWidth(serialNumber, key, width)

Changes the width of the dynamic key container.

plugin.dynamickey.setWidth(serialNumber, key, 800)

plugin.dynamickey.draw(serialNumber, key, index, backgroundType, backgroundData, width)

Updates the visual appearance of a specific dynamic key.

plugin.dynamickey.draw(
    serialNumber, 
    key, 
    1, 
    'base64', 
    'data:image/png;base64,iVBORw0...', 
    200
)

plugin.dynamickey.update(serialNumber, key, index, userData)

Updates the user data for a dynamic key.

plugin.dynamickey.update(serialNumber, key, 0, { status: 'updated' })

plugin.dynamickey.refresh(serialNumber, key)

Refreshes the dynamic key display (recommended after width changes).

plugin.dynamickey.refresh(serialNumber, key)

Event Handling

Plugin Events

‘plugin.alive’

Triggered when keys are loaded and ready for interaction.

plugin.on('plugin.alive', (payload) => {
    const { serialNumber, keys } = payload
  
    keys.forEach(key => {
        console.log(`Key loaded: ${key.cid} at position ${key.uid}`)
  
        // Initialize key based on its type
        if (key.cid === 'com.example.counter') {
            key.style.showTitle = true
            key.title = '0'
            plugin.draw(serialNumber, key, 'draw')
        }
    })
})

‘plugin.dead’

Triggered when plugin keys are destroyed

plugin.on('plugin.dead', (payload) => {
    const { serialNumber, keys } = payload
  
    keys.forEach(key => {
        console.log(`Key destoried: ${key.cid}`)
    })
})

‘plugin.data’

Triggered when users interact with keys.

plugin.on('plugin.data', (payload) => {
    const { serialNumber, data } = payload
    const key = data.key
  
    if (key.cid === 'com.example.button') {
        console.log('Button pressed!')
        return { status: 'success', message: 'Button handled' }
    }
  
    if (key.cid === 'com.example.slider') {
        console.log(`Slider value: ${data.value}`)
    }
})

‘plugin.config.updated’

Triggered when plugin configuration changes.

plugin.on('plugin.config.updated', (payload) => {
    console.log('Configuration updated:', payload.config)
})

System Events

‘system.shortcut’

Triggered when registered shortcuts are pressed.

plugin.on('system.shortcut', (payload) => {
    console.log(`Shortcut pressed: ${payload.shortcut}`)
})

‘system.actwin’

Triggered when the active window changes.

plugin.on('system.actwin', (payload) => {
    const { oldWin, newWin } = payload
    console.log(`Window changed: ${oldWin.title} -> ${newWin.title}`)
})

Device Events

‘device.status’

Triggered when device connection status changes.

plugin.on('device.status', (devices) => {
    devices.forEach(device => {
        if (device.status === 'connected') {
            console.log(`Device connected: ${device.serialNumber}`)
            // Configure the newly connected device
            plugin.setDeviceConfig(device.serialNumber, {
                brightness: 80,
                deviceName: 'My Plugin Device'
            })
        }
    })
})

UI Events

‘ui.message’

Triggered when receiving messages from the plugin’s UI.

plugin.on('ui.message', async (payload) => {
    console.log('Message from UI:', payload)
  
    if (payload.action === 'test') {
        // Perform test operations
        await testAPIs()
        return 'Test completed!'
    }
  
    return 'Message received!'
})

‘ui.log’

Handles log messages from the plugin’s UI (automatically handled).

Utilities and Helpers

Logger

The SDK provides a logger instance for debugging and monitoring.

const { logger } = require("@eniactech/flexdesigner-sdk")

logger.info('Information message')
logger.warn('Warning message')
logger.error('Error message')
logger.debug('Debug message')

Path Utilities

Access to plugin-specific paths.

const { pluginPath, resourcesPath } = require("@eniactech/flexdesigner-sdk")

console.log('Plugin directory:', pluginPath)
console.log('Resources directory:', resourcesPath)

Complete Example

Here’s a comprehensive example demonstrating various SDK features:

const { plugin, logger, pluginPath } = require("@eniactech/flexdesigner-sdk")
const { createCanvas } = require('@napi-rs/canvas')

// Store key data
const keyData = {}

// Plugin lifecycle
plugin.start()

// Handle key loading
plugin.on('plugin.alive', (payload) => {
    const { serialNumber, keys } = payload
  
    keys.forEach(key => {
        keyData[key.uid] = key
  
        switch (key.cid) {
            case 'com.example.counter':
                // Initialize counter
                keyData[key.uid].counter = 0
                key.style.showTitle = true
                key.title = 'Click Me!'
                plugin.draw(serialNumber, key, 'draw')
                break
          
            case 'com.example.slider':
                // Set initial slider value
                plugin.setSlider(serialNumber, key, 50)
                break
          
            case 'com.example.dynamic':
                // Setup dynamic keys
                setupDynamicKeys(serialNumber, key)
                break
        }
    })
})

// Handle user interactions
plugin.on('plugin.data', (payload) => {
    const { serialNumber, data } = payload
    const key = data.key
  
    switch (key.cid) {
        case 'com.example.counter':
            // Increment counter
            keyData[key.uid].counter++
            key.title = `${keyData[key.uid].counter}`
            plugin.draw(serialNumber, key, 'draw')
            break
      
        case 'com.example.wheel':
            // Handle wheel rotation
            showWheelFeedback(serialNumber, data.delta)
            break
    }
})

// Handle device connections
plugin.on('device.status', (devices) => {
    devices.forEach(device => {
        if (device.status === 'connected') {
            logger.info(`Device connected: ${device.serialNumber}`)
      
            // Configure device
            plugin.setDeviceConfig(device.serialNumber, {
                brightness: 100,
                deviceName: 'SDK Example Device',
                autoSleep: true
            })
        }
    })
})

// Helper function for dynamic keys
function setupDynamicKeys(serialNumber, key) {
    plugin.dynamickey.clear(serialNumber, key)
  
    // Add multiple dynamic keys
    for (let i = 0; i < 3; i++) {
        const canvas = createCanvas(200, 60)
        const ctx = canvas.getContext('2d')
  
        // Draw custom background
        ctx.fillStyle = `hsl(${i * 120}, 70%, 50%)`
        ctx.fillRect(0, 0, 200, 60)
  
        ctx.fillStyle = 'white'
        ctx.font = '20px Arial'
        ctx.textAlign = 'center'
        ctx.fillText(`Key ${i}`, 100, 35)
  
        const imageData = canvas.toDataURL()
  
        plugin.dynamickey.add(
            serialNumber,
            key,
            i,
            'base64',
            imageData,
            200,
            { id: i, name: `Dynamic Key ${i}` }
        )
    }
}

// Performance monitoring
setInterval(() => {
    const memUsage = process.memoryUsage()
  
    plugin.sendChartData([
        {
            label: 'Memory Usage',
            value: memUsage.heapUsed / 1024 / 1024,
            unit: 'MB',
            baseUnit: 'bytes',
            baseVal: memUsage.heapUsed,
            maxLen: 3,
            category: 'system',
            key: 'memory'
        }
    ])
}, 5000)

// Register shortcuts
setTimeout(() => {
    plugin.updateShortcuts([
        {
            shortcut: 'CommandOrControl+Shift+F1',
            action: 'register'
        }
    ])
}, 1000)

logger.info('Plugin example started successfully!')

Best Practices

Performance

  • Use plugin.dynamickey.refresh() after width changes

  • Batch multiple dynamic key operations when possible

  • Optimize image sizes for better performance

Error Handling

  • Always wrap async operations in try-catch blocks

  • Validate key types before calling type-specific methods

  • Handle device disconnection gracefully

User Experience

  • Provide visual feedback for user interactions

  • Use appropriate message levels for notifications

  • Keep snackbar messages concise and informative

Development

  • Use the logger for debugging instead of console.log

  • Test with multiple device connections

  • Validate configuration before applying changes

Troubleshooting

Common Issues

  1. Plugin not starting: Ensure correct command-line arguments are provided

  2. Keys not updating: Check if the correct serial number is used

  3. Dynamic keys not displaying: Call refresh() after width changes

  4. Events not firing: Verify event handler registration

Debugging

Enable debug logging by setting the appropriate log level:

logger.debug('Debug information')

Monitor WebSocket connections and ensure the plugin can communicate with FlexDesigner.

License

This SDK is provided under the terms specified by EniacTech. Please refer to your license agreement for usage terms and conditions.