### Example: Get or Create User ID Setting Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Managing-Plugin-Settings.md This example demonstrates how to retrieve a 'UserId' setting, or generate and save a new one if it doesn't exist. It uses `TryGetPluginSetting` and `SetPluginSetting`. ```csharp private String GetUserId () { const String SettingName = "UserId" ; // first try to get existing user ID if ( this . TryGetPluginSetting ( SettingName , out var existingUserId )) { return existingUserId ; } // if it does not exist, generate a new one and save it var newUserId = Guid . NewGuid (). ToString ( "N" ); this . SetPluginSetting ( SettingName , newUserId , false ); return newUserId ; } ``` -------------------------------- ### Install Plugin Locally Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md Installs the HapticWeb plugin to the local environment. ```bash logiplugintool install ./HapticWeb.lplug4 ``` -------------------------------- ### Install LogiPluginTool Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Getting-started.md Installs the LogiPluginTool globally as a .NET tool. This is the first step to managing your plugin projects. ```bash dotnet tool install --global LogiPluginTool ``` -------------------------------- ### Plugin Account Handling Example Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/External-Service-Login.md This C# example demonstrates how to create and manage a plugin account preference, subscribe to login/logout events, and report authentication status to an external service. ```csharp public class MyPlugin : Plugin { private readonly PluginPreferenceAccount _myAccount ; public MyPlugin () { // Create an account preference this . _myAccount = new PluginPreferenceAccount ( "my-account" ) { DisplayName = "My account" , IsRequired = true , LoginUrlTitle = "Sign in" , LogoutUrlTitle = "Sign out" }; // Add the preference to the list this . PluginPreferences . Add ( this . _myAccount ); } public override void Load () { // Subscribe to login/logout requests this . _myAccount . LoginRequested += this . OnMyAccountLoginRequested ; this . _myAccount . LogoutRequested += this . OnMyAccountLogoutRequested ; } public override void Unload () { // Unsubscribe from login/logout requests this . _myAccount . LoginRequested -= this . OnMyAccountLoginRequested ; this . _myAccount . LogoutRequested -= this . OnMyAccountLogoutRequested ; } private void OnMyAccountLoginRequested ( Object sender , EventArgs e ) { // Login to external service to get access token and refresh token (if it exists) // ... // Set user name, access token and refresh token this . _myAccount . ReportLogin ( "ExternalServiceUserName" , "ExternalServiceAccessToken" , "ExternalServiceRefreshToken" ); } private void OnMyAccountLogoutRequested ( Object sender , EventArgs e ) { // Logout from external service // ... // Clear user name, access token and refresh token this . _myAccount . ReportLogout (); } } ``` -------------------------------- ### Example LoupedeckPackage.yaml for Spotify Premium Plugin Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-structure.md A complete example of a `LoupedeckPackage.yaml` file for a Spotify Premium plugin. It demonstrates the usage of various fields, including color configurations, supported devices, binary folder paths for Windows and Mac, and URLs for licensing and support. ```yaml type : plugin4 name : SpotifyPremium displayName : Spotify Premium version : 1.0 author : Logitech copyright : Logitech backgroundColor : 4278869247 foregroundColor : 4294967295 textColor : 4294967295 supportedDevices : - LoupedeckCt - LoupedeckLive pluginFileName : SpotifyPremiumPlugin.dll pluginFolderWin : bin/win/ pluginFolderMac : bin/mac/ license : MIT licenseUrl : https://opensource.org/licenses/MIT homePageUrl : https://logitech.com supportPageUrl : https://support.logitech.com/f-a-q-support ``` -------------------------------- ### Check Logi Plugin Tool Installation Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/haptics-tutorial.md Verifies the Logi Plugin Tool installation by displaying its help information. ```bash LogiPluginTool --help ``` -------------------------------- ### Get Plugin Data Directory and Ensure Existence Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Storing-Plugin-Data.md Use `Plugin.GetPluginDataDirectory()` to get the path and `IoHelpers.EnsureDirectoryExists()` to create the directory if it doesn't exist before writing data. ```csharp var pluginDataDirectory = this . GetPluginDataDirectory (); if ( IoHelpers . EnsureDirectoryExists ( pluginDataDirectory )) { var filePath = Path . Combine ( pluginDataDirectory , "MyData.bin" ); using ( var streamWriter = new StreamWriter ( filePath )) { // Write data } } ``` -------------------------------- ### Get and Read Image Resources Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md After initializing PluginResources, you can use FindFile to get the resource name and ReadImage to load the image into a BitmapImage object. ```csharp String resourceName = PluginResources . FindFile ( "MyImage.png" ); BitmapImage myImage = PluginResources . ReadImage ( resourceName ); ``` -------------------------------- ### Start Hot Reloading (Windows) Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Getting-started.md Starts the .NET watch command to automatically rebuild the plugin project and reload it in the host software when source files are saved. Use this on Windows. ```bash cd ExamplePlugin \ src \ dotnet watch build ``` -------------------------------- ### Generate Plugin Project Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Getting-started.md Generates a new plugin project template using the LogiPluginTool. Replace 'Example' with your desired plugin name. ```bash logiplugintool generate Example ``` -------------------------------- ### Example: Changing Plugin Status Dynamically Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-Status.md This example demonstrates how to dynamically change the plugin's status based on an action parameter, converting the parameter to a PluginStatus enum. ```csharp protected override void RunCommand ( String actionParameter ) { if ( actionParameter . TryGetEnumValue < PluginStatus > ( out var pluginStatus )) { this . Plugin . OnPluginStatusChanged ( pluginStatus , $"Plugin status changed to {pluginStatus}." ); } } ``` -------------------------------- ### Start Hot Reloading (macOS) Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Getting-started.md Starts the .NET watch command to automatically rebuild the plugin project and reload it in the host software when source files are saved. Use this on macOS. ```bash cd ExamplePlugin/src/ dotnet watch build ``` -------------------------------- ### Start Svelte Development Server Source: https://github.com/fallstop/hapticwebplugin/blob/main/web-demo/README.md Run your Svelte project locally. The `--open` flag automatically opens the application in your default web browser. ```sh npm run dev ``` ```sh npm run dev -- --open ``` -------------------------------- ### List All Plugin Settings Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Managing-Plugin-Settings.md Use `ListPluginSettings` to get an array of all available plugin setting names. ```csharp protected String [] ListPluginSettings (); ``` -------------------------------- ### Sample Icon Template Structure Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Icon-Templates.md This is a fully annotated example of an Icon Template file, demonstrating the structure for defining background color, image items, and text items with their respective properties and placement. ```json { "backgroundColor" : 4278869247 , // Background color in ARGB format "items" : [ { "$type" : "Loupedeck.Service.ActionIconImageItem, LoupedeckShared" , // Is being used for proper deserialization and is optional "image" : "" , // Encoded image string if applicable "imageFileName" : null , // Reference to image file, if available "imageColor" : 4294967295 , // Tint color for the image in ARGB format "imageRotation" : "None" , // Image rotation "isVisible" : true , // Indicates whether the item is visible "itemType" : "Image" , // Specifies that this item is an image "area" : { "x" : 15 , "y" : 0 , "width" : 70 , "height" : 70 , "isFullScreen" : true } // Defines placement and size of the image }, { "$type" : "Loupedeck.Service.ActionIconTextItem, LoupedeckShared" , // Is being used for proper deserialization and is optional "text" : "Some text" , // Default text display "textColor" : 4294967295 , // Text color in ARGB format "fontSize" : 5 , // Font size "fontName" : "Brown Logitech Pan Light" , // Font type "isVisible" : true , // Indicates whether the item is visible "itemType" : "Text" , // Specifies that this item is a text component "area" : { "x" : 0 , "y" : 70 , "width" : 100 , "height" : 30 , "isFullScreen" : false } // Defines placement and dimensions for the text } ] } ``` -------------------------------- ### ActionEditorKeyboardKey Example Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Adds a keyboard shortcut capture control to the Action Editor. ```csharp this . ActionEditor . AddControlEx ( new ActionEditorKeyboardKey ( name : "KeyControl" , labelText : "Shortcut:" ) . SetBehavior ( ActionEditorKeyboardKeyBehavior . KeyboardKey )); ``` -------------------------------- ### Minimal Example: Toggle Multistate Dynamic Command Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Multistate-Plugin-Actions.md A basic implementation of a multistate dynamic command that toggles between two states ('On' and 'Off') using the ToggleCurrentState() method. ```csharp namespace Loupedeck.Test4Plugin { using System ; public class ToggleMultistateDynamicCommand : PluginMultistateDynamicCommand { public ToggleMultistateDynamicCommand () : base ( "Toggle Multistate" , null , "Test" ) { this . AddState ( "On" , "Turn me on" ); this . AddState ( "Off" , "Turn me off" ); } protected override void RunCommand ( String actionParameter ) => this . ToggleCurrentState (); } } ``` -------------------------------- ### ActionEditorDirectorySelector Example Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Adds a directory selector control to the Action Editor. ```csharp this . ActionEditor . AddControlEx ( new ActionEditorDirectorySelector ( name : "DirectoryControl" , labelText : "Select directory:" )); ``` -------------------------------- ### Implement Command Execution Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Add-a-simple-command.md Override the RunCommand method to define the action performed when the command is triggered. This example sends a virtual key code to mute/unmute audio. ```csharp protected override void RunCommand ( String actionParameter ) { this . Plugin . ClientApplication . SendKeyboardShortcut ( VirtualKeyCode . VolumeMute ); } ``` -------------------------------- ### Plugin Capabilities in LoupedeckPackage.yaml Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-Capabilities.md The pluginCapabilities field in LoupedeckPackage.yaml defines special installation and runtime requirements for plugins. ```yaml pluginCapabilities : - RequiresAdminInstallation - RequiresAdminUninstallation - RequireApplicationCloseOnInstallWin - RequireApplicationCloseOnInstallMac ``` -------------------------------- ### ActionEditorButton Example Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Adds an interactive button control to the Action Editor. ```csharp this . ActionEditor . AddControlEx ( new ActionEditorButton ( name : "ButtonControl" , labelText : "Click me" )); ``` -------------------------------- ### Set Plugin to Warning State Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-Status.md Use this to signal a partial issue with the plugin. Provide a descriptive message to guide the user. ```csharp this.OnPluginStatusChanged(PluginStatus.Warning, "Open the application."); ``` -------------------------------- ### ActionEditorSlider Example Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Adds a numeric slider control for value selection within a range. ```csharp this . ActionEditor . AddControlEx ( new ActionEditorSlider ( name : "SliderControl" , labelText : "Volume:" , description : "Adjust volume level" ) . SetValues ( minimumValue : 0 , maximumValue : 100 , defaultValue : 50 , step : 5 ) . SetFormatString ( "{0}%" )); ``` -------------------------------- ### Get Command Display Name Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Retrieves the display name for a command action. Returns 'Unknown' if the application is not found. ```csharp public override String GetCommandDisplayName ( String actionParameter , PluginImageSize imageSize ) => this . TryGetNativeApplication ( actionParameter , out var app ) ? app . DisplayName : "Unknown" ; ``` -------------------------------- ### Get Command Image Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Retrieves the icon image for a command action. Returns null if the application or its icon is not found. ```csharp public override BitmapImage GetCommandImage ( String actionParameter , PluginImageSize imageSize ) => this . TryGetNativeApplication ( actionParameter , out var app ) ? app . Icon ?. ToImage () : null ; ``` -------------------------------- ### Action Editor Lifecycle Events Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Subscribes to and handles the Action Editor's Started and Finished lifecycle events. Use these events for initializing resources when the editor opens and cleaning up when it closes. ```csharp // ... public MyActionEditorCommand () { // ... // Subscribe to Action Editor lifecycle events this . ActionEditor . Started += this . OnActionEditorStarted ; this . ActionEditor . Finished += this . OnActionEditorFinished ; } private void OnActionEditorStarted ( Object sender , ActionEditorStartedEventArgs e ) { // Called when user opens the Action Editor // Initialize any resources, start monitoring external data, etc. } private void OnActionEditorFinished ( Object sender , ActionEditorFinishedEventArgs e ) { // Called when user closes the Action Editor // Clean up resources, stop monitoring external data, save temporary data, etc. } ``` -------------------------------- ### Populating Listbox Items Dynamically Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Handles the ListboxItemsRequested event to dynamically populate items in a listbox control. This example adds predefined options and optionally sets a default selection. ```csharp // ... public MyActionEditorCommand () { // Add listbox controls that need dynamic population this . ActionEditor . AddControlEx ( new ActionEditorListbox ( name : "ListControl" , labelText : "Select option:" )); // Subscribe to event that fires when listbox needs items this . ActionEditor . ListboxItemsRequested += this . OnListboxItemsRequested ; } private void OnListboxItemsRequested ( Object sender , ActionEditorListboxItemsRequestedEventArgs e ) { if ( e . ControlName . EqualsNoCase ( "ListControl" )) { // Add items to the listbox e . AddItem ( name : "option1" , displayName : "Option 1" , description : "First option" ); e . AddItem ( name : "option2" , displayName : "Option 2" , description : "Second option" ); // Optionally set default selection e . SetSelectedItemName ( "option1" ); } ``` -------------------------------- ### Generate Plugin Project Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/haptics-tutorial.md Creates a new plugin project skeleton named 'Tutorial' using the Logi Plugin Tool. ```bash LogiPluginTool generate Tutorial ``` -------------------------------- ### Build Plugin Project Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Getting-started.md Navigates to the generated plugin folder and builds the solution. This step compiles your plugin code. ```bash cd ExamplePlugin dotnet build ``` -------------------------------- ### Set Command Display Properties Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Add-a-simple-command.md Initialize the command's display name, description, and group name in the constructor. ```csharp public ToggleMuteCommand () : base ( displayName : "Toggle Mute" , description : "Toggles audio mute state" , groupName : "Audio" ) { } ``` -------------------------------- ### Create a New Svelte Project Source: https://github.com/fallstop/hapticwebplugin/blob/main/web-demo/README.md Use these commands to initialize a new Svelte project. The first creates a project in the current directory, while the second creates a project in a specified subdirectory. ```sh npx sv create ``` ```sh npx sv create my-app ``` -------------------------------- ### Build HapticWebPlugin Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md Builds the HapticWebPlugin project using .NET. Use 'Debug' for auto-reloads in Logi Plugin Service or 'Release' for a production build. ```bash # Debug build (auto-reloads in Logi Plugin Service) dotnet build -c Debug # Release build dotnet build -c Release ``` -------------------------------- ### Initialize Image Resource Paths Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md In the constructor, initialize string members with the full paths to the image resources using PluginResources.FindFile(). This ensures the plugin can locate the correct images for button display. ```csharp private readonly String _imageResourcePathThumbUp; private readonly String _imageResourcePathThumbDown; public ThumbUpDownCommand() : base(displayName: "Thumb up/down", description: null, groupName: "Switches") { this._imageResourcePathThumbUp = PluginResources.FindFile("ThumbUp.png"); this._imageResourcePathThumbDown = PluginResources.FindFile("ThumbDown.png"); } ``` -------------------------------- ### Get Adjustment Value Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Retrieves the string representation of an adjustment value. Returns null if the volume is not found. ```csharp public override String GetAdjustmentValue ( String actionParameter ) => this . TryGetVolume ( actionParameter , out var volume ) ? volume . ToString ( "D" ) : null ; ``` -------------------------------- ### Package Plugin Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md Packages the HapticWeb plugin for distribution. ```bash logiplugintool pack ./bin/Release ./HapticWeb.lplug4 ``` -------------------------------- ### Pack Plugin to .lplug4 File Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Distributing-the-plugin.md Use the 'pack' command with the Logi Plugin Tool to create a .lplug4 package from your plugin's build directory. ```bash logiplugintool pack ./bin/Release/ ./Example.lplug4 ``` -------------------------------- ### Get Current Action State Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Multistate-Plugin-Actions.md Use TryGetCurrentState() to retrieve the current state index of an action. Overloads exist for actions with and without an action parameter. ```csharp public Boolean TryGetCurrentState ( out Int32 currentState ); public Boolean TryGetCurrentState ( String actionParameter , out Int32 currentState ); ``` -------------------------------- ### Initialize PluginLog in Constructor Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Logging.md Initialize the PluginLog class in your plugin's constructor. Replace 'DemoPlugin' with your actual plugin class name. ```csharp public DemoPlugin () => PluginLog.Init ( this.Log ); ``` -------------------------------- ### Read Plugin Setting Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Managing-Plugin-Settings.md Use `TryGetPluginSetting` to retrieve a plugin setting by its name. It returns true if the setting exists, otherwise false, setting the output parameter to null. ```csharp protected Boolean TryGetPluginSetting ( String settingName , out String settingValue ); ``` -------------------------------- ### Write Plugin Setting Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Managing-Plugin-Settings.md Use `SetPluginSetting` to save a plugin setting. Specify whether to back up the setting online by setting the `backupOnline` parameter to true or false. ```csharp protected void SetPluginSetting ( String settingName , String settingValue , Boolean backupOnline ); ``` -------------------------------- ### Create Tree Profile Action Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Profile-Actions.md Implement a profile action that uses a two-level combo box for selection, suitable for hierarchical data like Windows Settings applications. ```csharp this . MakeProfileAction ( "tree" ); ``` ```csharp protected override PluginProfileActionData GetProfileActionData () { // create tree data var tree = new PluginProfileActionTree ( "Select Windows Settings Application" ); // describe levels tree . AddLevel ( "Category" ); tree . AddLevel ( "Application" ); // add data var categoryNames = this . _applications . Values . Select ( a => a . CategoryName ). Distinct (); foreach ( var categoryName in categoryNames ) { var node = tree . Root . AddNode ( categoryName ); var items = this . _applications . Values . Where ( a => a . CategoryName . EqualsNoCase ( categoryName )); foreach ( var item in items ) { node . AddItem ( item . ApplicationUri , item . ApplicationName , null ); } } // return tree data return tree ; } ``` ```csharp protected override String GetCommandDisplayName ( String actionParameter , PluginImageSize imageSize ) => this . _applications . TryGetValue ( actionParameter , out var application ) ? application . ApplicationName : null ; ``` ```csharp protected override void RunCommand ( String actionParameter ) => Process . Start ( actionParameter ); ``` -------------------------------- ### Handling Control Value Changes Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Subscribes to and handles the ControlValueChanged event to react to user input changes in real-time. This example shows how to update the display name based on textbox input and handle button clicks. ```csharp // ... public MyActionEditorCommand () { // Add controls those will trigger value change events this . ActionEditor . AddControlEx ( new ActionEditorTextbox ( name : "TextControl" , labelText : "Enter text:" )); this . ActionEditor . AddControlEx ( new ActionEditorButton ( name : "ButtonControl" , labelText : "Click me" )); // Subscribe to value change events this . ActionEditor . ControlValueChanged += this . OnControlValueChanged ; } private void OnControlValueChanged ( Object sender , ActionEditorControlValueChangedEventArgs e ) { if ( e . ControlName . EqualsNoCase ( "TextControl" )) { var controlValue = e . ActionEditorState . GetControlValue ( "TextControl" ); // Update display name based on user input e . ActionEditorState . SetDisplayName ( $"Action: {controlValue}" ); } if ( e . ControlName . EqualsNoCase ( "ButtonControl" )) { // Handle button click } ``` -------------------------------- ### Create List Profile Action Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Profile-Actions.md Implement a profile action that allows users to select an item from a list. The selected item is then used to update the plugin status. ```csharp public class DynamicListProfileAction : PluginDynamicCommand { public DynamicListProfileAction () { this . DisplayName = "Dynamic List" ; this . GroupName = "Profile Actions" ; for ( var i = 0 ; i < 5 ; i ++ ) { this . AddParameter ( $"item{i}" , $"Item {i}" , this . GroupName ); } } } ``` ```csharp this . MakeProfileAction ( "list;Select parameter:" ); ``` ```csharp protected override void RunCommand ( String actionParameter ) => this . Plugin . OnPluginStatusChanged ( PluginStatus . Warning , actionParameter ); ``` -------------------------------- ### Trigger Haptic Feedback via WebSocket API (JavaScript) Source: https://github.com/fallstop/hapticwebplugin/blob/main/web-demo/static/llms.txt This JavaScript code snippet demonstrates how to connect to the WebSocket endpoint and trigger a haptic waveform by sending its index. The 'completed' waveform (index 7) is used as an example. ```javascript const ws = new WebSocket('wss://local.jmw.nz:41443/ws'); ws.onopen = () => { // Trigger "completed" waveform (index 7) ws.send(new Uint8Array([7])); }; ``` -------------------------------- ### Get Command Image Based on State Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md Override GetCommandImage to return the appropriate image (ThumbUp.png or ThumbDown.png) based on the current command state (_isThumbDown). This method is called by Logi Plugin Service to determine the button's visual representation. ```csharp protected override BitmapImage GetCommandImage(String actionParameter, PluginImageSize imageSize) { var resourcePath = this._isThumbDown ? this._imageResourcePathThumbDown : this._imageResourcePathThumbUp; return PluginResources.ReadImage(resourcePath); } ``` -------------------------------- ### Create Text Profile Action Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Profile-Actions.md Implement a profile action that accepts user-defined text input. This is useful for actions like sending chat messages. ```csharp this . MakeProfileAction ( "text;Enter chat message to send:" ); ``` ```csharp protected override void RunCommand ( String actionParameter ) => Chat . SendMessage ( actionParameter ); ``` -------------------------------- ### Enable Haptic Capability in Plugin Configuration Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Haptics-Getting-Started.md Declare the HasHapticMapping capability in your LoupedeckPackage.yaml file to enable haptic functionality for your plugin. ```yaml # ... other configuration fields pluginCapabilities : - HasHapticMapping # Enables haptics ``` -------------------------------- ### Build Svelte Project for Production Source: https://github.com/fallstop/hapticwebplugin/blob/main/web-demo/README.md Generate an optimized production build of your Svelte application. This command prepares your project for deployment. ```sh npm run build ``` -------------------------------- ### Define a Dynamic Folder Class Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Inherit from PluginDynamicFolder and set display name and group name in the constructor. ```csharp public class TaskSwitcherDynamicFolder : PluginDynamicFolder { public TaskSwitcherDynamicFolder () { this.DisplayName = "Alt+Tab"; this.GroupName = "System"; } } ``` -------------------------------- ### Add Parameters in Constructor Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Add-a-command-with-a-parameter.md Adds four parameters to a command, representing switch indices. Each parameter is defined with an action parameter string, a display name, and a group name. ```csharp public ButtonSwitchesCommand () : base () { for ( var i = 0 ; i < 4 ; i ++ ) { // parameter is the switch index var actionParameter = i . ToString (); // add parameter this . AddParameter ( actionParameter , $"Switch {i}" , "Switches" ); } } ``` -------------------------------- ### Create a Dynamic Command Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md This snippet shows how to create a basic dynamic command that toggles an internal boolean state on button press. This is a foundational step for commands that require dynamic image updates. ```csharp namespace Loupedeck.DemoPlugin { using System; public class ThumbUpDownCommand : PluginDynamicCommand { private Boolean _isThumbDown = false; public ThumbUpDownCommand() : base(displayName: "Thumb up/down", description: null, groupName: "Switches") { } protected override void RunCommand(String actionParameter) { this._isThumbDown = !this._isThumbDown; } } } ``` -------------------------------- ### ActionEditorFileSelector Configuration Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Adds a file selection control and sets its initial directory to the user's desktop. ```csharp this . ActionEditor . AddControlEx ( new ActionEditorFileSelector ( name : "FileControl" , labelText : "Select file:" ) . SetInitialDirectory ( Environment . GetFolderPath ( Environment . SpecialFolder . Desktop ))); ``` -------------------------------- ### Initialize Counter Adjustment Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Add-a-simple-adjustment.md Initialize the adjustment with a display name, description, group name, and indicate if it has a reset functionality. ```csharp public CounterAdjustment () : base ( displayName : "Counter" , description : "Counts rotation ticks" , groupName : "Adjustments" , hasReset : true ) { } ``` -------------------------------- ### Define Default Event Source in YAML Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Haptics-Getting-Started.md Configure the default event source and its events using a YAML file. This approach centralizes event definitions. ```yaml displayName : My application description : Contains various events generated for the application iconFile : DefaultEventSource.svg events : - name : periodic15min displayName : Every 15 minutes description : This haptic event is sent every 15 minutes - name : buttonPress displayName : Button Press description : This haptic event is sent when the user presses the button ``` -------------------------------- ### Log Plugin Message with Exception Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Logging.md Use this method to log both an exception and an associated message from a plugin. Ensure PluginLog is initialized. ```csharp PluginLog.Info(ex, "Counter was reset"); ``` -------------------------------- ### SendText Command Implementation Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Implements a button action that sends user-provided text. It configures a textbox for text input. ```csharp public class SendTextCommand : ActionEditorCommand { private const String TextControlName = "Text" ; public SendTextCommand () { this . Name = "SendText" ; this . DisplayName = "Send Text" ; this . GroupName = "Action Editor" ; this . Description = "Place text into an active text field" ; this . ActionEditor . AddControlEx ( new ActionEditorTextbox ( TextControlName , "Text:" )); } protected override Boolean RunCommand ( ActionEditorActionParameters actionParameters ) { if ( actionParameters . TryGetString ( TextControlName , out var text )) { // Implement text sending functionality return true ; } return false ; } } ``` -------------------------------- ### Verify .lplug4 Plugin Package Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Distributing-the-plugin.md Use the 'verify' command with the Logi Plugin Tool to validate the integrity and format of a .lplug4 package. ```bash logiplugintool verify ./Example.lplug4 ``` -------------------------------- ### Basic Action Editor Command Structure Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Defines a custom action with basic properties and adds textbox and checkbox controls. It also subscribes to control value change events. ```csharp public class MyActionEditorCommand : ActionEditorCommand { public MyActionEditorCommand () { // Set basic properties this . Name = "UniqueActionName" ; this . DisplayName = "User-friendly name" ; this . GroupName = "Category" ; this . Description = "Brief description of functionality" ; // Add controls for user configuration this . ActionEditor . AddControlEx ( new ActionEditorTextbox ( "TextControl" , "Enter text:" )); this . ActionEditor . AddControlEx ( new ActionEditorCheckbox ( "CheckboxControl" , "Enable feature:" )); // Subscribe to events this . ActionEditor . ListboxItemsRequested += this . OnListboxItemsRequested ; this . ActionEditor . ControlValueChanged += this . OnControlValueChanged ; } protected override Boolean RunCommand ( ActionEditorActionParameters actionParameters ) { // Use the configured values when the action executes if ( actionParameters . TryGetString ( "TextControl" , out var text )) { // Perform action with user's configured text return true ; } return false ; } } ``` -------------------------------- ### Initialize PluginResources in Constructor Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md Initialize the PluginResources class in your plugin's constructor. Ensure the namespace matches your project and replace 'DemoPlugin' with your actual plugin class name. ```csharp public DemoPlugin () => PluginResources . Init ( this . Assembly ); ``` -------------------------------- ### Check DNS Resolution Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md Checks if 'local.jmw.nz' correctly resolves to the loopback IP address (127.0.0.1). ```bash ping local.jmw.nz ``` -------------------------------- ### ActionEditorTextbox with Options Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Action-Editor-Actions.md Configures a text input control with 'required' and 'placeholder' options. ```csharp this . ActionEditor . AddControlEx ( new ActionEditorTextbox ( name : "TextControl" , labelText : "Enter text:" ) . SetRequired () . SetPlaceholder ( "Type here..." )); ``` -------------------------------- ### Map Haptic Waveforms to Events Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/haptics-tutorial.md Maps the 'buttonPress' event to default and device-specific haptic waveforms. ```yaml haptics : buttonPress : DEFAULT : sharp_state_change MX Master 4 : sharp_collision # Optional: device-specific mappings ``` -------------------------------- ### Uninstall Plugin Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md Uninstalls the HapticWeb plugin from the local environment. ```bash logiplugintool uninstall HapticWeb ``` -------------------------------- ### Execute Command and Close Folder Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Executes a command and then programmatically closes the dynamic folder. Use consistently with either closing after each command or not at all. ```csharp public override void RunCommand ( String actionParameter ) { # execute the selected command here # close the folder after the command this . Close (); } ``` -------------------------------- ### Define Navigation Buttons in Code Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md When navigation mode is 'None', use specific action names to programmatically add navigation buttons like 'Back', 'Previous', and 'Next'. ```csharp // For "Back" button PluginDynamicFolder.NavigateUpActionName; // For "Previous Touch Page" button PluginDynamicFolder.NavigateLeftActionName; // For "Next Touch Page" button PluginDynamicFolder.NavigateRightActionName; ``` -------------------------------- ### Add HasHapticMapping Capability Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/haptics-tutorial.md Configures the plugin to support haptic mappings by adding 'HasHapticMapping' to the plugin capabilities in the metadata YAML file. ```yaml pluginCapabilities : - HasHapticMapping ``` -------------------------------- ### Specify Multiple Application Process Names Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Link-the-plugin-to-an-application.md Override the `GetProcessNames` method to define an array of process names for supported applications. This allows the plugin to activate for any of the listed applications. ```csharp protected override String [] GetProcessNames () => new [] { "Ableton Live 10 Lite" , "Ableton Live 10 Standard" }; ``` -------------------------------- ### Define Button Press Actions Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Defines the action names for button presses in a dynamic folder. Includes navigation and custom commands. ```csharp public override IEnumerable < String > GetButtonPressActionNames ( DeviceType _ ) { return new [] { PluginDynamicFolder . NavigateUpActionName , this . CreateCommandName ( "7" ), this . CreateCommandName ( "8" ), this . CreateCommandName ( "9" ), this . CreateCommandName ( "." ), this . CreateCommandName ( "4" ), this . CreateCommandName ( "5" ), this . CreateCommandName ( "6" ), this . CreateCommandName ( "0" ), this . CreateCommandName ( "1" ), this . CreateCommandName ( "2" ), this . CreateCommandName ( "3" ) }; } ``` -------------------------------- ### Generate XLIFF Files with LogiPluginTool Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-Localization.md Generates XLIFF localization files using the LogiPluginTool command-line utility. Specify the plugin name and the output directory for the generated files. ```bash LogiPluginTool xliff Spotify ./ ``` -------------------------------- ### Define Default Event Source Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/haptics-tutorial.md Defines a default event source named 'buttonPress' with a display name and description for haptic events. ```yaml events : - name : buttonPress displayName : Button Press description : Triggered when the button is pressed ``` -------------------------------- ### Log Plugin Message Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Logging.md Use this method to log a simple message from a plugin. Ensure PluginLog is initialized. ```csharp PluginLog.Info("Counter was reset"); ``` -------------------------------- ### Execute Command Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Implementing-Dynamic-Folders.md Executes a command based on the provided action parameter, attempting to parse it as a process ID. ```csharp public override void RunCommand ( String actionParameter ) { if ( Int32 . TryParse ( actionParameter , out var processId )) { // Implement process activation logic with the given processId } } ``` -------------------------------- ### Application Patterns in LoupedeckPackage.yaml Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-Capabilities.md The applicationPatterns field defines regex expressions to identify supported applications, which is required when using application close capabilities. ```yaml applicationPatterns : processNamePattern : ^lightroom$ bundleNamePattern : ^com.adobe.LightroomClassicCC7$ displayNamePattern : ^Adobe Lightroom Classic$ executablePathPattern : Adobe Lightroom Classic\lightroom.exe$ ``` -------------------------------- ### Specify No Associated Application Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Plugin-Capabilities.md Set the HasNoApplication flag to true in your Plugin class if the plugin does not require an application to be in the foreground or running locally. ```csharp public override Boolean HasNoApplication => true ; ``` -------------------------------- ### Define ToggleMuteCommand Class Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Add-a-simple-command.md Inherit your command class from PluginDynamicCommand to create a new command. ```csharp class ToggleMuteCommand : PluginDynamicCommand ``` -------------------------------- ### PluginResources Helper Methods Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md These static methods from the PluginResources class provide convenient ways to find and read plugin resource files, such as images. They abstract away the complexities of resource path management. ```csharp public static String FindFile(String fileName) => PluginResources._assembly.FindFileOrThrow(fileName); public static BitmapImage ReadImage(String resourceName) => PluginResources._assembly.ReadImage(PluginResources.FindFile(resourceName)); ``` -------------------------------- ### RunCommand for Parameterized Command Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Add-a-command-with-a-parameter.md Handles command execution based on the provided action parameter. It toggles the state of a switch and notifies the service about changes to the command's display name or image. ```csharp protected override void RunCommand ( String actionParameter ) { if ( Int32 . TryParse ( actionParameter , out var i )) { // turn the switch this . _switches [ i ] = ! this . _switches [ i ]; // inform service that command display name and/or image has changed this . ActionImageChanged ( actionParameter ); } } ``` -------------------------------- ### Define Multiple States for a Dynamic Action Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Multistate-Plugin-Actions.md In the dynamic action class constructor, call the AddState() method for each action state. This method defines the display name and description for a state. ```csharp public LampSwitchDynamicCommand () { this . AddState ( "On" , "Lamp is turned on" ); this . AddState ( "Off" , "Lamp is turned off" ); } ``` -------------------------------- ### REST API Endpoints Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md The HapticWebPlugin provides a RESTful API for interacting with haptic feedback features. This includes checking service health, listing available waveforms, and triggering specific haptic effects. ```APIDOC ## GET / ### Description Health check with service info and available endpoints. ### Method GET ### Endpoint / ## GET /waveforms ### Description List all 15 available haptic waveforms. ### Method GET ### Endpoint /waveforms ## POST /haptic/{waveform} ### Description Trigger a specific haptic waveform. ### Method POST ### Endpoint /haptic/{waveform} ### Parameters #### Path Parameters - **waveform** (string) - Required - The name of the haptic waveform to trigger. ### Request Example ```bash curl -X POST -d '' https://local.jmw.nz:41443/haptic/sharp_collision ``` ### Response #### Success Response (200) - **success** (boolean) - Indicates if the request was successful. - **waveform** (string) - The name of the triggered waveform. #### Response Example ```json { "success": true, "waveform": "completed" } ``` ``` -------------------------------- ### List Available Waveforms REST API Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md Retrieves a list of all 15 available haptic waveforms supported by the plugin. ```bash curl https://local.jmw.nz:41443/waveforms ``` -------------------------------- ### Define Waveform Mappings in YAML Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Haptics-Getting-Started.md Map haptic events to specific waveforms using a YAML file. This allows for custom haptic responses based on event names and target devices. ```yaml haptics : periodic15min : DEFAULT : happy_alert buttonPress : DEFAULT : sharp_state_change MX Master 4 : sharp_collision # Device-specific mapping ``` -------------------------------- ### List Waveforms Source: https://github.com/fallstop/hapticwebplugin/blob/main/web-demo/static/llms.txt Retrieves a list of all available haptic waveforms supported by the plugin. ```APIDOC ## GET /waveforms ### Description List all 15 available haptic waveforms. ### Method GET ### Endpoint /waveforms ### Response #### Success Response (200) - **field1** (type) - Description ``` -------------------------------- ### Add Background Image and Text to Button Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Change-a-button-image.md This snippet demonstrates how to create a button image with a background image and text using BitmapBuilder. Ensure the image file is added as an embedded resource to the plugin project. ```csharp protected override BitmapImage GetCommandImage(String actionParameter, PluginImageSize imageSize) { using (var bitmapBuilder = new BitmapBuilder(imageSize)) { bitmapBuilder.SetBackgroundImage(PluginResources.ReadImage("MyPlugin.EmbeddedResources.MyImage.png")); bitmapBuilder.DrawText("My text"); return bitmapBuilder.ToImage(); } } ``` -------------------------------- ### WebSocket API Source: https://github.com/fallstop/hapticwebplugin/blob/main/README.md The HapticWebPlugin offers a WebSocket API for lower-latency haptic feedback triggers. This API allows clients to send waveform indices over a persistent connection. ```APIDOC ## WebSocket API ### Endpoint `wss://local.jmw.nz:41443/ws` ### Description Keeps a warm connection for theoretically lower latency. Send a single byte containing the waveform index (0-14). No response is sent back. Connection stays open for repeated triggers. ### Protocol - Send a single byte containing the waveform index (0-14) - No response is sent back - Connection stays open for repeated triggers ### Request Example (JavaScript) ```javascript const ws = new WebSocket('wss://local.jmw.nz:41443/ws'); ws.onopen = () => { console.log('Connected to HapticWeb'); // Trigger "sharp_collision" (index 0) ws.send(new Uint8Array([0])); // Trigger "completed" (index 7) ws.send(new Uint8Array([7])); }; ``` ``` -------------------------------- ### Specify Single Application Process Name Source: https://github.com/fallstop/hapticwebplugin/blob/main/docs/Link-the-plugin-to-an-application.md Override the `GetProcessName` method to return the process name of the application you want to link to the plugin. This ensures the plugin activates when this specific application is in focus. ```csharp protected override String GetProcessName () => "DemoApplication" ; ```