Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-dotnettools.csdevkit",
"ms-dotnettools.csharp",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.vscodeintellicode-csharp"
]
}
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch & Attach",
"type": "coreclr",
"request": "attach",
"processName": "LogiPluginService",
"preLaunchTask": "debug-plugin",
"postDebugTask": "stop-loupedeck",
"justMyCode": true
}
]
}
14 changes: 14 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"csharp.preview.improvedLaunchExperience": true,
"files.exclude": {
".vscode": false,
"**/node_modules": false,
"**/.gitattributes": false,
"**/.gitignore": false,
"**/.prettierrc": false,
"**/eslint.config.mjs": false,
"**/package-lock.json": false,
"**/pnpm-lock.yaml": false,
"**/tsconfig.json": false
}
}
122 changes: 122 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "stop-loupedeck",
"type": "shell",
"dependsOn": ["stop-pluginservice"],
"osx": {
"command": "/usr/bin/pkill -x Loupedeck"
},
"windows": {
"command": "Taskkill /IM \"Loupedeck.exe\" /T /F"
}
},
{
"label": "stop-pluginservice",
"type": "shell",
"osx": {
"command": "/usr/bin/pkill -x LogiPluginService"
},
"windows": {
"command": "Taskkill /IM \"LogiPluginService.exe\" /T /F"
}
},
{
"label": "debug-plugin",
"osx": {
// "command": "/Applications/Loupedeck.app/Contents/MacOS/Loupedeck"
"command": "/Applications/Utilities/LogiPluginService.app/Contents/MacOS/LogiPluginService"
},
"windows": {
"command": "wt 'C:\\Program Files\\Logi\\LogiPluginService\\LogiPluginService.exe'"
// "command": "wt 'C:\\Program Files (x86)\\Loupedeck\\Loupedeck2\\configui2\\Loupedeck.exe'"
},
"type": "shell",
"dependsOn": ["build-debug"],
"isBackground": true,
"problemMatcher": {
"owner": "custom",
"fileLocation": ["absolute"],
"pattern": [
{
"regexp": "^\\d{4}-\\d{2}-\\d{2}T(\\d{2}-\\d{2}-\\d{2})-(\\d+) \\|\\s+(\\d+) \\|\\s+([A-Z]+) \\|\\s+(.*)$",
"severity": 4,
"line": 0,
"column": 1,
"message": 5,
"code": 4
},
{
"regexp": "^\\d{2}-\\d{2}-\\d{2}-(\\d+) \\[CN\\] File name is '(.*)'",
"file": 2,
"message": 2
},
{
"regexp": "^\\d{2}-\\d{2}-\\d{2}-(\\d+) \\[WS\\] (.*)$",
"message": 2
},
{
"regexp": "^\\d{2}-\\d{2}-\\d{2}-(\\d+) Waiting \\(locking\\) main thread\\.\\.\\.$",
"message": 0
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "^\\d{2}-\\d{2}-\\d{2}-(\\d+) Initializing Loupedeck connection",
"endsPattern": "^\\d{2}-\\d{2}-\\d{2}-(\\d+) Waiting \\(locking\\) main thread\\.\\.\\.$"
}
}
},
{
"label": "build-debug",
"type": "shell",
"command": "dotnet",
"osx": {
"args": [
"build",
"${workspaceFolder}/ObsStudioPlugin\\src\\ObsStudioPlugin.sln", // Adjust to your path
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"/p:Configuration=Debug",
"/p:Platform=\"Any CPU\""
]
},
"windows": {
"args": [
"build",
"${workspaceFolder}\\ObsStudioPlugin\\src\\ObsStudioPlugin.sln", // Adjust to your path
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"/p:Configuration=Debug"
]
},
"problemMatcher": "$msCompile"
},
{
"label": "build-release",
"command": "dotnet",
"type": "shell",
"osx": {
"args": [
"build",
"${workspaceFolder}/ObsStudioPlugin\\src\\ObsStudioPlugin.sln", // Adjust to your path
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"/p:Configuration=Release",
"/p:Platform=\"Any CPU\""
]
},
"windows": {
"args": [
"build",
"${workspaceFolder}\\ObsStudioPlugin\\src\\ObsStudioPlugin.sln", // Adjust to your path
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
"/p:Configuration=Release"
]
},
"problemMatcher": "$msCompile"
}
]
}
147 changes: 147 additions & 0 deletions ObsStudioPlugin/src/ObsPlugin/Actions/SourceTransformCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
namespace Loupedeck.ObsStudioPlugin.Actions
{
using System;

internal class SourceTransformCommand : PluginMultistateDynamicCommand
{
public const String IMGSceneSelected = "SourceVisibilityOn.svg";
public const String IMGSceneUnselected = "SourceVisibilityOff.svg";
public const String IMGSceneInaccessible = "SourceDisabled.png";
public const String SourceNameUnknown = "Name Not Available";

private const Int16 SOURCE_UNSELECTED = 0;
private const Int16 SOURCE_SELECTED = 1;

public SourceTransformCommand()
{
this.Description = "Enables Move Top/Bottom/Lef/Right and Zoom In/Out of a Source";
this.GroupName = "2. Sources";
_ = this.AddState("Hidden", "Source hidden");
_ = this.AddState("Visible", "Source visible");
}

protected override Boolean OnLoad()
{
this.IsEnabled = false;

ObsStudioPlugin.Proxy.AppConnected += this.OnAppConnected;
ObsStudioPlugin.Proxy.AppDisconnected += this.OnAppDisconnected;

ObsStudioPlugin.Proxy.AppEvtSceneListChanged += this.OnSceneListChanged;
ObsStudioPlugin.Proxy.AppEvtCurrentSceneChanged += this.OnCurrentSceneChanged;
ObsStudioPlugin.Proxy.AppEvtSceneNameChanged += this.OnSceneListChanged; //Note using same handler since we just re-generate params


ObsStudioPlugin.Proxy.AppEvtSceneItemVisibilityChanged += this.OnSceneItemVisibilityChanged;

ObsStudioPlugin.Proxy.AppEvtSceneItemAdded += this.OnSceneItemAdded;
ObsStudioPlugin.Proxy.AppEvtSceneItemRemoved += this.OnSceneItemRemoved;

ObsStudioPlugin.Proxy.AppInputRenamed += this.OnSourceRenamed;


this.OnAppDisconnected(this, null);

return true;
}

protected override Boolean OnUnload()
{
ObsStudioPlugin.Proxy.AppConnected -= this.OnAppConnected;
ObsStudioPlugin.Proxy.AppDisconnected -= this.OnAppDisconnected;

ObsStudioPlugin.Proxy.AppEvtSceneListChanged -= this.OnSceneListChanged;
ObsStudioPlugin.Proxy.AppEvtCurrentSceneChanged -= this.OnCurrentSceneChanged;
ObsStudioPlugin.Proxy.AppEvtSceneNameChanged -= this.OnSceneListChanged;

ObsStudioPlugin.Proxy.AppEvtSceneItemVisibilityChanged -= this.OnSceneItemVisibilityChanged;

ObsStudioPlugin.Proxy.AppEvtSceneItemAdded -= this.OnSceneItemAdded;
ObsStudioPlugin.Proxy.AppEvtSceneItemRemoved -= this.OnSceneItemRemoved;

ObsStudioPlugin.Proxy.AppInputRenamed -= this.OnSourceRenamed;


return true;
}

protected override void RunCommand(String actionParameter) => ObsStudioPlugin.Proxy.AppSceneItemVisibilityToggle(actionParameter);

private void OnSceneListChanged(Object sender, EventArgs e) => this.ResetParameters(true);

private void OnCurrentSceneChanged(Object sender, EventArgs e) => this.ActionImageChanged();

private void OnSceneItemAdded(Object sender, SceneItemArgs arg)
{
this.AddSceneItemParameter(arg.SceneName, arg.ItemName, arg.ItemId);
this.ParametersChanged();
}

private void OnSceneItemRemoved(Object sender, SceneItemArgs arg)
{
var s = SceneItemKey.Encode(ObsStudioPlugin.Proxy.CurrentSceneCollection, arg.SceneName, arg.ItemId,arg.ItemName);
this.Plugin.Log.Info($"Removing scene item {s} sources");
this.RemoveParameter(s);
this.ParametersChanged();
}

//Note: We can possibly do cherry-picking on the parameters but that require quite a bit of code.
private void OnSourceRenamed(Object sender, OldNewStringChangeEventArgs args) => this.ResetParameters(true);

private void OnAppConnected(Object sender, EventArgs e) => this.IsEnabled = true;

private void OnAppDisconnected(Object sender, EventArgs e)
{
this.IsEnabled = false;

this.ResetParameters(false);
this.ActionImageChanged();
}

protected void OnSceneItemVisibilityChanged(Object sender, SceneItemVisibilityChangedArgs arg)
{
var actionParameter = SceneItemKey.Encode(ObsStudioPlugin.Proxy.CurrentSceneCollection, arg.SceneName, arg.ItemId);
_ = this.SetCurrentState(actionParameter, arg.Visible ? SOURCE_SELECTED : SOURCE_UNSELECTED);
//this.ActionImageChanged(actionParameter);
}

protected override BitmapImage GetCommandImage(String actionParameter, Int32 stateIndex, PluginImageSize imageSize)
{
var imageName = IMGSceneInaccessible;
if (SceneItemKey.TryParse(actionParameter, out var parsed))
{
imageName = parsed.Collection != ObsStudioPlugin.Proxy.CurrentSceneCollection || !ObsStudioPlugin.Proxy.AllSceneItems.ContainsKey(actionParameter)
? IMGSceneInaccessible
: stateIndex == SOURCE_SELECTED ? IMGSceneSelected : IMGSceneUnselected;
}

return EmbeddedResources.ReadBinaryFile(ObsStudioPlugin.ImageResPrefix + imageName).ToImage();
}

private void AddSceneItemParameter(String sceneName, String itemName,Int32 itemId)
{
var key = SceneItemKey.Encode(ObsStudioPlugin.Proxy.CurrentSceneCollection, sceneName, itemId);
this.AddParameter(key, $"{itemName}", $"{this.GroupName}{CommonStrings.SubgroupSeparator}{sceneName}").Description =
ObsStudioPlugin.Proxy.AllSceneItems[key].Visible ? "Hide" : "Show" + $" source \"{itemName}\" of scene \"{sceneName}\"";
this.SetCurrentState(key, ObsStudioPlugin.Proxy.AllSceneItems[key].Visible ? SOURCE_SELECTED : SOURCE_UNSELECTED);
}

private void ResetParameters(Boolean readContent)
{
this.RemoveAllParameters();

if (readContent)
{
this.Plugin.Log.Info($"Adding {ObsStudioPlugin.Proxy.AllSceneItems?.Count} sources");

foreach (var item in ObsStudioPlugin.Proxy.AllSceneItems)
{
this.AddSceneItemParameter(item.Value.SceneName, item.Value.SourceName, item.Value.SourceId);
}
}

this.ParametersChanged();
this.ActionImageChanged();
}
}
}
Loading