16 Commits

Author SHA1 Message Date
jwetzell ceb18307f6 Update min and target android version 2023-11-25 23:31:47 -06:00
jwetzell 2f5ce281d3 cleanup logging 2023-11-25 23:26:07 -06:00
jwetzell 7b6ed09ced rework TCPClient yet again to try to nail down this SLIP framing problem 2023-11-25 22:46:06 -06:00
jwetzell 486af45d53 Make SlipFrame constants public 2023-11-25 22:45:10 -06:00
jwetzell e5d09538ab add in status/data and connection event 2022-11-08 15:41:27 -06:00
jwetzell 1a0ccc8763 Update publish.yml 2022-11-05 15:03:51 -05:00
jwetzell fce3eab8d7 bump version number 2022-11-05 14:58:58 -05:00
jwetzell 2176ca4488 TCP Work, Log cleanup, Message parsing 2022-11-05 13:50:27 -05:00
jwetzell 57ca80df77 Fix QLab Message parsing for reply formats 2022-11-05 13:23:14 -05:00
jwetzell 974d14156f Update dependencies 2022-10-11 18:00:32 -05:00
jwetzell 92a44d95d4 Create LICENSE 2022-07-25 10:06:15 -05:00
jwetzell 498d2a41f9 Update README.md 2022-07-23 13:56:56 -05:00
jwetzell 730e7bc554 Update dependencies 2022-07-23 13:10:25 -05:00
jwetzell 1eb8e0c42b Cue properties clean up 2022-07-23 09:57:47 -05:00
jwetzell 69b8210362 switch to simpletcp library 2022-07-23 09:57:33 -05:00
jwetzell 1deca3e7f3 Change Log Levels 2022-07-23 09:57:06 -05:00
26 changed files with 460 additions and 304 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Publish NuGet - name: Publish NuGet
# You may pin to the exact commit or the version. # You may pin to the exact commit or the version.
# uses: brandedoutcast/publish-nuget@c12b8546b67672ee38ac87bea491ac94a587f7cc # uses: brandedoutcast/publish-nuget@c12b8546b67672ee38ac87bea491ac94a587f7cc
uses: brandedoutcast/publish-nuget@v2.5.5 uses: alirezanet/publish-nuget@v3.0.4
with: with:
# Filepath of the project to be packaged, relative to root of repository # Filepath of the project to be packaged, relative to root of repository
PROJECT_FILE_PATH: QControlKit/QControlKit.csproj PROJECT_FILE_PATH: QControlKit/QControlKit.csproj
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Joel Wetzell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+11
View File
@@ -289,6 +289,17 @@
public const string Error = "error"; public const string Error = "error";
} }
public static class QConnectionRole
{
public const string View = "view";
public const string ViewEdit = "view|edit";
public const string ViewEditControl = "view|edit|control";
public const string ViewControl = "view|control";
public const string EditControl = "edit|control";
public const string Edit = "edit";
public const string Control = "control";
}
public static class QColorName public static class QColorName
{ {
public const string Red = "red"; public const string Red = "red";
+3 -1
View File
@@ -48,7 +48,8 @@ namespace QControlKit.Events
public class QWorkspaceConnectedArgs : EventArgs public class QWorkspaceConnectedArgs : EventArgs
{ {
public string status { get; set; }
public string data { get; set; }
} }
public class QWorkspaceDisconnectedArgs : EventArgs public class QWorkspaceDisconnectedArgs : EventArgs
@@ -59,6 +60,7 @@ namespace QControlKit.Events
public class QWorkspaceConnectionErrorArgs : EventArgs public class QWorkspaceConnectionErrorArgs : EventArgs
{ {
public string status { get; set; } public string status { get; set; }
public string data { get; set; }
} }
public class QQLabPreferencesUpdatedArgs : EventArgs public class QQLabPreferencesUpdatedArgs : EventArgs
{ {
+8 -8
View File
@@ -13,6 +13,8 @@ namespace QControlKit
{ {
public class QBrowser public class QBrowser
{ {
private ILogger _log = Log.Logger.ForContext<QBrowser>();
public ObservableCollection<QServer> servers = new ObservableCollection<QServer>(); public ObservableCollection<QServer> servers = new ObservableCollection<QServer>();
public event QServerFoundHandler ServerFound; public event QServerFoundHandler ServerFound;
@@ -26,17 +28,16 @@ namespace QControlKit
public async void ProbeForQLabInstances() public async void ProbeForQLabInstances()
{ {
Log.Debug("[qbrowser] probing for instances"); _log.Debug("probing for instances");
IReadOnlyList<IZeroconfHost> results = await IReadOnlyList<IZeroconfHost> results = await
ZeroconfResolver.ResolveAsync(QBonjour.TCPService,TimeSpan.FromSeconds(3)); ZeroconfResolver.ResolveAsync(QBonjour.TCPService,TimeSpan.FromSeconds(3));
foreach (var zeroconfHost in results) foreach (var zeroconfHost in results)
{ {
Log.Debug($"[qbrowser] found host {zeroconfHost.IPAddress}:{zeroconfHost.DisplayName}"); _log.Debug($"found host {zeroconfHost.IPAddress}:{zeroconfHost.DisplayName}");
foreach (var service in zeroconfHost.Services) foreach (var service in zeroconfHost.Services)
{ {
Log.Debug($"[qbrowser] found {service.Key}:{service.Value}");
if (service.Key.Equals(QBonjour.TCPService)) if (service.Key.Equals(QBonjour.TCPService))
{ {
@@ -44,7 +45,7 @@ namespace QControlKit
if (server == null) if (server == null)
{ {
Log.Information($"[qbrowser] Found {zeroconfHost.DisplayName} : {zeroconfHost.IPAddress} : {service.Value.Port}"); _log.Information($"Found {zeroconfHost.DisplayName} : {zeroconfHost.IPAddress} : {service.Value.Port}");
QServer serverToAdd = new QServer(zeroconfHost.IPAddress, service.Value.Port); QServer serverToAdd = new QServer(zeroconfHost.IPAddress, service.Value.Port);
serverToAdd.name = zeroconfHost.DisplayName; serverToAdd.name = zeroconfHost.DisplayName;
serverToAdd.zeroconfHost = zeroconfHost; serverToAdd.zeroconfHost = zeroconfHost;
@@ -70,10 +71,9 @@ namespace QControlKit
if (found == null) if (found == null)
{ {
Log.Information($"[qbrowser] Lost {server.name} : {server.host} : {server.port}"); _log.Information($"Lost {server.name} : {server.host} : {server.port} disconnecting");
server.disconnect(); server.disconnect();
Log.Information($"[qbrowser] after server disconnect()"); _log.Verbose($"after server disconnect()");
servers.Remove(server); servers.Remove(server);
OnServerLost(server); OnServerLost(server);
} }
@@ -123,7 +123,7 @@ namespace QControlKit
public void Close() public void Close()
{ {
Log.Information($"[qbrowser] Close requested"); _log.Information($"Close requested");
foreach (var server in servers) foreach (var server in servers)
{ {
server.disconnect(); server.disconnect();
+32 -33
View File
@@ -12,6 +12,8 @@ namespace QControlKit
{ {
public class QClient public class QClient
{ {
private ILogger _log = Log.Logger.ForContext<QClient>();
TCPClient tcpClient; TCPClient tcpClient;
public event QWorkspacesUpdatedHandler WorkspacesUpdated; public event QWorkspacesUpdatedHandler WorkspacesUpdated;
@@ -35,7 +37,7 @@ namespace QControlKit
public QClient(string host, int port) public QClient(string host, int port)
{ {
tcpClient = new TCPClient(host, port); tcpClient = new TCPClient(host, port);
Log.Debug($"[client] setup connection to: <{host}:{port}>"); _log.Debug($"setup connection to: <{host}:{port}>");
tcpClient.MessageReceived += ProcessMessage; tcpClient.MessageReceived += ProcessMessage;
} }
@@ -52,15 +54,14 @@ namespace QControlKit
public void disconnect() public void disconnect()
{ {
Log.Information($"[client] disconnecting from {tcpClient.Address}"); _log.Information($"disconnecting from {tcpClient.Address}");
tcpClient.Close(); tcpClient.Close();
} }
public void sendMessage(string address, params object[] args) public void sendMessage(string address, params object[] args)
{ {
tcpClient.QueueForSending(new OscMessage(address, args)); tcpClient.Send(new OscMessage(address, args));
Log.Information($"[client] send message {address} : {args}");
} }
private void ProcessMessage(object source, MessageEventArgs args) private void ProcessMessage(object source, MessageEventArgs args)
@@ -70,7 +71,7 @@ namespace QControlKit
if (message.IsReply) if (message.IsReply)
{ {
JToken data = message.response; JToken data = message.data;
//special case, want to update cue properties //special case, want to update cue properties
if (message.IsReplyFromCue) if (message.IsReplyFromCue)
@@ -84,26 +85,25 @@ namespace QControlKit
string property = message.AddressParts.Last(); string property = message.AddressParts.Last();
if (property == null) if (property == null)
return; return;
if (property == QOSCKey.PlaybackPositionId)
{
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
return;
}
//create object manually since single value replies don't have dictionaries //create object manually since single value replies don't have dictionaries
JObject properties = new JObject(); JObject properties = new JObject();
properties.Add(property, data); properties.Add(property, data);
OnCueUpdated(message.cueID, properties); OnCueUpdated(message.cueID, properties);
if (property == QOSCKey.PlaybackPositionId)
{
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
}
} }
else else
{ {
Log.Error($"[client] unhandled reply from cue: Type: {data.Type} value: {message.response}"); _log.Error($"unhandled reply from cue: Type: {data.Type} value: {message.data}");
} }
} }
else if (message.IsReplyFromCueLists) else if (message.IsReplyFromCueLists)
{ {
OnCueListsUpdated(message.response); OnCueListsUpdated(message.data);
} }
else if (message.IsWorkspacesInfo) else if (message.IsWorkspacesInfo)
{ {
@@ -111,14 +111,14 @@ namespace QControlKit
} }
else if (message.IsConnect) else if (message.IsConnect)
{ {
if (message.response.ToString() == "ok") if (message.status.ToString().Equals("ok"))
OnWorkspaceConnected(); OnWorkspaceConnected(message.status.ToString(),message.data.ToString());
else else
OnWorkspaceConnectionError(message.response.ToString()); OnWorkspaceConnectionError(message.status.ToString(), message.data.ToString());
} }
else else
{ {
Log.Error($"[client] unhandled reply message: {message.address}"); _log.Error($"unhandled reply message: {message.address}");
} }
} }
else if(message.IsUpdate) { else if(message.IsUpdate) {
@@ -150,7 +150,6 @@ namespace QControlKit
else if ( message.IsPreferencesUpdate) else if ( message.IsPreferencesUpdate)
{ {
//need to do checks for 4.2 or newer //need to do checks for 4.2 or newer
string key = message.AddressParts.Last(); string key = message.AddressParts.Last();
if (key == null) if (key == null)
return; return;
@@ -162,76 +161,76 @@ namespace QControlKit
} }
else else
{ {
Log.Error($"[client] unhandled update message: {message.address}"); _log.Error($"unhandled update message: {message.address}");
} }
} }
else else
{ {
Log.Error($"[client] unhandled message: {message.address}"); _log.Error($"unhandled message: {message.address}");
} }
} }
protected virtual void OnCueUpdated(string cueID, JToken properties) protected virtual void OnCueUpdated(string cueID, JToken properties)
{ {
Log.Debug($"[client] cue updated: {cueID}"); _log.Debug($"cue updated: {cueID}");
CueUpdated?.Invoke(this, new QCueUpdatedArgs { cueID = cueID, data = properties }); CueUpdated?.Invoke(this, new QCueUpdatedArgs { cueID = cueID, data = properties });
} }
protected virtual void OnCueNeedsUpdated(string cueID) protected virtual void OnCueNeedsUpdated(string cueID)
{ {
Log.Debug($"[client] cue needs updated: {cueID}"); _log.Debug($"cue needs updated: {cueID}");
CueNeedsUpdated?.Invoke(this, new QCueNeedsUpdatedArgs { cueID = cueID }); CueNeedsUpdated?.Invoke(this, new QCueNeedsUpdatedArgs { cueID = cueID });
} }
protected virtual void OnCueListsUpdated(JToken response) protected virtual void OnCueListsUpdated(JToken response)
{ {
Log.Debug($"[client] Cue Lists Updated"); _log.Debug("Cue Lists Updated");
CueListsUpdated?.Invoke(this, new QCueListsUpdatedArgs { data = response }); CueListsUpdated?.Invoke(this, new QCueListsUpdatedArgs { data = response });
} }
protected virtual void OnCueListChangedPlaybackPosition(string cueListID, string cueID) protected virtual void OnCueListChangedPlaybackPosition(string cueListID, string cueID)
{ {
Log.Debug($"[client] CueList <{cueListID}> Playback Position Changed to <{cueID}>"); _log.Debug($"CueList <{cueListID}> Playback Position Changed to <{cueID}>");
CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = cueListID, cueID = cueID }); CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = cueListID, cueID = cueID });
} }
protected virtual void OnWorkspaceUpdated() protected virtual void OnWorkspaceUpdated()
{ {
//Log.Debug($"[client] Workspace Updated"); _log.Debug($"Workspace Updated");
WorkspaceUpdated?.Invoke(this, new QWorkspaceUpdatedArgs()); WorkspaceUpdated?.Invoke(this, new QWorkspaceUpdatedArgs());
} }
protected virtual void OnWorkspaceSettingsUpdated(string settingsType) protected virtual void OnWorkspaceSettingsUpdated(string settingsType)
{ {
Log.Debug($"[client] Workspace Settings Updated"); _log.Debug($"Workspace Settings Updated");
WorkspaceSettingsUpdated?.Invoke(this, new QWorkspaceSettingsUpdatedArgs { settingsType = settingsType }); WorkspaceSettingsUpdated?.Invoke(this, new QWorkspaceSettingsUpdatedArgs { settingsType = settingsType });
} }
protected virtual void OnWorkspaceLightDashboardUpdated() protected virtual void OnWorkspaceLightDashboardUpdated()
{ {
Log.Debug($"[client] Workspace Light Dashboard Updated"); _log.Debug("Workspace Light Dashboard Updated");
WorkspaceLightDashboardUpdated?.Invoke(this, new QWorkspaceLightDashboardUpdatedArgs()); WorkspaceLightDashboardUpdated?.Invoke(this, new QWorkspaceLightDashboardUpdatedArgs());
} }
protected virtual void OnQLabPreferencesUpdated(string key) protected virtual void OnQLabPreferencesUpdated(string key)
{ {
Log.Debug($"[client] QLab Preferences Updated"); _log.Debug("QLab Preferences Updated");
QLabPreferencesUpdated?.Invoke(this, new QQLabPreferencesUpdatedArgs { key = key }); QLabPreferencesUpdated?.Invoke(this, new QQLabPreferencesUpdatedArgs { key = key });
} }
protected virtual void OnWorkspaceDisconnected() protected virtual void OnWorkspaceDisconnected()
{ {
Log.Debug($"[client] Workspace Disconnected"); _log.Debug("Workspace Disconnected");
WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs()); WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs());
} }
protected virtual void OnWorkspaceConnected() protected virtual void OnWorkspaceConnected(string status, string data)
{ {
WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs()); WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs { status = status, data = data });
} }
protected virtual void OnWorkspaceConnectionError(string status) protected virtual void OnWorkspaceConnectionError(string status, string data)
{ {
WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = status }); WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = status, data = data });
} }
protected virtual void OnWorkspacesUpdated(QMessage message) protected virtual void OnWorkspacesUpdated(QMessage message)
@@ -239,7 +238,7 @@ namespace QControlKit
if(WorkspacesUpdated != null) if(WorkspacesUpdated != null)
{ {
List<QWorkspaceInfo> workspaces = new List<QWorkspaceInfo>(); List<QWorkspaceInfo> workspaces = new List<QWorkspaceInfo>();
foreach (var item in message.response) foreach (var item in message.data)
{ {
QWorkspaceInfo workspacefound = JsonConvert.DeserializeObject<QWorkspaceInfo>(item.ToString()); QWorkspaceInfo workspacefound = JsonConvert.DeserializeObject<QWorkspaceInfo>(item.ToString());
workspaces.Add(workspacefound); workspaces.Add(workspacefound);
+4 -3
View File
@@ -6,7 +6,7 @@
<Authors>Joel Wetzell</Authors> <Authors>Joel Wetzell</Authors>
<Version>1.0.0-devel</Version> <Version>1.0.0-devel</Version>
<PackOnBuild>true</PackOnBuild> <PackOnBuild>true</PackOnBuild>
<PackageVersion>0.0.24</PackageVersion> <PackageVersion>0.0.33</PackageVersion>
<PackageId>QControlKit</PackageId> <PackageId>QControlKit</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<Summary>Unofficial C# port of Figure53's QLabKit.objc</Summary> <Summary>Unofficial C# port of Figure53's QLabKit.objc</Summary>
@@ -15,10 +15,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="TcpSharp" Version="1.2.0" />
<PackageReference Include="Zeroconf" Version="3.5.11" /> <PackageReference Include="Zeroconf" Version="3.5.11" />
<PackageReference Include="SuperSimpleTcp" Version="2.6.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Constants\" /> <Folder Include="Constants\" />
@@ -32,5 +32,6 @@
<None Remove="SharpOSC\" /> <None Remove="SharpOSC\" />
<None Remove="SimpleTCP" /> <None Remove="SimpleTCP" />
<None Remove="SuperSimpleTcp" /> <None Remove="SuperSimpleTcp" />
<None Remove="WatsonTcp" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+14 -11
View File
@@ -12,6 +12,7 @@ namespace QControlKit
{ {
public class QCue : IComparable public class QCue : IComparable
{ {
private ILogger _log = Log.Logger.ForContext<QCue>();
public QWorkspace workspace; public QWorkspace workspace;
public Dictionary<string, object> cueData; public Dictionary<string, object> cueData;
@@ -45,8 +46,6 @@ namespace QControlKit
init(); init();
this.workspace = workspace; this.workspace = workspace;
updatePropertiesWithDictionary(dict); updatePropertiesWithDictionary(dict);
workspace.fetchBasicPropertiesForCue(this);
} }
public QCue(JToken dict, QWorkspace workspace, int nestLevel) public QCue(JToken dict, QWorkspace workspace, int nestLevel)
@@ -55,7 +54,6 @@ namespace QControlKit
this.workspace = workspace; this.workspace = workspace;
this.nestLevel = nestLevel + 1; this.nestLevel = nestLevel + 1;
updatePropertiesWithDictionary(dict); updatePropertiesWithDictionary(dict);
workspace.fetchBasicPropertiesForCue(this);
} }
public void init() public void init()
@@ -165,7 +163,6 @@ namespace QControlKit
QCue cue = childCuesUIDMap[aUid]; QCue cue = childCuesUIDMap[aUid];
//Log.Debug($"Removing Child Cue From {listName} : {cue.uid} + {cue.listName}");
childCues.Remove(cue); childCues.Remove(cue);
childCuesUIDMap.Remove(aUid); childCuesUIDMap.Remove(aUid);
} }
@@ -650,14 +647,12 @@ namespace QControlKit
JToken value = obj.Value; JToken value = obj.Value;
if (obj.Key.Equals(QOSCKey.Cues)) if (obj.Key.Equals(QOSCKey.Cues))
{ {
//Log.Debug($"Cues OSC Key found in update message...updating child cues");
if (value.Type != JTokenType.Array) if (value.Type != JTokenType.Array)
continue; continue;
updateChildCuesWithPropertiesArray(value, false); updateChildCuesWithPropertiesArray(value, false);
} }
else if(obj.Key.Equals(QOSCKey.Children) && IsGroup) else if(obj.Key.Equals(QOSCKey.Children) && IsGroup)
{ {
//Log.Debug($"Children OSC Key found in update message for {uid} ...updating child and removing deleted ones.");
if (value.Type != JTokenType.Array) if (value.Type != JTokenType.Array)
continue; continue;
updateChildCuesWithPropertiesArray(value, true); updateChildCuesWithPropertiesArray(value, true);
@@ -671,7 +666,6 @@ namespace QControlKit
{ {
cueUpdated = true; cueUpdated = true;
propertiesUpdated.Add(obj.Key); propertiesUpdated.Add(obj.Key);
//Log.Debug($"[cue] cue property {obj.Key} updated with {obj.Value}");
} }
} }
} }
@@ -806,7 +800,7 @@ namespace QControlKit
if (propertyForKey(key) != null) if (propertyForKey(key) != null)
{ {
object prop = propertyForKey(key); object prop = propertyForKey(key);
//Log.Warning($"{listName} IsBroken: {propertyForKey(QOSCKey.IsBroken).ToString()} {prop.GetType().ToString()}"); //_log.Warning($"{listName} IsBroken: {propertyForKey(QOSCKey.IsBroken).ToString()} {prop.GetType().ToString()}");
if(prop.GetType() == typeof(Boolean)){ if(prop.GetType() == typeof(Boolean)){
return (bool)prop; return (bool)prop;
} }
@@ -896,7 +890,7 @@ namespace QControlKit
} }
else if (key.Equals(QOSCKey.Children)) else if (key.Equals(QOSCKey.Children))
{ {
Log.Debug($"Children Cues property updated for: {this.displayName} : {this.uid}"); _log.Debug($"Children Cues property updated for: {this.displayName} : {this.uid}");
} }
else if(key.Equals(QOSCKey.PlaybackPositionId)) else if(key.Equals(QOSCKey.PlaybackPositionId))
{ {
@@ -995,9 +989,18 @@ namespace QControlKit
#region Event Handling #region Event Handling
void OnCuePropertiesUpdated(List<string> properties) void OnCuePropertiesUpdated(List<string> properties)
{ {
_log.Verbose("OnCuePropertiesUpdated");
if(parentID != null && parentID != "") if(parentID != null && parentID != "")
{ {
workspace.fetchBasicPropertiesForCue(workspace.cueWithID(parentID)); QCue parentCue = workspace.cueWithID(parentID);
if(parentCue != null)
{
workspace.fetchBasicPropertiesForCue(workspace.cueWithID(parentID));
}
else
{
_log.Error($"parent cue with id: {parentID} can't be found this could be a problem");
}
} }
CuePropertiesUpdated?.Invoke(this, new QCuePropertiesUpdatedArgs { properties = properties }); CuePropertiesUpdated?.Invoke(this, new QCuePropertiesUpdatedArgs { properties = properties });
} }
@@ -1014,7 +1017,7 @@ namespace QControlKit
{ {
string indent = new string(' ', level*2); string indent = new string(' ', level*2);
Log.Information($"{indent}\u00b7{displayName} - {uid}"); _log.Information($"{indent}\u00b7{displayName} - {uid}");
if (IsGroup) if (IsGroup)
{ {
level++; level++;
+50 -4
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using SharpOSC; using SharpOSC;
@@ -28,7 +29,32 @@ namespace QControlKit
public bool IsReply { get { return OSCMessage.Address.StartsWith("/reply"); } } public bool IsReply { get { return OSCMessage.Address.StartsWith("/reply"); } }
// /reply/cue_id/1/action // /reply/cue_id/1/action
public bool IsReplyFromCue { get{ return OSCMessage.Address.StartsWith("/reply/cue_id"); } } public bool IsReplyFromCue {
get {
if (!IsReply)
{
return false;
}
//return OSCMessage.Address.StartsWith("/reply/cue_id");
if (AddressParts.Length > 5)
{
// /reply/workspace/*/cue_id/*/*
if (AddressParts[1].Equals("workspace") && AddressParts[3].Equals("cue_id")){
return true;
}
}
if(AddressParts.Length > 2)
{
// /reply/cue_id/*/*
if (AddressParts[1].Equals("cue_id"))
{
return true;
}
}
return false;
}
}
public bool IsReplyFromCueLists { get { return IsReply && OSCMessage.Address.EndsWith("cueLists"); } } public bool IsReplyFromCueLists { get { return IsReply && OSCMessage.Address.EndsWith("cueLists"); } }
public bool IsUpdate { get{ return OSCMessage.Address.StartsWith("/update"); } } public bool IsUpdate { get{ return OSCMessage.Address.StartsWith("/update"); } }
@@ -115,12 +141,25 @@ namespace QControlKit
public string[] AddressParts { get { return address.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries); } } public string[] AddressParts { get { return address.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries); } }
public JToken response public JToken data
{ {
get get
{ {
JObject responseObj = JObject.Parse((string)arguments[0]); JObject dataObj = JObject.Parse((string)arguments[0]);
return responseObj["data"]; return dataObj["data"];
}
}
public JToken status
{
get
{
if (arguments != null && arguments.Count > 0)
{
JObject statusObj = JObject.Parse((string)arguments[0]);
return statusObj["status"];
}
return null;
} }
} }
@@ -136,6 +175,13 @@ namespace QControlKit
//TODO: check string cast //TODO: check string cast
return arguments.Count > 0 ? (string)arguments[0] : null; return arguments.Count > 0 ? (string)arguments[0] : null;
} else if (IsReplyFromCue) { } else if (IsReplyFromCue) {
// /reply/workspace/*/cue_id/*/*
if (AddressParts.Length > 5)
{
return AddressParts[4];
}
// /reply/cue_id/*/*
return AddressParts[2]; return AddressParts[2];
} else { } else {
return null; return null;
+11 -6
View File
@@ -10,11 +10,13 @@ namespace QControlKit
{ {
public class QServer public class QServer
{ {
private ILogger _log = Log.Logger.ForContext<QServer>();
public event QServerUpdatedHandler ServerUpdated; public event QServerUpdatedHandler ServerUpdated;
public event QServerWorkspaceAddedHandler WorkspaceAdded; public event QServerWorkspaceAddedHandler WorkspaceAdded;
public event QServerWorkspaceRemovedHandler WorkspaceRemoved; public event QServerWorkspaceRemovedHandler WorkspaceRemoved;
private QClient client; private QClient _client;
public string host { get; set; } public string host { get; set; }
public int port { get; set; } public int port { get; set; }
@@ -28,18 +30,21 @@ namespace QControlKit
this.host = host; this.host = host;
this.port = port; this.port = port;
client = new QClient(host, port); _client = new QClient(host, port);
client.WorkspacesUpdated += OnServerWorkspacesUpdated; client.WorkspacesUpdated += OnServerWorkspacesUpdated;
if (!client.connect()) if (!client.connect())
{ {
Log.Error($"[server] unable to connect to QLab Server: {host}:{port}"); _log.Error($"unable to connect to QLab Server: {host}:{port}");
} }
} }
public string description { get { return $"{name} - {host} - {port}"; } } public string description { get { return $"{name} - {host} - {port}"; } }
public QClient client {
get { return _client; }
}
public void refreshWorkspaces() public void refreshWorkspaces()
{ {
client.sendMessage("/workspaces"); client.sendMessage("/workspaces");
@@ -111,12 +116,12 @@ namespace QControlKit
public void disconnect() public void disconnect()
{ {
Log.Information($"[server] disconnect requested for server <{name}>"); _log.Information($"disconnect requested for server <{name}>");
foreach (var workspace in workspaces) foreach (var workspace in workspaces)
{ {
if (workspace.connected) if (workspace.connected)
{ {
Log.Debug($"[server] Closing workspace <{workspace.name}> still connected to {name}"); _log.Debug($"Closing workspace <{workspace.name}> still connected to {name}");
workspace.disconnect(); workspace.disconnect();
} }
} }
+33 -25
View File
@@ -10,6 +10,7 @@ namespace QControlKit
{ {
public class QWorkspace: IEquatable<QWorkspace> public class QWorkspace: IEquatable<QWorkspace>
{ {
private ILogger _log = Log.Logger.ForContext<QWorkspace>();
private QServer server; private QServer server;
private QClient client; private QClient client;
@@ -64,7 +65,7 @@ namespace QControlKit
updateWithWorkspaceInfo(workspaceInfo); updateWithWorkspaceInfo(workspaceInfo);
client = new QClient(server.host, server.port); client = server.client;
client.WorkspaceConnected += OnWorkspaceConnected; client.WorkspaceConnected += OnWorkspaceConnected;
client.WorkspaceConnectionError += OnWorkspaceConnectionError; client.WorkspaceConnectionError += OnWorkspaceConnectionError;
@@ -75,7 +76,7 @@ namespace QControlKit
client.CueUpdated += OnCueUpdated; client.CueUpdated += OnCueUpdated;
this.server = server; this.server = server;
Log.Debug($"[workspace] <{name}> on <{server.name}> initialized."); _log.Debug($"<{name}> on <{server.name}> initialized.");
} }
//updateWithDictionary //updateWithDictionary
@@ -159,24 +160,26 @@ namespace QControlKit
public string[] versionParts { get { return version.Split('.'); } } public string[] versionParts { get { return version.Split('.'); } }
public bool connectedToQLab3 { get { return versionParts[0] == "3"; } } public bool connectedToQLab3 { get { return versionParts[0] == "3"; } }
public bool connectedToQLab4 { get { return versionParts[0] == "4"; } }
public bool connectedToQLab5 { get { return versionParts[0] == "5"; } }
#region Connection/reconnection #region Connection/reconnection
public void connect(string passcode = null) public void connect(string passcode = null)
{ {
Log.Information($"[workspace] connecting to <{name}> @ {this.server.host}:{this.server.port}"); _log.Information($"connecting to <{name}> @ {this.server.host}:{this.server.port}");
if(hasPasscode && passcode == null) if(hasPasscode && passcode == null)
{ {
Log.Error($"[workspace] *** workspace <{name}> requires a passcode but none was supplied."); _log.Error($"*** workspace <{name}> requires a passcode but none was supplied.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.BadPass }); OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.BadPass, data = "badpass" });
return; return;
} }
if (!client.connect()) if (!client.connect())
{ {
Log.Error($"[workspace] *** couldn't connect to server client is not connected."); _log.Error($"*** couldn't connect to server client is not connected.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.Error }); OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.Error, data = "error" });
return; return;
} }
@@ -210,7 +213,7 @@ namespace QControlKit
public void disconnect() public void disconnect()
{ {
Log.Information($"[workspace] disconnecting from <{name}>"); _log.Information($"disconnecting from <{name}>");
if (!connected) if (!connected)
return; return;
stopReceivingUpdates(); stopReceivingUpdates();
@@ -319,17 +322,12 @@ namespace QControlKit
{ {
List<string> keys = new List<string> { List<string> keys = new List<string> {
QOSCKey.UID, QOSCKey.UID,
QOSCKey.Number,
QOSCKey.Name,
QOSCKey.ListName,
QOSCKey.Type,
QOSCKey.ColorName,
QOSCKey.Flagged, QOSCKey.Flagged,
QOSCKey.Armed, QOSCKey.Armed,
QOSCKey.IsRunning, QOSCKey.IsRunning,
QOSCKey.IsBroken, QOSCKey.IsBroken,
QOSCKey.IsLoaded,
QOSCKey.Parent, QOSCKey.Parent,
QOSCKey.Notes
}; };
fetchPropertiesForCue(cue, keys.ToArray(), false); fetchPropertiesForCue(cue, keys.ToArray(), false);
@@ -422,27 +420,27 @@ namespace QControlKit
{ {
//clear passcode if there was one set in the connect() method //clear passcode if there was one set in the connect() method
this.passcode = null; this.passcode = null;
Log.Error($"[workspace] *** Password for workspace <{name}> was incorrect!"); _log.Error($"*** Password for workspace <{name}> was incorrect!");
} }
else else
{ {
Log.Error($"[workspace] *** Unable to connect to workspace: <{name}> on server: <{server.name}>"); _log.Error($"*** Unable to connect to workspace: <{name}> on server: <{server.name}>");
} }
WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = args.status }); WorkspaceConnectionError?.Invoke(this, args);
} }
private void OnWorkspaceConnected(object source, QWorkspaceConnectedArgs args) private void OnWorkspaceConnected(object source, QWorkspaceConnectedArgs args)
{ {
Log.Information($"[workspace] Connection to <{name}> successful, finishing things up."); _log.Information($"Connection to <{name}> successful, finishing things up.");
WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs()); WorkspaceConnected?.Invoke(this, args);
finishConnection(); finishConnection();
} }
private void OnWorkspaceDisconnected(object source, QWorkspaceDisconnectedArgs args) private void OnWorkspaceDisconnected(object source, QWorkspaceDisconnectedArgs args)
{ {
//this might not be called with TCP? //this might not be called with TCP?
Log.Warning($"[workspace] *** Workspace has indicated it is disconnecting"); _log.Warning($"*** Workspace has indicated it is disconnecting");
WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs()); WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs());
} }
@@ -511,7 +509,7 @@ namespace QControlKit
//add Event handled? use CueUpdated one? //add Event handled? use CueUpdated one?
} }
Log.Debug($"[workspace] cueLists finished processing. root updated? {rootCueUpdated}"); _log.Debug($"cueLists finished processing. root updated? {rootCueUpdated}");
OnWorkspaceUpdated(); OnWorkspaceUpdated();
@@ -529,7 +527,7 @@ namespace QControlKit
cueList.setProperty(args.cueID, QOSCKey.PlaybackPositionId, false); cueList.setProperty(args.cueID, QOSCKey.PlaybackPositionId, false);
} }
Log.Information($"[workspace] cue list <{args.cueListID}> playback position changed to <{args.cueID}>"); _log.Information($"cue list <{args.cueListID}> playback position changed to <{args.cueID}>");
CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = args.cueListID, cueID = args.cueID }); CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = args.cueListID, cueID = args.cueID });
} }
@@ -543,7 +541,7 @@ namespace QControlKit
{ {
if (args.cueID.Equals(QIdentifiers.RootCueUpdate)) if (args.cueID.Equals(QIdentifiers.RootCueUpdate))
{ {
Log.Debug("[workspace] root cue update requested, updating all cue lists"); _log.Debug("root cue update requested, updating all cue lists");
foreach (var cuelist in this.cueLists) foreach (var cuelist in this.cueLists)
{ {
if (!cuelist.uid.Equals(QIdentifiers.ActiveCues)) if (!cuelist.uid.Equals(QIdentifiers.ActiveCues))
@@ -565,10 +563,20 @@ namespace QControlKit
private void OnCueUpdated(object source, QCueUpdatedArgs args) private void OnCueUpdated(object source, QCueUpdatedArgs args)
{ {
_log.Verbose($"OnCueUpdated: {args.cueID}");
QCue cue = cueWithID(args.cueID); QCue cue = cueWithID(args.cueID);
if (cue == null || cue.ignoreUpdates)
if(cue == null)
{
_log.Error($"Could not find cue to update, this is likely a problem.");
return; return;
}
if (cue.ignoreUpdates)
{
return;
}
cue.updatePropertiesWithDictionary(args.data); cue.updatePropertiesWithDictionary(args.data);
} }
@@ -583,7 +591,7 @@ namespace QControlKit
#region Printing #region Printing
public void Print() public void Print()
{ {
Log.Information($"[workspace] {name}"); _log.Information($"{name}");
foreach (var cueList in root.cues) foreach (var cueList in root.cues)
{ {
cueList.Print(); cueList.Print();
-2
View File
@@ -26,8 +26,6 @@ namespace SharpOSC
private static OscMessage parseMessage(byte[] msg) private static OscMessage parseMessage(byte[] msg)
{ {
int index = 0; int index = 0;
//Log.Debug("Raw ASCII DATA: " + System.Text.Encoding.ASCII.GetString(msg));
//Log.Debug("Raw UTF-8 DATA: " + System.Text.Encoding.UTF8.GetString(msg));
string address = null; string address = null;
char[] types = new char[0]; char[] types = new char[0];
List<object> arguments = new List<object>(); List<object> arguments = new List<object>();
+5 -4
View File
@@ -4,10 +4,10 @@ namespace SharpOSC
{ {
public static class SlipFrame public static class SlipFrame
{ {
static readonly byte END = 0xc0; public static readonly byte END = 0xc0;
static readonly byte ESC = 0xdb; public static readonly byte ESC = 0xdb;
static readonly byte ESC_END = 0xDC; public static readonly byte ESC_END = 0xDC;
static readonly byte ESC_ESC = 0xDD; public static readonly byte ESC_ESC = 0xDD;
public static List<byte[]> Decode(byte[] data) public static List<byte[]> Decode(byte[] data)
{ {
@@ -26,6 +26,7 @@ namespace SharpOSC
buffer.Add(data[i]); buffer.Add(data[i]);
} }
} }
return messages; return messages;
} }
+74 -69
View File
@@ -1,8 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Linq;
using System.Text;
using Serilog; using Serilog;
using SuperSimpleTcp; using TcpSharp.Events;
namespace SharpOSC namespace SharpOSC
{ {
@@ -17,6 +18,8 @@ namespace SharpOSC
public class TCPClient public class TCPClient
{ {
private ILogger _log = Log.Logger.ForContext<TCPClient>();
public int Port public int Port
{ {
get { return _port; } get { return _port; }
@@ -30,96 +33,96 @@ namespace SharpOSC
public delegate void MessageReceivedHandler(object source, MessageEventArgs args); public delegate void MessageReceivedHandler(object source, MessageEventArgs args);
public event MessageReceivedHandler MessageReceived; public event MessageReceivedHandler MessageReceived;
private Queue<OscPacket> SendQueue = new Queue<OscPacket>();
private Thread sendThread;
string _address; string _address;
TcpSharp.TcpSharpSocketClient tcpClient;
SimpleTcpClient simpleClient;
private List<byte> frameStream = new List<byte>();
public TCPClient(string address, int port) public TCPClient(string address, int port)
{ {
_port = port; _port = port;
_address = address; _address = address;
simpleClient = new SimpleTcpClient(address, port);
simpleClient.Events.Connected += Connected;
simpleClient.Events.Disconnected += Disconnected;
simpleClient.Events.DataReceived += DataReceived;
simpleClient.Logger = Log.Debug;
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
private void DataReceived(object sender, DataReceivedEventArgs e)
{
List<byte[]> messages = SlipFrame.Decode(e.Data);
for(int i = 0; i < messages.Count; i++)
{
OscPacket packet = OscPacket.GetPacket(messages[i]);
OscMessage responseMessage = (OscMessage)packet;
OnMessageReceived(responseMessage);
}
}
private void Disconnected(object sender, ConnectionEventArgs e)
{
Log.Debug("[tcpClient] connection to " + e.IpPort + " was disconnected");
}
private void Connected(object sender, ConnectionEventArgs e)
{
Log.Debug($"[tcpclient] connected to <{e.IpPort}>");
} }
public bool Connect() public bool Connect()
{ {
try try
{ {
simpleClient.Connect(); tcpClient = new TcpSharp.TcpSharpSocketClient(Address, Port);
tcpClient.OnConnected += ClientConnected;
sendThread = new Thread(SendLoop); tcpClient.OnDataReceived += DataReceived;
sendThread.IsBackground = true; tcpClient.OnDisconnected += ClientDisconneted;
sendThread.Start(); tcpClient.OnError += ClientError;
tcpClient.Connect();
return true; return true;
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error(e.Message); _log.Error(e.Message);
return false; return false;
} }
}
private void ClientError(object sender, OnClientErrorEventArgs e)
{
_log.Error("client error");
_log.Error(e.Exception.ToString());
} }
public void QueueForSending(OscPacket packet) private void ClientDisconneted(object sender, OnClientDisconnectedEventArgs e)
{ {
SendQueue.Enqueue(packet); _log.Information($"client disconnected: {e.Reason}");
Close();
} }
private void SendLoop() private void DataReceived(object sender, OnClientDataReceivedEventArgs e)
{ {
while (simpleClient != null && simpleClient.IsConnected) frameStream.AddRange(e.Data);
{
if(SendQueue.Count > 0) int i = 0;
{ int frameEnd = frameStream.FindIndex(1, frameByte=>frameByte.Equals(SlipFrame.END));
OscPacket packet = SendQueue.Dequeue(); while(frameEnd > 0)
Send(packet); {
} List<byte> frame = frameStream.GetRange(0, frameEnd+1);
frameStream.RemoveRange(0, frameEnd + 1);
List<byte[]> messages = SlipFrame.Decode(frame.ToArray());
foreach (var message in messages)
{
try
{
OscPacket packet = OscPacket.GetPacket(message);
OscMessage responseMessage = (OscMessage)packet;
if (packet == null)
{
_log.Error("packet is null");
}
if (responseMessage == null)
{
_log.Error("responeMessage is null");
}
OnMessageReceived(responseMessage);
}
catch (Exception ex)
{
_log.Error($"Exception parsing OSC message: {ex.ToString()}");
}
}
frameEnd = frameStream.FindIndex(0, frameByte => frameByte.Equals(SlipFrame.END));
} }
} }
private void ClientConnected(object sender, OnClientConnectedEventArgs e)
{
_log.Debug($"connected to <{Address}:{Port}>");
}
public void Send(byte[] message) public void Send(byte[] message)
{ {
byte[] slipData = SlipFrame.Encode(message); byte[] slipData = SlipFrame.Encode(message);
simpleClient.Send(slipData); tcpClient.SendBytes(slipData.ToArray());
} }
public void Send(OscPacket packet) public void Send(OscPacket packet)
@@ -132,22 +135,24 @@ namespace SharpOSC
{ {
get get
{ {
if (simpleClient == null) if (tcpClient == null)
return false; return false;
else else
return simpleClient.IsConnected; return tcpClient.Connected;
} }
} }
public void Close() public void Close()
{ {
Log.Information("[tcpclient] Close() was requested"); if (tcpClient != null)
if (simpleClient != null)
{ {
if (simpleClient.IsConnected) tcpClient.OnConnected -= ClientConnected;
tcpClient.OnDataReceived -= DataReceived;
tcpClient.OnDisconnected -= ClientDisconneted;
if (tcpClient.Connected)
{ {
Log.Information($"[tcpClient] closing connection to {Address}"); _log.Debug($"closing connection to {Address}");
simpleClient.Disconnect(); tcpClient.Disconnect();
} }
} }
} }
@@ -157,4 +162,4 @@ namespace SharpOSC
MessageReceived?.Invoke(this, new MessageEventArgs() { Message = msg }); MessageReceived?.Invoke(this, new MessageEventArgs() { Message = msg });
} }
} }
} }
-2
View File
@@ -100,7 +100,6 @@ namespace SharpOSC
Log.Error(e.ToString()); Log.Error(e.ToString());
// If there is an error reading the packet, null is sent to the callback // If there is an error reading the packet, null is sent to the callback
} }
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
OscPacketCallback(packet); OscPacketCallback(packet);
} }
else else
@@ -151,7 +150,6 @@ namespace SharpOSC
{ {
byte[] bytes = queue.Dequeue(); byte[] bytes = queue.Dequeue();
var packet = OscPacket.GetPacket(bytes); var packet = OscPacket.GetPacket(bytes);
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
return packet; return packet;
} }
else else
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.jwetzell.qcontrolkitxamdemo"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.jwetzell.qcontrolkitxamdemo">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<application android:label="QControlKitXamDemo.Android"></application> <application android:label="QControlKitXamDemo.Android"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest> </manifest>
@@ -1,113 +1,113 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4F7F13C7-85EF-431F-AB03-B2494F0F4C7F}</ProjectGuid> <ProjectGuid>{4F7F13C7-85EF-431F-AB03-B2494F0F4C7F}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid> <TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>QControlKitXamDemo.Droid</RootNamespace> <RootNamespace>QControlKitXamDemo.Droid</RootNamespace>
<AssemblyName>QControlKitXamDemo.Android</AssemblyName> <AssemblyName>QControlKitXamDemo.Android</AssemblyName>
<Deterministic>True</Deterministic> <Deterministic>True</Deterministic>
<AndroidApplication>True</AndroidApplication> <AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass> <AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix> <MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix> <MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion> <TargetFrameworkVersion>v13.0</TargetFrameworkVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent> <AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2> <AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType> <DebugType>portable</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants> <DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode> <AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType> <DebugType>portable</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols> <AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Numerics" /> <Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" /> <Reference Include="System.Numerics.Vectors" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog.Sinks.Xamarin"> <PackageReference Include="Serilog.Sinks.Xamarin">
<Version>0.2.0.64</Version> <Version>1.0.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2622" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="MainActivity.cs" /> <Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" /> <Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Resources\AboutResources.txt" /> <None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" /> <None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" /> <None Include="Properties\AndroidManifest.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.xml" /> <AndroidResource Include="Resources\layout\Tabbar.xml" />
<AndroidResource Include="Resources\layout\Toolbar.xml" /> <AndroidResource Include="Resources\layout\Toolbar.xml" />
<AndroidResource Include="Resources\values\styles.xml" /> <AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" /> <AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" /> <AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" /> <AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\icon.png" /> <AndroidResource Include="Resources\mipmap-hdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" /> <AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\icon.png" /> <AndroidResource Include="Resources\mipmap-mdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" /> <AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\icon.png" /> <AndroidResource Include="Resources\mipmap-xhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" /> <AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" /> <AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" /> <AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" /> <AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" /> <AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\drawable\" /> <Folder Include="Resources\drawable\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\QControlKit\QControlKit.csproj"> <ProjectReference Include="..\..\QControlKit\QControlKit.csproj">
<Project>{51cd2579-79c9-49a9-b68d-93cbe878baa1}</Project> <Project>{51cd2579-79c9-49a9-b68d-93cbe878baa1}</Project>
<Name>QControlKit</Name> <Name>QControlKit</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\QControlKitXamDemo\QControlKitXamDemo.csproj"> <ProjectReference Include="..\QControlKitXamDemo\QControlKitXamDemo.csproj">
<Project>{20D2D0D8-A518-42DC-BAB8-CF098CDF03C4}</Project> <Project>{20D2D0D8-A518-42DC-BAB8-CF098CDF03C4}</Project>
<Name>QControlKitXamDemo</Name> <Name>QControlKitXamDemo</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidAsset Include="Assets\materialdesignicons-webfont.ttf" /> <AndroidAsset Include="Assets\materialdesignicons-webfont.ttf" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<ProjectExtensions> <ProjectExtensions>
<VisualStudio> <VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionQControlKitXamDemoAndroidHideInfoBar="True" /> <UserProperties XamarinHotReloadDebuggerTimeoutExceptionQControlKitXamDemoAndroidHideInfoBar="True" />
</VisualStudio> </VisualStudio>
</ProjectExtensions> </ProjectExtensions>
</Project> </Project>
@@ -5,6 +5,8 @@ using System.Linq;
using Foundation; using Foundation;
using UIKit; using UIKit;
using Serilog; using Serilog;
using Serilog.Events;
namespace QControlKitXamDemo.iOS namespace QControlKitXamDemo.iOS
{ {
// The UIApplicationDelegate for the application. This class is responsible for launching the // The UIApplicationDelegate for the application. This class is responsible for launching the
@@ -23,7 +25,7 @@ namespace QControlKitXamDemo.iOS
public override bool FinishedLaunching(UIApplication app, NSDictionary options) public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{ {
global::Xamarin.Forms.Forms.Init(); global::Xamarin.Forms.Forms.Init();
Log.Logger = new LoggerConfiguration().WriteTo.NSLog().MinimumLevel.Information().CreateLogger(); Log.Logger = new LoggerConfiguration().WriteTo.NSLog(outputTemplate: "[{Level}] ({SourceContext}) {Message}{NewLine}{Exception}").MinimumLevel.Verbose().CreateLogger();
LoadApplication(new App()); LoadApplication(new App());
return base.FinishedLaunching(app, options); return base.FinishedLaunching(app, options);
@@ -126,15 +126,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog"> <PackageReference Include="Serilog">
<Version>2.10.0</Version> <Version>2.12.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Serilog.Sinks.Xamarin"> <PackageReference Include="Serilog.Sinks.Xamarin">
<Version>0.2.0.64</Version> <Version>1.0.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="SuperSimpleTcp"> <PackageReference Include="SuperSimpleTcp">
<Version>2.6.0</Version> <Version>3.0.0.2</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
@@ -7,6 +7,7 @@ using Xamarin.Forms;
using QControlKit.Events; using QControlKit.Events;
using QControlKit.Constants; using QControlKit.Constants;
using Serilog;
namespace QControlKitXamDemo namespace QControlKitXamDemo
{ {
@@ -20,25 +21,44 @@ namespace QControlKitXamDemo
connectedWorkspace = workspace; connectedWorkspace = workspace;
connectedWorkspace.WorkspaceUpdated += Workspace_WorkspaceUpdated; connectedWorkspace.WorkspaceUpdated += Workspace_WorkspaceUpdated;
connectedWorkspace.WorkspaceConnectionError += ConnectedWorkspace_WorkspaceConnectionError; connectedWorkspace.WorkspaceConnectionError += ConnectedWorkspace_WorkspaceConnectionError;
connectedWorkspace.WorkspaceConnected += ConnectedWorkspace_WorkspaceConnected; ;
connectedWorkspace.connect(passcode); connectedWorkspace.connect(passcode);
connectedWorkspace.defaultSendUpdatesOSC = true; connectedWorkspace.defaultSendUpdatesOSC = true;
connectedWorkspace.CueListChangedPlaybackPosition += ConnectedWorkspace_CueListChangedPlaybackPosition; connectedWorkspace.CueListChangedPlaybackPosition += ConnectedWorkspace_CueListChangedPlaybackPosition;
} }
protected override void OnDisappearing() protected override void OnDisappearing()
{ {
base.OnDisappearing(); base.OnDisappearing();
connectedWorkspace.disconnect(); connectedWorkspace.disconnect();
connectedWorkspace.WorkspaceUpdated -= Workspace_WorkspaceUpdated; connectedWorkspace.WorkspaceUpdated -= Workspace_WorkspaceUpdated;
connectedWorkspace.WorkspaceConnectionError -= ConnectedWorkspace_WorkspaceConnectionError; connectedWorkspace.WorkspaceConnectionError -= ConnectedWorkspace_WorkspaceConnectionError;
connectedWorkspace.WorkspaceConnected -= ConnectedWorkspace_WorkspaceConnected;
connectedWorkspace.CueListChangedPlaybackPosition -= ConnectedWorkspace_CueListChangedPlaybackPosition; connectedWorkspace.CueListChangedPlaybackPosition -= ConnectedWorkspace_CueListChangedPlaybackPosition;
} }
private async void ConnectedWorkspace_WorkspaceConnected(object source, QWorkspaceConnectedArgs args)
{
if (args.status.Equals("ok") && connectedWorkspace.connectedToQLab5)
{
if (!args.data.Contains(QConnectionRole.View))
{
string passcode = await DisplayPromptAsync("PIN does not have view permissions.", "Try Again", maxLength: 4, keyboard: Keyboard.Numeric);
if (passcode != null)
{
connectedWorkspace.connect(passcode);
}
}
}
}
private async void ConnectedWorkspace_WorkspaceConnectionError(object source, QWorkspaceConnectionErrorArgs args) private async void ConnectedWorkspace_WorkspaceConnectionError(object source, QWorkspaceConnectionErrorArgs args)
{ {
if (args.status.Equals("badpass")) if (args.status.Equals("badpass"))
{ {
string passcode = await DisplayPromptAsync("Passcode was incorrect.", "Try Again", maxLength: 4, keyboard: Keyboard.Numeric); string passcode = await DisplayPromptAsync("PIN is incorrect.", "Try Again", maxLength: 4, keyboard: Keyboard.Numeric);
if (passcode != null) if (passcode != null)
{ {
connectedWorkspace.connect(passcode); connectedWorkspace.connect(passcode);
@@ -53,7 +73,7 @@ namespace QControlKitXamDemo
QCue selectedCue = connectedWorkspace.cueWithID(args.cueID); QCue selectedCue = connectedWorkspace.cueWithID(args.cueID);
if(selectedCue != null) //TODO: I think this being null is the result of a race condition? if(selectedCue != null) //TODO: I think this being null is the result of a race condition?
{ {
connectedWorkspace.fetchDefaultPropertiesForCue(selectedCue); connectedWorkspace.fetchBasicPropertiesForCue(selectedCue);
selectedCueGrid.BindingContext = new QCueViewModel(selectedCue, false); selectedCueGrid.BindingContext = new QCueViewModel(selectedCue, false);
if (cueGridDict.ContainsKey(args.cueID)) if (cueGridDict.ContainsKey(args.cueID))
{ {
@@ -64,6 +84,18 @@ namespace QControlKitXamDemo
}); });
} }
void updateCuePropertes(QCue cue)
{
cue.workspace.fetchDefaultPropertiesForCue(cue);
if (cue.cues.Count > 0)
{
foreach (QCue childCue in cue.cues)
{
childCue.workspace.fetchDefaultPropertiesForCue(childCue);
}
}
}
void Workspace_WorkspaceUpdated(object source, QWorkspaceUpdatedArgs args) void Workspace_WorkspaceUpdated(object source, QWorkspaceUpdatedArgs args)
{ {
if(connectedWorkspace.cueLists.Count > 0) if(connectedWorkspace.cueLists.Count > 0)
@@ -71,6 +103,7 @@ namespace QControlKitXamDemo
List<Task> cueAddTasks = new List<Task>(); List<Task> cueAddTasks = new List<Task>();
foreach(var aCue in connectedWorkspace.cueLists) foreach(var aCue in connectedWorkspace.cueLists)
{ {
updateCuePropertes(aCue);
Grid cueGrid = cueToGrid(aCue); Grid cueGrid = cueToGrid(aCue);
cueGridDict.Add(aCue.uid, cueGrid); cueGridDict.Add(aCue.uid, cueGrid);
MainThread.InvokeOnMainThreadAsync(() => MainThread.InvokeOnMainThreadAsync(() =>
@@ -95,7 +128,7 @@ namespace QControlKitXamDemo
HorizontalOptions = LayoutOptions.StartAndExpand, HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalTextAlignment = TextAlignment.Center, VerticalTextAlignment = TextAlignment.Center,
}; };
cueLabel.SetBinding(Label.TextProperty, "status", BindingMode.OneWay); cueLabel.SetBinding(Label.TextProperty, "name", BindingMode.OneWay);
var cueBackground = new Frame var cueBackground = new Frame
{ {
BindingContext = qCueViewModel, BindingContext = qCueViewModel,
@@ -30,7 +30,7 @@ namespace QControlKitXamDemo
qBrowserViewModel.autoUpdate = false; qBrowserViewModel.autoUpdate = false;
QWorkspace selectedWorkspace = (e.SelectedItem as QWorkspaceViewModel).workspace; QWorkspace selectedWorkspace = (e.SelectedItem as QWorkspaceViewModel).workspace;
Log.Debug($"[demo] workspace: {selectedWorkspace.nameWithoutPathExtension} has been selected"); Log.Debug($"workspace: {selectedWorkspace.nameWithoutPathExtension} has been selected");
((ListView)sender).SelectedItem = null; ((ListView)sender).SelectedItem = null;
if (selectedWorkspace.hasPasscode) if (selectedWorkspace.hasPasscode)
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
@@ -11,10 +11,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2622" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
<PackageReference Include="Acr.UserDialogs" Version="7.2.0.562" /> <PackageReference Include="Acr.UserDialogs" Version="7.2.0.564" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -13,11 +13,15 @@ namespace QControlKitXamDemo.ViewModels
{ {
public class QBrowserViewModel : INotifyPropertyChanged public class QBrowserViewModel : INotifyPropertyChanged
{ {
private ILogger _log = Log.Logger.ForContext<QBrowserViewModel>();
QBrowser browser; QBrowser browser;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
public bool autoUpdate = false; public bool autoUpdate = false;
public ObservableCollection<QServerViewModel> ServersGrouped { get; set; } public ObservableCollection<QServerViewModel> ServersGrouped { get; set; }
bool connected = false;
public QBrowserViewModel(QBrowser browser) public QBrowserViewModel(QBrowser browser)
{ {
this.browser = browser; this.browser = browser;
@@ -31,9 +35,10 @@ namespace QControlKitXamDemo.ViewModels
{ {
if (autoUpdate) if (autoUpdate)
{ {
_log.Verbose($"Auto Update is enabled running probe");
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
Log.Debug("[QBrowserViewModel] QBrowser probe triggered"); _log.Debug("QBrowser probe triggered");
this.browser.ProbeForQLabInstances(); this.browser.ProbeForQLabInstances();
}); });
} }
@@ -47,7 +52,7 @@ namespace QControlKitXamDemo.ViewModels
{ {
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
Log.Debug($"[QBrowserViewModel] adding server: {args.server.description}"); _log.Debug($"adding server: {args.server.description}");
ServersGrouped.Add(new QServerViewModel(args.server)); ServersGrouped.Add(new QServerViewModel(args.server));
}); });
} }
@@ -70,7 +75,7 @@ namespace QControlKitXamDemo.ViewModels
{ {
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
Log.Debug($"[QBrowserViewModel] removing server: {args.server.description}"); _log.Debug($"removing server: {args.server.description}");
ServersGrouped.Remove(serverToRemove); ServersGrouped.Remove(serverToRemove);
}); });
} }
@@ -85,7 +90,7 @@ namespace QControlKitXamDemo.ViewModels
{ {
if (!autoUpdate) if (!autoUpdate)
{ {
Log.Debug("[QBrowserViewModel] Manual Scan Initiated"); _log.Debug("Manual Scan Initiated");
browser.ProbeForQLabInstances(); browser.ProbeForQLabInstances();
} }
} }
@@ -11,6 +11,8 @@ namespace QControlKitXamDemo.ViewModels
{ {
public class QCueViewModel : INotifyPropertyChanged public class QCueViewModel : INotifyPropertyChanged
{ {
private ILogger _log = Log.Logger.ForContext<QCueViewModel>();
QCue cue; QCue cue;
bool isSelected = false; bool isSelected = false;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
@@ -18,16 +20,20 @@ namespace QControlKitXamDemo.ViewModels
public QCueViewModel(QCue cue, bool checkPlayback) public QCueViewModel(QCue cue, bool checkPlayback)
{ {
this.cue = cue; this.cue = cue;
if(checkPlayback) if (checkPlayback)
{
this.cue.workspace.CueListChangedPlaybackPosition += Workspace_CueListChangedPlaybackPosition; this.cue.workspace.CueListChangedPlaybackPosition += Workspace_CueListChangedPlaybackPosition;
}
this.cue.CuePropertiesUpdated += OnCuePropertiesUpdated; this.cue.CuePropertiesUpdated += OnCuePropertiesUpdated;
} }
private void OnCuePropertiesUpdated(object source, QCuePropertiesUpdatedArgs args) private void OnCuePropertiesUpdated(object source, QCuePropertiesUpdatedArgs args)
{ {
foreach(var property in args.properties) _log.Debug($"properties updated from cue");
foreach (var property in args.properties)
{ {
Log.Debug($"[cueviewmodel] property <{property}> has been updated for cue {name}."); _log.Debug($"property <{property}> has been updated for cue {name}.");
if (property.Equals(QOSCKey.Name) || property.Equals(QOSCKey.ListName)) if (property.Equals(QOSCKey.Name) || property.Equals(QOSCKey.ListName))
{ {
OnPropertyChanged("name"); OnPropertyChanged("name");
@@ -5,11 +5,14 @@ using Xamarin.Forms;
using QControlKit; using QControlKit;
using QControlKit.Events; using QControlKit.Events;
using Serilog;
namespace QControlKitXamDemo.ViewModels namespace QControlKitXamDemo.ViewModels
{ {
public class QServerViewModel : ObservableCollection<QWorkspaceViewModel>, INotifyPropertyChanged public class QServerViewModel : ObservableCollection<QWorkspaceViewModel>, INotifyPropertyChanged
{ {
private ILogger _log = Log.Logger.ForContext<QServerViewModel>();
QServer server; QServer server;
public new event PropertyChangedEventHandler PropertyChanged; public new event PropertyChangedEventHandler PropertyChanged;
public string name public string name
+9
View File
@@ -2,6 +2,15 @@
This is an unofficial .NET library port of Figure53's [QLabKit](https://github.com/Figure53/QLabKit.objc). This is an unofficial .NET library port of Figure53's [QLabKit](https://github.com/Figure53/QLabKit.objc).
## Components
- **QBrowser**: Searches for an maintains a list of QLab "instances" using bonjour
- **QClient**: Wrapper over underlying TCP/UDP connection as well as providing event handlers for common QLab replies (i.e. cue needs updated, workspace need updated, etc.)
- **QCue**: The most basic object in QLab, contains shared properties and methods for a cue in a cuelist (cuelists are also a cue) is extended for other cue types (Text, Midi, etc.)
- **QMessage**: Wrapper over underlying OSC Message, has methods to quickly determine the message type and parts (Workspace info, address, arguments, etc.)
- **QServer**: This class represents a computer that is running QLab, it has host information (IP, Port) as well as discovered workspaces on that computer.
- **QWorkspace**: This class represents a workspace on a QServer, it contains methods and properties to interact with a workspace as well as access the underlying QCues and QClient that is facilitating communication
## Todo: give a basic example of the workflow ## Todo: give a basic example of the workflow