Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert the 'Serial' field to a drop-down #10

Merged
Merged
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
80 changes: 73 additions & 7 deletions FaderSyncPlugin/OBS/GoXlrChannelSyncFilter.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -140,11 +141,22 @@ private static unsafe void GetDefaults(obs_data* settings)
private static unsafe obs_properties* GetProperties(void* data)
{
var properties = ObsProperties.obs_properties_create();
var context = (FilterContext*)data;

// Get the currently configured Serial as a String (we'll need this later)
var serial = Marshal.PtrToStringUTF8((IntPtr)context->DeviceSerial);
var deviceSerialError = "---ERROR---";

fixed (byte*
// device serial input
sDeviceSerialId = "DEVICE_SERIAL"u8.ToArray(),
sDeviceSerialDescription = "Device Serial"u8.ToArray(),

// We need a 'Default' serial in case something is wrong when loading
sDeviceSerialError = Encoding.UTF8.GetBytes(deviceSerialError),
sDeviceSerialDisconnected = "Error Connecting to the GoXLR Utility"u8.ToArray(),
sDeviceSerialNoDevices = "No GoXLR Devices Detected"u8.ToArray(),
sDeviceSerialSelect = "Select GoXLR"u8.ToArray(),

// add channels
sChannelNameId = "CHANNEL_NAME"u8.ToArray(),
Expand Down Expand Up @@ -191,15 +203,69 @@ private static unsafe void GetDefaults(obs_data* settings)
ObsProperties.obs_property_list_add_string(channelList, (sbyte*)sChannelMicMonitor, (sbyte*)sChannelMicMonitorId);
ObsProperties.obs_property_list_add_string(channelList, (sbyte*)sChannelLineOut, (sbyte*)sChannelLineOutId);

// // channel name text field
// ObsProperties.obs_properties_add_text(properties, (sbyte*)sChannelNameId, (sbyte*)sChannelNameDescription,
// obs_text_type.OBS_TEXT_DEFAULT);

// device serial text field
ObsProperties.obs_properties_add_text(properties, (sbyte*)sDeviceSerialId, (sbyte*)sDeviceSerialDescription,
obs_text_type.OBS_TEXT_DEFAULT);
// Create the Serial Dropdown..
var deviceList = ObsProperties.obs_properties_add_list(properties, (sbyte*)sDeviceSerialId, (sbyte*)sDeviceSerialDescription,
obs_combo_type.OBS_COMBO_TYPE_LIST, obs_combo_format.OBS_COMBO_FORMAT_STRING);


// Before we Proceed, we need to fetch a list of the available GoXLRs on the System..
var utility = UtilitySingleton.GetInstance();
var mixers = (JsonObject)utility.Status?["mixers"];
var locatedDevices = new ArrayList();
var forcedSerial = false;

// Iterate the status and add all the currently connected serials to a list.
if (mixers != null) {
foreach (var mixer in mixers) {
locatedDevices.Add(mixer.Key);
}
}

// Get an initial count of devices which we'll use for stuff later!
var locatedDeviceCount = locatedDevices.Count;

// If the user has perviously configured a GoXLR but it's not currently attached to the Utility, we need to
// force the serial into the list to prevent arbitrary device switching later on. We'll also flag this as a
// forced entry so we can appropriately label it.
if (serial != "" && !locatedDevices.Contains(serial)) {
locatedDevices.Add(serial);
forcedSerial = true;
}

if (locatedDevices.Count == 0) {
// We're in some kind of error state. Either the utility connection is broken or there are no GoXLRs attached, and the
// user hasn't previously defined a GoXLR. In this case we'll forcably add the 'Error' serial to the list so we can
// display the problem to the user in the drop-down.
locatedDevices.Add(deviceSerialError);
}

//ObsProperties.obs_properties_add_text(properties, (sbyte*)tWarnTitle, (sbyte*)tWarnMessage, obs_text_type.OBS_TEXT_INFO);
// Start filling out the list..
foreach (var located in locatedDevices) {
fixed (byte* sSerial = Encoding.UTF8.GetBytes((string)located)) {
if (located.Equals(deviceSerialError) && mixers == null) {
// Unable to Connect to the Utility, no GoXLR previously configured in the Filter
ObsProperties.obs_property_list_add_string(deviceList, (sbyte*)sDeviceSerialDisconnected, (sbyte*)sDeviceSerialError);
} else if (located.Equals(deviceSerialError) && locatedDeviceCount == 0) {
// No GoXLR Devices Attached, no GoXLR previously configured in the Filter
ObsProperties.obs_property_list_add_string(deviceList, (sbyte*)sDeviceSerialNoDevices, (sbyte*)sDeviceSerialError);
} else if (located.Equals(deviceSerialError) && locatedDeviceCount > 0) {
// In this scenario we've left an Error State. By not pushing the Error Serial into the list OBS will automatically
// switch the dropdown to the first entry (a real GoXLR Serial) forcing an update and taking us out of the error state.
} else {
var title = (string)located;

// Has this device been forced into the located list due to it being disconnected?
if (forcedSerial && located.Equals(serial)) {
// We can do a *LOT* better than this and potentially check WHY it's disconnected..
title = String.Format("{0} - Disconnected", located);
}
fixed(byte* sTitle = Encoding.UTF8.GetBytes(title)) {
ObsProperties.obs_property_list_add_string(deviceList, (sbyte*)sTitle, (sbyte*)sSerial);
}
}
}
}
}

return properties;
Expand Down
19 changes: 16 additions & 3 deletions UtilityClient/Native/WebsocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
private ClientWebSocket _client = new();

private CancellationTokenSource _cancellationTokenSource = new();
private Task _receiveMessageTask;

Check warning on line 11 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Non-nullable field '_receiveMessageTask' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 11 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Non-nullable field '_receiveMessageTask' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
private Task _connectionTask;

Check warning on line 12 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Non-nullable field '_connectionTask' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 12 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Non-nullable field '_connectionTask' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

private bool _isConnected = false;

Expand All @@ -30,7 +30,7 @@
continue;
}

WebSocketReceiveResult result = null;

Check warning on line 33 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Converting null literal or possible null value to non-nullable type.

Check warning on line 33 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Converting null literal or possible null value to non-nullable type.
try
{
result = await _client.ReceiveAsync(new ArraySegment<byte>(buffer),
Expand Down Expand Up @@ -69,11 +69,12 @@

case WebSocketMessageType.Close:
// If the server initiates the close handshake, handle it
Task.Run(DisconnectAsync);

Check warning on line 72 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 72 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
break;

case WebSocketMessageType.Binary:
default:
// This wont occur with the Utility, but we need to trigger DisconnectAsync to perform tidying up!
await _client.CloseAsync(WebSocketCloseStatus.ProtocolError, "Only Text is supported.", CancellationToken.None);
this.OnDisconnected?.Invoke(this, "Connection closed because server tried to send binary or invalid message.");
break;
Expand All @@ -94,8 +95,8 @@

case WebSocketState.Aborted:
case WebSocketState.Closed:
if (this._isConnected) this.OnDisconnected?.Invoke(this, "Connection closed.");
this._isConnected = false;
// Trigger an internal disconnect to clean resources.
Task.Run(DisconnectAsync);

Check warning on line 99 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 99 in UtilityClient/Native/WebsocketClient.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
break;

default:
Expand Down Expand Up @@ -126,7 +127,10 @@

public async Task DisconnectAsync()
{
await this._client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
// Only attempt to close the socket if it's not already closed
if (this._client.State != WebSocketState.Aborted && this._client.State != WebSocketState.Closed) {
await this._client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
}
this.OnDisconnected?.Invoke(this, "Connection closed.");

this._cancellationTokenSource.Cancel();
Expand All @@ -140,6 +144,15 @@

this._receiveMessageTask.Dispose();
this._connectionTask.Dispose();

// Dispose of, and create a new client / Token for future connections
this._client.Dispose();
this._client = new();
this._cancellationTokenSource.Dispose();
this._cancellationTokenSource = new();

// Flag the connection as disconnected
this._isConnected = false;
}

public async Task SendMessage(string message)
Expand Down
7 changes: 7 additions & 0 deletions UtilityClient/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
if (isPatchMessage)
{
var patchString = data["Patch"]!.ToJsonString();
var patch = JsonSerializer.Deserialize<JsonPatch>(patchString);

Check warning on line 40 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.

Check warning on line 40 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.

Check warning on line 40 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.

Check warning on line 40 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.
var resultResult = patch?.Apply(this.Status);
this.Status = resultResult!.Result!;
}
Expand All @@ -47,6 +47,13 @@
OnException?.Invoke(this, je);
} // nothing
};

base.OnDisconnected += async (object? sender, string message) => {

Check warning on line 51 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 51 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
Console.WriteLine("Disconnected from Host: {0}", message);

// Reset the status, so Upstream code knows we're not connected.
this.Status = JsonNode.Parse("{}")!;
};
}

private async Task<JsonNode?> AwaitResponse(uint operationId)
Expand Down Expand Up @@ -83,7 +90,7 @@
}
}

public new async Task ConnectAsync()

Check warning on line 93 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Windows

The member 'Utility.ConnectAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 93 in UtilityClient/Utility.cs

View workflow job for this annotation

GitHub Actions / Build for Linux

The member 'Utility.ConnectAsync()' does not hide an accessible member. The new keyword is not required.
{
String pipeName = OperatingSystem.IsWindows() ? "@goxlr.socket" : "/tmp/goxlr.socket";
SocketClient socketClient = new SocketClient(pipeName);
Expand Down
Loading