35 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
jwetzell ff498f83d0 * SlipFrame.cs:
* TCPClient.cs: Split SLIP frame stuff to separate class

* QControlKit.csproj: Bump version number
2022-02-17 17:45:59 -06:00
jwetzell 19794f40a7 * App.xaml:
* App.xaml.cs:
* MainPage.xaml:
* Info.plist:
* MainPage.xaml.cs:
* AppDelegate.cs:
* CueListPage.xaml.cs:
* QControlKitXamDemo.csproj:
* QCueViewModel.cs:
* QServerViewModel.cs:
* QBrowserViewModel.cs:
* QWorkspaceViewModel.cs:
* QControlKitXamDemo.iOS.csproj:
* QControlKitXamDemo.Android.csproj:
* materialdesignicons-webfont.ttf:
* materialdesignicons-webfont.ttf: Update demo to look a bit closer to
  real

* QClient.cs:
* QBrowser.cs: Change log levels

* QControlKit.csproj: Update Package Version

* QCue.cs: decent fix for bool property fetching

* QMessage.cs: add ToString()

* QServer.cs: Add Logging

* TCPClient.cs: Big Restructure using SuperSimpleTcp and SlipDecode
  GAH!

* QWorkspace.cs: Add ability to address active cues
2022-02-17 17:27:05 -06:00
jwetzell 6057105f44 * QCue.cs:
* QWorkspace.cs:
* QControlKitXamDemo.csproj:
* QControlKitXamDemo.iOS.csproj:
* QControlKitXamDemo.Android.csproj: Update Dependencies

* QTextCue.cs: Add QTextCue Class
2022-02-03 13:45:19 -06:00
jwetzell 66ae48cf33 * QMessage.cs:
* QWorkspace.cs:
* QEventHandlers.cs: Cleanup

* QControlKit.csproj:
* QControlKitXamDemo.csproj:
* QControlKitXamDemo.iOS.csproj:
* QControlKitXamDemo.Android.csproj: Update packages
2021-12-28 10:18:58 -06:00
jwetzell 9601d59d81 * QBrowser.cs: Change log levels
* QClient.cs: Change to Queue for TCP sending messages

* QControlKit.csproj:
* Info.plist:
* MainPage.xaml.cs:
* QControlKitXamDemo.csproj:
* QControlKitXamDemo.iOS.csproj:
* QControlKitXamDemo.Android.csproj: Updates

* TCPClient.cs: Start work on send queue for TCP
2021-11-18 00:42:31 -06:00
jwetzell 406bcc9e7f Update publish.yml 2021-09-05 11:32:25 -05:00
jwetzell 015913b2a1 Update version number 2021-09-05 11:28:43 -05:00
jwetzell 6f20d7ba19 * QControlKit.csproj: Update Packages
* QBrowser.cs: Fix zeroconf server loss detection
2021-09-04 10:41:49 -05:00
jwetzell 6dd0f766e8 Add Workspace Event Handlers 2021-07-28 09:07:55 -05:00
jwetzell bd099a2162 Bump version number 2021-07-28 08:32:03 -05:00
jwetzell effd9d3f92 Add Connection Error call for client not connected 2021-07-28 08:30:50 -05:00
jwetzell e7f60ba782 Add Group Cue class. Closes #19 2021-07-18 11:40:29 -05:00
jwetzell cd6115bfbc Add Network Cue class. Closes #12 2021-07-17 20:33:27 -05:00
jwetzell e455b42b72 Add Network Cue class. Closes #6. 2021-07-17 20:10:28 -05:00
jwetzell 6f683ba6e8 Update dependencies 2021-07-17 08:23:58 -05:00
jwetzell 3656a9f9b7 Add QMicCue class. Closes #10 2021-07-16 14:10:09 +00:00
jwetzell c750297cf5 Start work on adding missing OSC Methods 2021-07-15 22:36:30 -05:00
jwetzell b639dc401f Only publish on release 2021-07-14 09:54:03 -05:00
jwetzell 96adbadbd7 * QCue.cs: Add child cue sorting
* QControlKit.csproj: Update Versions

* QMessage.cs:
* QWorkspace.cs:
* CueListPage.xaml.cs: Cleanup TODO's and Logging
2021-06-20 18:50:39 -05:00
43 changed files with 2208 additions and 682 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Publish NuGet
# You may pin to the exact commit or the version.
# uses: brandedoutcast/publish-nuget@c12b8546b67672ee38ac87bea491ac94a587f7cc
uses: brandedoutcast/publish-nuget@v2.5.5
uses: alirezanet/publish-nuget@v3.0.4
with:
# Filepath of the project to be packaged, relative to root of repository
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.
+400 -55
View File
@@ -11,6 +11,7 @@
public const string Cue = "Cue";
public const string CueList = "Cue List";
public const string Cart = "Cart";
public const string CueCart = "Cue Cart";
public const string Group = "Group";
public const string Audio = "";
public const string Mic = "Mic";
@@ -46,73 +47,231 @@
public static class QOSCKey
{
public const string UID = "uniqueID";
public const string Type = "type";
public const string Parent = "parent";
public const string Name = "name";
public const string Number = "number";
public const string Notes = "notes";
public const string FileTarget = "fileTarget";
public const string CueTargetNumber = "cueTargetNumber";
public const string CurrentCueTarget = "currentCueTarget";
public const string ColorName = "colorName";
public const string Flagged = "flagged";
public const string ActionElapsed = "actionElapsed";
public const string AllowsEditingDuration = "allowsEditingDuration";
public const string Armed = "armed";
public const string AutoLoad = "autoLoad"; //implement on QCue
public const string CartPosition = "cartPosition";
public const string ColorName = "colorName";
public const string Cues = "cues";
public const string CueTargetNumber = "cueTargetNumber";
public const string CueTargetId = "cueTargetId"; //implement on QCue
public const string CurrentCueTarget = "currentCueTarget";
public const string CurrentFileTime = "currentFileTime"; //implement on QCue
public const string ContinueMode = "continueMode";
public const string CurrentDuration = "currentDuration";
public const string DefaultName = "defaultName"; //implement on QCue
public const string DisplayName = "displayName"; //implement on QCue
public const string DuckLevel = "duckLevel"; //implement on QCue
public const string DuckOthers = "duckOthers"; //implement on QCue
public const string DuckTime = "duckTime"; //implement on QCue
public const string FileTarget = "fileTarget";
public const string Flagged = "flagged";
public const string FadeAndStopOthers = "fadeAndStopOthers"; //implement on QCue
public const string FadeAndStopOthersTime = "fadeAndStopOthersTime"; //implement on QCue
public const string HasFileTargets = "hasFileTargets";
public const string HasCueTargets = "hasCueTargets";
public const string IsActionRunning = "isActionRunning"; //implement on QCue
public const string IsBroken = "isBroken";
public const string IsLoaded = "isLoaded";
public const string IsOverridden = "isOverridden";
public const string IsPanicking = "isPanicking";
public const string IsPaused = "isPaused";
public const string IsRunning = "isRunning";
public const string IsTailingOut = "isTailingOut";
public const string ListName = "listName";
public const string LoadAt = "loadAt"; //implement on QCue
public const string LoadActionAt = "loadActionAt"; //implement on QCue
public const string MaxTimeInCueSequence = "maxTimeInCueSequence"; //implement on QCue
public const string Name = "name";
public const string Notes = "notes";
public const string Number = "number";
public const string PanicInTime = "panicInTime"; //implement on QCue
public const string PreWait = "preWait";
public const string PostWait = "postWait";
public const string CurrentDuration = "currentDuration";
public const string PercentPreWaitElapsed = "percentPreWaitElapsed";
public const string PercentPostWaitElapsed = "percentPostWaitElapsed";
public const string PercentActionElapsed = "percentActionElapsed";
public const string PreWaitElapsed = "preWaitElapsed";
public const string Parent = "parent";
public const string PostWaitElapsed = "postWaitElapsed";
public const string ActionElapsed = "actionElapsed";
public const string GroupMode = "mode";
public const string CartPosition = "cartPosition";
public const string CartRows = "cartRows";
public const string CartColumns = "cartColumns";
public const string HasFileTargets = "hasFileTargets";
public const string HasCueTargets = "hasCueTargets";
public const string AllowsEditingDuration = "allowsEditingDuration";
public const string IsPanicking = "isPanicking";
public const string IsTailingOut = "isTailingOut";
public const string IsRunning = "isRunning";
public const string IsLoaded = "isLoaded";
public const string IsPaused = "isPaused";
public const string IsBroken = "isBroken";
public const string IsOverridden = "isOverridden";
public const string TranslationX = "translationX";
public const string TranslationY = "translationY";
public const string ScaleX = "scaleX";
public const string ScaleY = "scaleY";
public const string OriginX = "originX";
public const string OriginY = "originY";
public const string Quaternion = "quaternion";
public const string SurfaceSize = "surfaceSize";
public const string CueSize = "cueSize";
public const string PreserveAspectRatio = "preserveAspectRatio";
public const string Layer = "layer";
public const string Patch = "patch";
public const string PatchList = "patchList";
public const string SurfaceList = "surfaceList";
public const string Cues = "cues";
public const string Children = "children";
public const string ListName = "listName";
public const string SurfaceID = "surfaceID";
public const string FullSurface = "fullSurface";
public const string Opacity = "opacity";
public const string RotationZ = "rotationZ";
public const string RotationY = "rotationY";
public const string RotationX = "rotationX";
public const string PlaybackPositionId = "playbackPositionId";
public const string StartNextCueWhenSliceEnds = "startNextCueWhenSliceEnds";
public const string StopTargetWhenSliceEnds = "stopTargetWhenSliceEnds";
public const string SliderLevel = "sliderLevel";
public const string SecondTriggerAction = "secondTriggerAction"; //implement on QCue
public const string SecondTriggerOnRelease = "secondTriggerOnRelease"; //implement on QCue
public const string SoloCueInTime = "soloCueInTime"; //implement on QCue
public const string TempCueTargetNumber = "tempCueTargetNumber"; //implement on QCue
public const string TempCueTargetId = "tempCueTargetId"; //implement on QCue
public const string TempDuration = "tempDuration"; //implement on QCue
public const string Type = "type";
public const string UID = "uniqueID";
public const string UniqueID = "uniqueID";
public const string ValuesForKeys = "valuesForKeys";
public const string ValuesForKeysWithArguments = "valuesForKeysWithArguments";
//Group Cue Keys
public const string CartColumns = "cartColumns";
public const string CartRows = "cartRows";
public const string Children = "children";
public const string Go = "go";
public const string Mode = "mode";
public const string MoveCartCue = "moveCartCue";
public const string PlaybackPositionId = "playbackPositionId";
public const string PlaybackPosition = "playbackPosition"; //Implement on Group Cue
public const string PlayheadId = "playheadId"; //Implement on Group Cue
public const string Playhead = "playhead"; //Implement on Group Cue
//Audio Cue Keys
public const string DoFade = "doFade"; //Implement on Audio Cue and Video Cue
public const string DoPitchShift = "doPitchShift"; //Implement on Audio Cue and Video Cue
public const string EndTime = "endTime"; //Implement on Audio Cue and Video Cue
public const string Gang = "gang"; //Implement on Audio Cue and Video Cue
public const string InfiniteLoop = "infiniteLoop"; //Implement on Audio Cue and Video Cue
public const string Level = "level"; //Implement on Audio Cue and Video Cue
public const string Levels = "levels"; //Implement on Audio Cue and Video Cue
public const string LiveAverageLevel = "liveAverageLevel"; //Implement on Audio Cue and Video Cue
public const string LockFadeToCue = "lockFadeToCue"; //Implement on Audio Cue and Video Cue
public const string NumChannelsIn = "numChannelsIn"; //Implement on Audio Cue and Video Cue
public const string PatchList = "patchList"; //Implement on Audio Cue and Video Cue
public const string PlayCount = "playCount"; //Implement on Audio Cue and Video Cue
public const string LiveRate = "liveRate"; //Implement on Audio Cue and Video Cue
public const string SliceMarkers = "sliceMarkers"; //Implement on Audio Cue and Video Cue
public const string SliceMarker = "sliceMarker"; //Implement on Audio Cue and Video Cue
public const string Time = "time"; //Implement on Audio Cue related to slicemarker and Video Cue
public const string LastSlicePlayCount = "lastSlicePlayCount"; //Implement on Audio Cue and Video Cue
public const string LastSliceInfiniteLoop = "lastSliceInfiniteLoop"; //Implement on Audio Cue and Video Cue
public const string SliderLevel = "sliderLevel"; // Implement on Video Cue
public const string SliderLevels = "sliderLevels"; //Implement on Audio Cue and Video Cue
public const string StartTime = "startTime"; // Implement on Audio Cue and Video Cue
//Mic Cue Keys
public const string ChannelOffset = "channelOffset"; //Implement on Mic Cue
public const string Channels = "channels"; //Implement on Mic Cue
//Video Cue Keys
public const string CueSize = "cueSize"; //Implement on Video Cue
public const string DoEffect = "doEffect"; //Implement on Video Cue
public const string EffectIndex = "effectIndex"; //Implement on Video Cue
public const string EffectSet = "effectSet"; //Implement on Video Cue
public const string liveEffectSet = "liveEffectSet"; //Implement on Video Cue
public const string FullSurface = "fullSurface"; //Implement on Video Cue
public const string HoldLastFrame = "holdLastFrame"; //Implement on Video Cue
public const string Layer = "layer"; //Implement on Video Cue
public const string Opacity = "opacity"; //Implement on Video Cue
public const string OriginX = "originX"; //Implement on Video Cue
public const string OriginY = "originY"; //Implement on Video Cue
public const string Origin = "origin"; //Implement on Video Cue
public const string PreserveAspectRatio = "preserveAspectRatio"; //Implement on Video Cue
public const string Quaternion = "quaternion"; //Implement on Video Cue
public const string RotateZ = "rotateZ"; //Implement on Video Cue
public const string RotateY = "rotateY"; //Implement on Video Cue
public const string RotateX = "rotateX"; //Implement on Video Cue
public const string LiveRotation = "liveRotation"; //Implement on Video Cue
public const string XAxis = "Xaxis"; //Implement on Video Cue
public const string YAxis = "Yaxis"; //Implement on Video Cue
public const string ZAxis = "Zaxis"; //Implement on Video Cue
public const string X = "x"; //Implement on Video Cue
public const string Y = "y"; //Implement on Video Cue
public const string Z = "z"; //Implement on Video Cue
public const string ScaleX = "scaleX"; //Implement on Video Cue
public const string LiveScaleX = "liveScaleX"; //Implement on Video Cue
public const string ScaleY = "scaleY"; //Implement on Video Cue
public const string LiveScaleY = "liveScaleY"; //Implement on Video Cue
public const string Scale = "scale"; //Implement on Video Cue
public const string LiveScale = "liveScale"; //Implement on Video Cue
public const string SurfaceID = "surfaceID"; //Implement on Video Cue
public const string SurfaceList = "surfaceList"; //Implement on Video Cue
public const string SurfaceName = "surfaceName"; //Implement on Video Cue
public const string SurfaceSize = "surfaceSize"; //Implement on Video Cue
public const string TranslationX = "translationX";
public const string LiveTranslationX = "liveTranslationX"; //Implement on Video Cue
public const string TranslationY = "translationY";
public const string LiveTranslationY = "liveTranslationY"; //Implement on Video Cue
public const string Translation = "translation"; //Implement on Video Cue
public const string LiveTranslation = "liveTranslation"; //Implement on Video Cue
//Camera Cue Keys (Extension of Video Cue
public const string CameraPatch = "cameraPatch";
//Text Cue Keys
public const string FixedWidth = "fixedWidth"; //Implement on Text Cue
public const string Text = "text"; //Implement on Text Cue
public const string LiveText = "liveText"; //Implement on Text Cue
public const string Format = "format"; //Implement on Text Cue
public const string Alignment = "alignment"; //Implement on Text Cue
public const string FontFamily = "fontFamily"; //Implement on Text Cue
public const string FontStyle = "fontStyle"; //Implement on Text Cue
public const string FontFamilyAndStyle = "fontFamilyAndStyle"; //Implement on Text Cue
public const string FontName = "fontName"; //Implement on Text Cue
public const string FontSize = "fontSize"; //Implement on Text Cue
public const string LineSpacing = "lineSpacing"; //Implement on Text Cue
public const string Color = "color"; //Implement on Text Cue
public const string BackgroundColor = "backgroundColor"; //Implement on Text Cue
public const string StrikethroughColor = "strikethroughColor"; //Implement on Text Cue
public const string UnderlineColor = "underlineColor"; //Implement on Text Cue
public const string StrikethroughStyle = "strikethroughStyle"; //Implement on Text Cue
public const string UnderlineStyle = "underlineStyle"; //Implement on Text Cue
public const string OutputSize = "outputSize"; //Implement on Text Cue
//Light Cue Keys
public const string AlwaysCollate = "alwaysCollate";
public const string CollateAndStart = "collateAndStart";
public const string LightCommandText = "lightCommandText";
public const string Prune = "prune";
public const string PruneCommands = "pruneCommands";
public const string RemoveLightCommandsMatching = "removeLightCommandsMatching";
public const string ReplaceLightCommand = "replaceLightCommand";
public const string SafeSort = "safeSort";
public const string SafeSortCommands = "safeSortCommands";
public const string SetLight = "setLight";
public const string UpdateLightCommand = "updateLightCommand";
//Fade Cue Keys
//Network Cue Keys
public const string CustomString = "customString";
public const string MessageType = "messageType";
public const string QLabCommand = "qlabCommand";
public const string QLabCueNumber = "qlabCueNumber";
public const string QLabCueParameters = "qlabCueParameters";
public const string RawString = "rawString";
public const string UdpString = "udpString";
//MIDI Cue Keys
//Midi File Cue Keys
public const string Patch = "patch"; //Implement on Audio Cue and Video Cue and Network Cue
public const string Rate = "rate"; //Implement on Audio Cue and Video Cue
//Devamp Cue Keys
public const string startNextCueWhenSliceEnds = "startNextCueWhenSliceEnds";
public const string stopTargetWhenSliceEnds = "stopTargetWhenSliceEnds";
//Script Cue Keys
public const string ScriptSource = "scriptSource";
// v3
public const string Duration = "duration";
public const string FullScreen = "fullScreen";
public const string FullScreen = "fullScreen"; //Implement on Video Cue
}
public static class QIdentifiers
@@ -130,6 +289,17 @@
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 const string Red = "red";
@@ -143,16 +313,191 @@
public const string Default = "default"; //is this even an option?
}
enum QFadeMode
public enum QTriggerAction
{
Nothing = 0,
Panics = 1,
Stops = 2,
Hard_Stops = 3,
Hard_Stops_And_Restarts = 4
}
public enum QFadeMode
{
Absolute = 0,
Relative = 1
}
enum QContinueMode
public enum QContinueMode
{
NoContinue = 0,
AutoContinue = 1,
AutoFollow = 2
}
public enum QMSCCommand
{
GO = 1,
STOP = 2,
RESUME = 3,
TIMED_GO = 4,
LOAD = 5,
SET = 6,
FIRE = 7,
ALL_OFF =8,
RESTORE = 9,
RESET = 10,
GO_OFF = 11,
GO_OR_JAM_CLOCK = 16,
STANDBY_PLUSS = 17,
STANDBY_MINUS = 18,
SEQUENCE_PLUS = 19,
SEQUENCE_MINUS = 20,
START_CLOCK = 21,
STOP_CLOCK = 22,
ZERO_CLOCK = 23,
SET_CLOCK = 24,
MTC_CHASE_ON = 25,
MTC_CHASE_OFF = 26,
OPEN_CUE_LIST = 27,
CLOSE_CUE_LIST = 28,
OPEN_CUE_PATH = 29,
CLOSE_CUE_PATH = 30,
}
public enum QMSCCommandFormat
{
All_Types = 127,
Lighting_General = 1,
Moving_Lights = 2,
Color_Changers = 3,
Strobes = 4,
Lasers = 5,
Chasers = 6,
Sound_General = 16,
Music = 17,
CD_Players = 18,
EPROM_Playback = 19,
Audio_Tape_Machines = 20,
Intercoms = 21,
Amplifiers = 22,
Audio_Effects_Devices = 23,
Equalizers = 24,
Machinery_General = 32,
Rigging = 33,
Flys = 34,
Lifts = 35,
Turntables = 36,
Trusses = 37,
Robots = 38,
Animation = 39,
Floats = 40,
Breakaways = 41,
Barges = 42,
Video_General = 48,
Video_Tape_Machines = 49,
Video_Cassette_Machines = 50,
Video_Disc_Players = 51,
Video_Switchers = 52,
Video_Effects = 53,
Video_Character_Generators = 54,
Video_Still_Stores = 55,
Video_Monitors = 56,
Projection_General = 64,
Film_Projectors = 65,
Slide_Projectors = 66,
Video_Projectors = 67,
Dissolvers = 68,
Shutter_Controls = 69,
Process_Control_General = 80,
Hydraulic_Oil = 81,
H2O = 82,
CO2 = 83,
Compressed_Air = 84,
Natural_Gas = 85,
Fog = 86,
Smoke = 87,
Cracked_Haze = 88,
Pyrotechnics_General = 96,
Fireworks = 97,
Explosions = 98,
Flame = 99,
Smoke_Pots = 100
}
public enum QNetworkMessageType
{
QLAB = 1,
OSC = 2,
UDP = 3
}
public enum QNetworkQLabCommand
{
start = 1,
stop = 2,
hardStop = 3,
pause = 4,
resume = 5,
togglePause = 6,
load = 7,
preview = 8,
reset = 9,
panic = 10,
number = 11,
name = 12,
notes = 13,
cueTargetNumber = 14,
preWait = 15,
duration = 16,
postWait = 17,
continueMode = 18,
flagged = 19,
armed = 20,
colorName = 21
}
public enum QRotationType
{
THREE_D = 0,
X = 1,
Y = 2,
Z = 3
}
public enum QVideoEffect
{
Color_Controls = 1,
Exposure = 2,
Gamma = 3,
Sepia_Monochrome = 4,
Min_Max_Invert = 5,
White_point = 6,
Box_Disc_Gaussian_Blurs = 7,
Motion_Blur = 8,
Sharpen_Luminance = 9,
Unsharp_Mask = 10,
Zoom_Blur = 11,
Pixellation = 12,
Screen = 13,
Bloom_and_Gloom = 14,
CMYK_Halftone = 15,
Color_Posterize = 16,
Crystallize_and_Pointillize = 17,
Edge_Work = 18,
Kaleidoscope = 1,
Median_and_Comic_Effect = 19,
Noise_Reduction = 20,
Circle_Splash_Hole_Distortion = 21,
Pinch_Bump_Distortion = 22,
Torus_Lens_Distortion = 23,
Twirl_Circular_Wrap_Vortex = 24,
Glass_Lozenge = 25,
Op_Tile = 26,
Perspective_Tile = 27,
Quad_Tiles = 28,
Reflected_Tiles = 29,
Rotated_Tiles = 30,
Titles = 31
}
}
+31
View File
@@ -0,0 +1,31 @@
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QDevampCue : QCue
{
public bool startNextCueWhenSliceEnds
{
get
{
return (bool)propertyForKey(QOSCKey.startNextCueWhenSliceEnds);
}
set
{
setProperty(value, QOSCKey.startNextCueWhenSliceEnds);
}
}
public bool stopTargetWhenSliceEnds
{
get
{
return (bool)propertyForKey(QOSCKey.stopTargetWhenSliceEnds);
}
set
{
setProperty(value, QOSCKey.stopTargetWhenSliceEnds);
}
}
}
}
+117
View File
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QGroupCue : QCue
{
public int cartColumns
{
get
{
return (int)propertyForKey(QOSCKey.CartColumns);
}
set
{
setProperty(value, QOSCKey.CartColumns);
}
}
public int cartRows
{
get
{
return (int)propertyForKey(QOSCKey.CartRows);
}
set
{
setProperty(value, QOSCKey.CartRows);
}
}
public List<QCue> children
{
get
{
return cues;
}
}
public void go()
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.Go}");
}
public int mode
{
get
{
return (int)propertyForKey(QOSCKey.Mode);
}
set
{
if(value > 0 && value < 5)
{
setProperty(value, QOSCKey.Mode);
}
}
}
public void moveCartCue(QCue cart, int row, int column)
{
if(cart.type.Equals(QCueType.CueCart) || cart.type.Equals(QCueType.Cart))
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.MoveCartCue}/{cart.uid}", row, column);
}
}
public string playhead
{
get
{
return propertyForKey(QOSCKey.Playhead).ToString();
}
set
{
setProperty(value, QOSCKey.Playhead);
}
}
public string playheadId
{
get
{
return propertyForKey(QOSCKey.PlayheadId).ToString();
}
set
{
setProperty(value, QOSCKey.PlayheadId);
}
}
public string playbackPosition
{
get
{
return propertyForKey(QOSCKey.PlaybackPosition).ToString();
}
set
{
setProperty(value, QOSCKey.PlaybackPosition);
}
}
public string playbackPositionId
{
get
{
return propertyForKey(QOSCKey.PlaybackPositionId).ToString();
}
set
{
setProperty(value, QOSCKey.PlaybackPositionId);
}
}
}
}
+91
View File
@@ -0,0 +1,91 @@
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QLightCue : QCue
{
public bool alwaysCollate
{
get
{
return (bool)propertyForKey(QOSCKey.AlwaysCollate);
}
set
{
setProperty(value, QOSCKey.AlwaysCollate);
}
}
public string lightCommandText
{
get
{
return propertyForKey(QOSCKey.LightCommandText).ToString();
}
set
{
setProperty(value, QOSCKey.LightCommandText);
}
}
public void collateAndStart()
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.CollateAndStart}");
}
public void prune()
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.Prune}");
}
public void pruneCommands()
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.PruneCommands}");
}
public void removeLightCommandsMatching(string match)
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.RemoveLightCommandsMatching}", match);
}
public void replaceLightCommand(string oldCommand, string newCommand)
{
if(this.workspace.versionParts[0] == "4" && int.Parse(this.workspace.versionParts[1]) >= 4)
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.ReplaceLightCommand}", oldCommand, newCommand);
}
else
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.UpdateLightCommand}", oldCommand, newCommand);
}
}
public void safeSort()
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.SafeSort}");
}
public void safeSortCommands()
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.SafeSortCommands}");
}
public void setLight(string light, object setting)
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.SetLight}", light, setting);
}
public void updateLightCommand(string oldCommand, string newCommand)
{
if (int.Parse(this.workspace.versionParts[0]) >= 4 && int.Parse(this.workspace.versionParts[1]) >= 4)
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.ReplaceLightCommand}", oldCommand, newCommand);
}
else
{
workspace.sendMessage($"/cue_id/{this.uid}/{QOSCKey.UpdateLightCommand}", oldCommand, newCommand);
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QMicCue : QCue
{
public int channelOffset
{
get
{
return (int)propertyForKey(QOSCKey.ChannelOffset);
}
}
public int channels
{
get
{
return (int)propertyForKey(QOSCKey.Channels);
}
set
{
setProperty(value, QOSCKey.Channels);
}
}
public void setDefaultLevels()
{
workspace.sendMessage($"/cue_id/{this.uid}/setDefaultLevels");
}
public void setSilientLevels()
{
workspace.sendMessage($"/cue_id/{this.uid}/setSilentLevels");
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QMidiFileCue :QCue
{
public bool patch
{
get
{
return (bool)propertyForKey(QOSCKey.Patch);
}
set
{
setProperty(value, QOSCKey.Patch);
}
}
public bool rate
{
get
{
return (bool)propertyForKey(QOSCKey.Rate);
}
set
{
setProperty(value, QOSCKey.Rate);
}
}
}
}
+104
View File
@@ -0,0 +1,104 @@
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QNetworkCue : QCue
{
public string customString
{
get
{
return propertyForKey(QOSCKey.CustomString).ToString();
}
set
{
setProperty(value, QOSCKey.CustomString);
}
}
public QNetworkMessageType messageType
{
get
{
return (QNetworkMessageType)propertyForKey(QOSCKey.MessageType);
}
set
{
setProperty(value, QOSCKey.MessageType);
}
}
public QNetworkQLabCommand qlabCommand
{
get
{
return (QNetworkQLabCommand)propertyForKey(QOSCKey.QLabCommand);
}
set
{
setProperty(value, QOSCKey.QLabCommand);
}
}
public int patch
{
get
{
return (int)propertyForKey(QOSCKey.Patch);
}
set
{
setProperty(value, QOSCKey.Patch);
}
}
public string qlabCueNumber
{
get
{
return propertyForKey(QOSCKey.QLabCueNumber).ToString();
}
set
{
setProperty(value, QOSCKey.QLabCueNumber);
}
}
public string qlabCueParameters
{
get
{
return propertyForKey(QOSCKey.QLabCueParameters).ToString();
}
set
{
setProperty(value, QOSCKey.QLabCueParameters);
}
}
public string rawString
{
get
{
return propertyForKey(QOSCKey.RawString).ToString();
}
set
{
setProperty(value, QOSCKey.RawString);
}
}
public string udpString
{
get
{
return propertyForKey(QOSCKey.UdpString).ToString();
}
set
{
setProperty(value, QOSCKey.UdpString);
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QScriptCue : QCue
{
public string scriptSource
{
get
{
return propertyForKey(QOSCKey.ScriptSource).ToString();
}
}
public void compileSource()
{
workspace.sendMessage($"/cue_id/{this.uid}/compileSource");
}
}
}
+69
View File
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using QControlKit.Constants;
namespace QControlKit.Cue
{
public class QTextCue : QCue
{
public int fixedWidth
{
get
{
return (int)propertyForKey(QOSCKey.FixedWidth);
}
set
{
if(fixedWidth > 0)
{
setProperty(value, QOSCKey.FixedWidth);
}
}
}
public string text
{
get
{
return propertyForKey(QOSCKey.Text).ToString();
}
set
{
setProperty(value, QOSCKey.Text);
}
}
public string liveText
{
get
{
return propertyForKey(QOSCKey.LiveText).ToString();
}
set
{
setProperty(value, QOSCKey.LiveText);
}
}
//TODO:
/* https://qlab.app/docs/v4/scripting/osc-dictionary-v4/#text-cue-methods
* /cue/{cue_number}/text/format {json_string}
* /cue/{cue_number}/text/format/alignment {alignment}
* /cue/{cue_number}/text/format/fontFamily
* /cue/{cue_number}/text/format/fontStyle
* /cue/{cue_number}/text/format/fontFamilyAndStyle {family} {style}
* /cue/{cue_number}/text/format/fontName {name}
* /cue/{cue_number}/text/format/fontSize {number}
* /cue/{cue_number}/text/format/lineSpacing {number}
* /cue/{cue_number}/text/format/color {red} {green} {blue} {alpha}
* /cue/{cue_number}/text/format/backgroundColor {red} {green} {blue} {alpha}
* /cue/{cue_number}/text/format/strikethroughColor {red} {green} {blue} {alpha}
* /cue/{cue_number}/text/format/underlineColor {red} {green} {blue} {alpha}
* /cue/{cue_number}/text/format/strikethroughStyle {style}
* /cue/{cue_number}/text/format/underlineStyle {style}
* /cue/{cue_number}/text/outputSize
* /cue/{cue_number}/liveText/outputSize
*/
}
}
+3 -1
View File
@@ -48,7 +48,8 @@ namespace QControlKit.Events
public class QWorkspaceConnectedArgs : EventArgs
{
public string status { get; set; }
public string data { get; set; }
}
public class QWorkspaceDisconnectedArgs : EventArgs
@@ -59,6 +60,7 @@ namespace QControlKit.Events
public class QWorkspaceConnectionErrorArgs : EventArgs
{
public string status { get; set; }
public string data { get; set; }
}
public class QQLabPreferencesUpdatedArgs : EventArgs
{
+3 -2
View File
@@ -1,4 +1,6 @@
namespace QControlKit.Events
using System;
namespace QControlKit.Events
{
public delegate void QServerFoundHandler(object source, QServerFoundArgs args);
public delegate void QServerLostHandler(object source, QServerLostArgs args);
@@ -16,7 +18,6 @@
public delegate void QWorkspaceConnectionErrorHandler(object source, QWorkspaceConnectionErrorArgs args);
public delegate void QQLabPreferencesUpdatedHandler(object source, QQLabPreferencesUpdatedArgs args);
public delegate void QCueUpdatedHandler(object source, QCueUpdatedArgs args);
public delegate void QCuePropertiesUpdatedHandler(object source, QCuePropertiesUpdatedArgs args);
public delegate void QCueListsUpdatesHandler(object source, QCueListsUpdatedArgs args);
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>AnyCPU</LastUsedPlatform>
<publishUrl>bin/Release/netstandard2.0/publish</publishUrl>
<DeleteExistingFiles>false</DeleteExistingFiles>
<TargetFramework>netstandard2.0</TargetFramework>
<SelfContained>false</SelfContained>
<_IsPortable>true</_IsPortable>
</PropertyGroup>
</Project>
+13 -12
View File
@@ -13,6 +13,8 @@ namespace QControlKit
{
public class QBrowser
{
private ILogger _log = Log.Logger.ForContext<QBrowser>();
public ObservableCollection<QServer> servers = new ObservableCollection<QServer>();
public event QServerFoundHandler ServerFound;
@@ -26,11 +28,14 @@ namespace QControlKit
public async void ProbeForQLabInstances()
{
_log.Debug("probing for instances");
IReadOnlyList<IZeroconfHost> results = await
ZeroconfResolver.ResolveAsync(QBonjour.TCPService,TimeSpan.FromSeconds(3));
foreach (var zeroconfHost in results)
{
_log.Debug($"found host {zeroconfHost.IPAddress}:{zeroconfHost.DisplayName}");
foreach (var service in zeroconfHost.Services)
{
if (service.Key.Equals(QBonjour.TCPService))
@@ -40,7 +45,7 @@ namespace QControlKit
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);
serverToAdd.name = zeroconfHost.DisplayName;
serverToAdd.zeroconfHost = zeroconfHost;
@@ -62,19 +67,14 @@ namespace QControlKit
foreach(var server in qServers)
{
bool serverToBeRemoved = true;
foreach(var zeroconfHost in results){
QServer existingServer = serverForAddress(zeroconfHost.IPAddress);
if(server != null)
{
serverToBeRemoved = false;
}
}
if (serverToBeRemoved)
IZeroconfHost found = results.ToList().Find(x => x.IPAddress.Equals(server.host));
if (found == null)
{
_log.Information($"Lost {server.name} : {server.host} : {server.port} disconnecting");
server.disconnect();
_log.Verbose($"after server disconnect()");
servers.Remove(server);
Log.Information($"[qbrowser] Lost {server.name} : {server.host} : {server.port}");
OnServerLost(server);
}
}
@@ -123,7 +123,8 @@ namespace QControlKit
public void Close()
{
foreach(var server in servers)
_log.Information($"Close requested");
foreach (var server in servers)
{
server.disconnect();
}
+31 -32
View File
@@ -12,6 +12,8 @@ namespace QControlKit
{
public class QClient
{
private ILogger _log = Log.Logger.ForContext<QClient>();
TCPClient tcpClient;
public event QWorkspacesUpdatedHandler WorkspacesUpdated;
@@ -35,7 +37,7 @@ namespace QControlKit
public QClient(string host, int port)
{
tcpClient = new TCPClient(host, port);
Log.Debug($"[client] setup connection to: <{host}:{port}>");
_log.Debug($"setup connection to: <{host}:{port}>");
tcpClient.MessageReceived += ProcessMessage;
}
@@ -52,7 +54,7 @@ namespace QControlKit
public void disconnect()
{
Log.Debug($"[client] disconnecting from {tcpClient.Address}");
_log.Information($"disconnecting from {tcpClient.Address}");
tcpClient.Close();
}
@@ -60,7 +62,6 @@ namespace QControlKit
public void sendMessage(string address, params object[] args)
{
tcpClient.Send(new OscMessage(address, args));
Log.Debug($"[client] send message {address} : {args}");
}
private void ProcessMessage(object source, MessageEventArgs args)
@@ -70,7 +71,7 @@ namespace QControlKit
if (message.IsReply)
{
JToken data = message.response;
JToken data = message.data;
//special case, want to update cue properties
if (message.IsReplyFromCue)
@@ -84,26 +85,25 @@ namespace QControlKit
string property = message.AddressParts.Last();
if (property == null)
return;
if (property == QOSCKey.PlaybackPositionId)
{
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
return;
}
//create object manually since single value replies don't have dictionaries
JObject properties = new JObject();
properties.Add(property, data);
OnCueUpdated(message.cueID, properties);
if (property == QOSCKey.PlaybackPositionId)
{
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
}
}
else
{
Log.Warning($"[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)
{
OnCueListsUpdated(message.response);
OnCueListsUpdated(message.data);
}
else if (message.IsWorkspacesInfo)
{
@@ -111,14 +111,14 @@ namespace QControlKit
}
else if (message.IsConnect)
{
if (message.response.ToString() == "ok")
OnWorkspaceConnected();
if (message.status.ToString().Equals("ok"))
OnWorkspaceConnected(message.status.ToString(),message.data.ToString());
else
OnWorkspaceConnectionError(message.response.ToString());
OnWorkspaceConnectionError(message.status.ToString(), message.data.ToString());
}
else
{
Log.Warning($"[client] unhandled reply message: {message.address}");
_log.Error($"unhandled reply message: {message.address}");
}
}
else if(message.IsUpdate) {
@@ -150,7 +150,6 @@ namespace QControlKit
else if ( message.IsPreferencesUpdate)
{
//need to do checks for 4.2 or newer
string key = message.AddressParts.Last();
if (key == null)
return;
@@ -162,76 +161,76 @@ namespace QControlKit
}
else
{
Log.Warning($"[client] unhandled update message: {message.address}");
_log.Error($"unhandled update message: {message.address}");
}
}
else
{
Log.Warning($"[client] unhandled message: {message.address}");
_log.Error($"unhandled message: {message.address}");
}
}
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 });
}
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 });
}
protected virtual void OnCueListsUpdated(JToken response)
{
Log.Debug($"[client] Cue Lists Updated");
_log.Debug("Cue Lists Updated");
CueListsUpdated?.Invoke(this, new QCueListsUpdatedArgs { data = response });
}
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 });
}
protected virtual void OnWorkspaceUpdated()
{
//Log.Debug($"[client] Workspace Updated");
_log.Debug($"Workspace Updated");
WorkspaceUpdated?.Invoke(this, new QWorkspaceUpdatedArgs());
}
protected virtual void OnWorkspaceSettingsUpdated(string settingsType)
{
Log.Debug($"[client] Workspace Settings Updated");
_log.Debug($"Workspace Settings Updated");
WorkspaceSettingsUpdated?.Invoke(this, new QWorkspaceSettingsUpdatedArgs { settingsType = settingsType });
}
protected virtual void OnWorkspaceLightDashboardUpdated()
{
Log.Debug($"[client] Workspace Light Dashboard Updated");
_log.Debug("Workspace Light Dashboard Updated");
WorkspaceLightDashboardUpdated?.Invoke(this, new QWorkspaceLightDashboardUpdatedArgs());
}
protected virtual void OnQLabPreferencesUpdated(string key)
{
Log.Debug($"[client] QLab Preferences Updated");
_log.Debug("QLab Preferences Updated");
QLabPreferencesUpdated?.Invoke(this, new QQLabPreferencesUpdatedArgs { key = key });
}
protected virtual void OnWorkspaceDisconnected()
{
Log.Debug($"[client] Workspace Disconnected");
_log.Debug("Workspace Disconnected");
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)
@@ -239,7 +238,7 @@ namespace QControlKit
if(WorkspacesUpdated != null)
{
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());
workspaces.Add(workspacefound);
+17 -5
View File
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Authors>Joel Wetzell</Authors>
<Version>1.0.0-devel</Version>
<PackOnBuild>true</PackOnBuild>
<PackageVersion>0.0.17</PackageVersion>
<PackageVersion>0.0.33</PackageVersion>
<PackageId>QControlKit</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Summary>Unofficial C# port of Figure53's QLabKit.objc</Summary>
@@ -15,11 +15,23 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Zeroconf" Version="3.4.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="TcpSharp" Version="1.2.0" />
<PackageReference Include="Zeroconf" Version="3.5.11" />
</ItemGroup>
<ItemGroup>
<Folder Include="Constants\" />
<Folder Include="Cue\" />
<Folder Include="SharpOSC\" />
</ItemGroup>
<ItemGroup>
<None Remove="Cue\" />
<None Remove="SharpOSC" />
<None Remove="Zeroconf" />
<None Remove="SharpOSC\" />
<None Remove="SimpleTCP" />
<None Remove="SuperSimpleTcp" />
<None Remove="WatsonTcp" />
</ItemGroup>
</Project>
+145 -88
View File
@@ -12,12 +12,14 @@ namespace QControlKit
{
public class QCue : IComparable
{
private ILogger _log = Log.Logger.ForContext<QCue>();
public QWorkspace workspace;
public Dictionary<string, object> cueData;
public List<QCue> childCues;
public Dictionary<string, QCue> childCuesUIDMap;
public int sortIndex;
public int nestLevel;
@@ -43,31 +45,6 @@ namespace QControlKit
{
init();
this.workspace = workspace;
//this breaks things???
//it definitely is in the original but somehow the recursion is not working as I expect..
//but cue population still works without this as child cues are populated later
/*JToken children = dict[QOSCKey.Cues];
if(children != null && children.Type == JTokenType.Array)
{
Log.Debug($"[cue] new cue being created with {children.Count()} childCues");
foreach (var aChildDict in children)
{
string uid = (string)aChildDict[QOSCKey.UID];
//Log.Debug($"[cue] childDict with uid: {uid} being processed");
if (uid == null)
continue;
string name = dict[QOSCKey.UID].ToString();
Log.Debug($"[cue] calling new cue from {name}");
//QCue child = new QCue(aChildDict, workspace);
*//*childCues.Add(child);
childCuesUIDMap.Add(uid, child);*//*
}
}*/
updatePropertiesWithDictionary(dict);
}
@@ -76,30 +53,6 @@ namespace QControlKit
init();
this.workspace = workspace;
this.nestLevel = nestLevel + 1;
//this breaks things???
//it definitely is in the original but somehow the recursion is not working as I expect..
//but cue population still works without this as child cues are populated later
/*JToken children = dict[QOSCKey.Cues];
if(children != null && children.Type == JTokenType.Array)
{
Log.Debug($"[cue] new cue being created with {children.Count()} childCues");
foreach (var aChildDict in children)
{
string uid = (string)aChildDict[QOSCKey.UID];
//Log.Debug($"[cue] childDict with uid: {uid} being processed");
if (uid == null)
continue;
string name = dict[QOSCKey.UID].ToString();
Log.Debug($"[cue] calling new cue from {name}");
//QCue child = new QCue(aChildDict, workspace);
*//*childCues.Add(child);
childCuesUIDMap.Add(uid, child);*//*
}
}*/
updatePropertiesWithDictionary(dict);
}
@@ -186,7 +139,7 @@ namespace QControlKit
childCues.Add(cue);
childCuesUIDMap.Add(uid, cue);
//some sorting of the childCues needs to be done? //TODO
//reset sorting index
//reset sorting index?
}
public void removeChildCue(QCue cue) {
@@ -210,7 +163,6 @@ namespace QControlKit
QCue cue = childCuesUIDMap[aUid];
//Log.Debug($"Removing Child Cue From {listName} : {cue.uid} + {cue.listName}");
childCues.Remove(cue);
childCuesUIDMap.Remove(aUid);
}
@@ -244,7 +196,14 @@ namespace QControlKit
{
get
{
return propertyForKey(QOSCKey.Parent).ToString();
if(propertyForKey(QOSCKey.Parent) != null)
{
return propertyForKey(QOSCKey.Parent).ToString();
}
else
{
return "";
}
}
}
public string playbackPositionID {
@@ -301,7 +260,14 @@ namespace QControlKit
{
get
{
return propertyForKey(QOSCKey.ListName).ToString();
if(propertyForKey(QOSCKey.ListName) == null)
{
return "";
}
else
{
return propertyForKey(QOSCKey.ListName).ToString();
}
}
set
{
@@ -353,46 +319,39 @@ namespace QControlKit
if (workspace.connectedToQLab3 || workspace.isOlderThanVersion("4.2.0"))
return false;
bool overridden = (bool)propertyForKey(QOSCKey.IsOverridden);
if (overridden)
return true;
foreach (var cue in cues)
{
if (cue.IsOverridden)
return true;
}
return false;
return getBoolForKey(QOSCKey.IsOverridden);
}
}
public bool IsBroken
{
get
{
if ((bool)propertyForKey(QOSCKey.IsBroken))
return true;
foreach(var cue in cues)
foreach (var cue in cues)
{
if (cue.IsBroken)
return true;
}
return false;
return getBoolForKey(QOSCKey.IsBroken);
}
}
public bool IsRunning
{
get
{
bool running = (bool)propertyForKey(QOSCKey.IsRunning);
if (running)
return true;
foreach(var cue in cues)
foreach (var cue in cues)
{
if (cue.IsRunning)
return true;
}
return false;
return getBoolForKey(QOSCKey.IsRunning);
}
}
public bool IsTailingOut
@@ -402,16 +361,13 @@ namespace QControlKit
if (workspace.connectedToQLab3)
return false;
bool tailingOut = (bool)propertyForKey(QOSCKey.IsTailingOut);
if (tailingOut)
return true;
foreach(var cue in cues)
foreach (var cue in cues)
{
if (cue.IsTailingOut)
return true;
}
return false;
return getBoolForKey(QOSCKey.IsTailingOut);
}
}
public bool IsPanicking
@@ -421,16 +377,13 @@ namespace QControlKit
if (workspace.connectedToQLab3)
return false;
bool panicking = (bool)propertyForKey(QOSCKey.IsPanicking);
if (panicking)
return true;
foreach (var cue in cues)
{
if (cue.IsPanicking)
return true;
}
return false;
return getBoolForKey(QOSCKey.IsPanicking);
}
}
public bool IsGroup
@@ -571,6 +524,62 @@ namespace QControlKit
}
}
public float actionElapsed
{
get
{
object secs = propertyForKey(QOSCKey.ActionElapsed);
if (secs != null)
{
return float.Parse(secs.ToString());
}
else
{
return 0F;
}
}
}
public QTriggerAction secondTriggerAction
{
get
{
return (QTriggerAction)propertyForKey(QOSCKey.SecondTriggerAction);
}
set
{
setProperty(value, QOSCKey.SecondTriggerAction);
}
}
public bool secondTriggerOnRelease
{
get
{
var val = (int)propertyForKey(QOSCKey.SecondTriggerOnRelease);
if (val == 0)
{
return true;
}
else
{
return false;
}
}
set
{
if (value)
{
setProperty(0, QOSCKey.SecondTriggerOnRelease);
}
else
{
setProperty(1, QOSCKey.SecondTriggerOnRelease);
}
}
}
//TODO add quaternion property and setter
public Size surfaceSize() { throw new NotImplementedException(); }
public Size cueSize() { throw new NotImplementedException(); }
@@ -638,14 +647,12 @@ namespace QControlKit
JToken value = obj.Value;
if (obj.Key.Equals(QOSCKey.Cues))
{
//Log.Debug($"Cues OSC Key found in update message...updating child cues");
if (value.Type != JTokenType.Array)
continue;
updateChildCuesWithPropertiesArray(value, false);
}
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)
continue;
updateChildCuesWithPropertiesArray(value, true);
@@ -659,7 +666,6 @@ namespace QControlKit
{
cueUpdated = true;
propertiesUpdated.Add(obj.Key);
//Log.Debug($"[cue] cue property {obj.Key} updated with {obj.Value}");
}
}
}
@@ -730,7 +736,8 @@ namespace QControlKit
if (needsSortChildCues)
{
//something about cues needing to be sorted?
childCues.Sort((q1, q2)=> q1.sortIndex.CompareTo(q2.sortIndex));
needsSortChildCues = false;
}
return needsNotifyCueUpdated;
@@ -787,6 +794,32 @@ namespace QControlKit
}
return null;
}
public bool getBoolForKey(string key)
{
if (propertyForKey(key) != null)
{
object prop = propertyForKey(key);
//_log.Warning($"{listName} IsBroken: {propertyForKey(QOSCKey.IsBroken).ToString()} {prop.GetType().ToString()}");
if(prop.GetType() == typeof(Boolean)){
return (bool)prop;
}
else if (prop.GetType() == typeof(JValue))
{
JValue value = (JValue)prop;
return (bool)value.Value;
}
else
{
return false;
}
}
else
{
return false;
}
}
public object propertyForKey(string key)
{
//TODO: implerment special key checks color, surfaceName, patchDescription,
@@ -857,7 +890,7 @@ namespace QControlKit
}
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))
{
@@ -873,8 +906,6 @@ namespace QControlKit
else
cueData.Remove(key);
//TODO: Cuelistchangedplaybackpositionid?
}
else
{
@@ -937,6 +968,20 @@ namespace QControlKit
public void togglePause() { workspace.togglePauseCue(this); }
public void preview() { workspace.previewCue(this); }
public void panic() { workspace.panicCue(this); }
public void playheadNext()
{
if (this.type == QCueType.CueList)
{
workspace.sendMessageForCue(this, "playhead/next");
}
}
public void playheadPrevious()
{
if (this.type == QCueType.CueList)
{
workspace.sendMessageForCue(this, "playhead/previous");
}
}
#endregion
@@ -944,7 +989,19 @@ namespace QControlKit
#region Event Handling
void OnCuePropertiesUpdated(List<string> properties)
{
Log.Debug($"[cue] <{nonEmptyName}> properties have been updated.");
_log.Verbose("OnCuePropertiesUpdated");
if(parentID != null && 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 });
}
#endregion
@@ -960,7 +1017,7 @@ namespace QControlKit
{
string indent = new string(' ', level*2);
Log.Information($"{indent}\u00b7{displayName} - {uid}");
_log.Information($"{indent}\u00b7{displayName} - {uid}");
if (IsGroup)
{
level++;
+71 -13
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json.Linq;
using SharpOSC;
@@ -28,7 +29,32 @@ namespace QControlKit
public bool IsReply { get { return OSCMessage.Address.StartsWith("/reply"); } }
// /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 IsUpdate { get{ return OSCMessage.Address.StartsWith("/update"); } }
@@ -103,6 +129,7 @@ namespace QControlKit
return (parts.Length == 4 && parts[3].Equals("connect"));
}
}
#endregion
//host method
@@ -114,12 +141,25 @@ namespace QControlKit
public string[] AddressParts { get { return address.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries); } }
public JToken response
public JToken data
{
get
{
JObject responseObj = JObject.Parse((string)arguments[0]);
return responseObj["data"];
JObject dataObj = JObject.Parse((string)arguments[0]);
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;
}
}
@@ -129,23 +169,41 @@ namespace QControlKit
{
get
{
if (IsCueUpdate)
{
if (IsCueUpdate) {
return AddressParts[4];
}else if (IsPlaybackPositionUpdate)
{
} else if (IsPlaybackPositionUpdate) {
//TODO: check string cast
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];
}
else
{
} else {
return null;
}
}
}
override
public string ToString()
{
string stringified = "";
stringified += $"\nAddress: {this.address}\nArguments: \n";
for(int i = 0; i<this.arguments.Count; i++)
{
object argument = this.arguments[i];
stringified += $"\targ {i + 1}: {argument.ToString()}\n";
}
return stringified;
}
}
}
+11 -5
View File
@@ -10,11 +10,13 @@ namespace QControlKit
{
public class QServer
{
private ILogger _log = Log.Logger.ForContext<QServer>();
public event QServerUpdatedHandler ServerUpdated;
public event QServerWorkspaceAddedHandler WorkspaceAdded;
public event QServerWorkspaceRemovedHandler WorkspaceRemoved;
private QClient client;
private QClient _client;
public string host { get; set; }
public int port { get; set; }
@@ -28,18 +30,21 @@ namespace QControlKit
this.host = host;
this.port = port;
client = new QClient(host, port);
_client = new QClient(host, port);
client.WorkspacesUpdated += OnServerWorkspacesUpdated;
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 QClient client {
get { return _client; }
}
public void refreshWorkspaces()
{
client.sendMessage("/workspaces");
@@ -111,11 +116,12 @@ namespace QControlKit
public void disconnect()
{
_log.Information($"disconnect requested for server <{name}>");
foreach (var workspace in workspaces)
{
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();
}
}
+82 -82
View File
@@ -1,11 +1,8 @@
//TODO: workspace connection error handlers
using Newtonsoft.Json;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using QControlKit.Events;
using QControlKit.Constants;
@@ -13,6 +10,7 @@ namespace QControlKit
{
public class QWorkspace: IEquatable<QWorkspace>
{
private ILogger _log = Log.Logger.ForContext<QWorkspace>();
private QServer server;
private QClient client;
@@ -25,9 +23,6 @@ namespace QControlKit
private QCue root;
private Timer heartbeatTimer;
private int heartbeatAttempts;
public bool defaultSendUpdatesOSC;
public string version;
@@ -36,6 +31,8 @@ namespace QControlKit
public event QWorkspaceUpdatedHandler WorkspaceUpdated;
public event QCueListChangedPlaybackPositionHandler CueListChangedPlaybackPosition;
public event QWorkspaceConnectionErrorHandler WorkspaceConnectionError;
public event QWorkspaceDisconnectedHandler WorkspaceDisconnected;
public event QWorkspaceConnectedHandler WorkspaceConnected;
private void Init()
{
@@ -68,7 +65,7 @@ namespace QControlKit
updateWithWorkspaceInfo(workspaceInfo);
client = new QClient(server.host, server.port);
client = server.client;
client.WorkspaceConnected += OnWorkspaceConnected;
client.WorkspaceConnectionError += OnWorkspaceConnectionError;
@@ -79,7 +76,7 @@ namespace QControlKit
client.CueUpdated += OnCueUpdated;
this.server = server;
Log.Debug($"[workspace] <{name}> on {server.name} initialized.");
_log.Debug($"<{name}> on <{server.name}> initialized.");
}
//updateWithDictionary
@@ -160,26 +157,29 @@ namespace QControlKit
public List<QCue> cueLists { get { return root.cues; } }
public string fullNameWithCueList(QCue cueList) { return ""; }
//TODO other convenience methods
public string[] versionParts { get { return version.Split('.'); } }
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
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)
{
Log.Error($"[workspace] *** workspace <{name}> requires a passcode but none was supplied.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = "badpass" });
_log.Error($"*** workspace <{name}> requires a passcode but none was supplied.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.BadPass, data = "badpass" });
return;
}
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, data = "error" });
return;
}
@@ -193,10 +193,9 @@ namespace QControlKit
private void finishConnection()
{
//TODO
connected = true;
startReceivingUpdates();
//fetchQLabVersion();
//fetchQLabVersion(); //this is not needed since the version is loaded from the workspace info call
fetchCueLists();
}
@@ -210,30 +209,22 @@ namespace QControlKit
else
connect();
//todo
}
public void disconnect()
{
Log.Information($"[workspace] disconnecting from <{name}>");
_log.Information($"disconnecting from <{name}>");
if (!connected)
return;
if (heartbeatTimer != null)
stopHeartbeat();
stopReceivingUpdates();
disconnectFromWorkspace();
connected = false;
client.disconnect();
//TODO
//root.removeAllChildCues();
connected = false;
}
public void temporarilyDisconnect()
{
//TODO
//TODO what does this mean?
}
#endregion
@@ -253,13 +244,15 @@ namespace QControlKit
#region Workspace Methods
public void disconnectFromWorkspace() { client.sendMessage($"{workspacePrefix}/disconnect"); }
public void startReceivingUpdates() { client.sendMessage($"{workspacePrefix}/updates", 1); }
public void getPropertyForActive(string prop) { client.sendMessage($"{workspacePrefix}/cue/active/{prop}"); }
public void stopReceivingUpdates() { client.sendMessage($"{workspacePrefix}/updates", 0); }
public void enableAlwaysReply() { client.sendMessage($"{workspacePrefix}/alwaysReply", 1); }
public void disableAlwaysReply() { client.sendMessage($"{workspacePrefix}/alwaysReply", 0); }
public void fetchQLabVersion() { client.sendMessage($"{workspacePrefix}/version"); } //TODO: EventHandler for this? Is this still needed?
public void fetchCueLists() { client.sendMessage($"{workspacePrefix}/cueLists"); } //TODO: EventHandler for CueListUpdated
public void fetchQLabVersion() { client.sendMessage($"{workspacePrefix}/version"); }
public void fetchCueLists() { client.sendMessage($"{workspacePrefix}/cueLists"); }
public void fetchPlaybackPositionForCue(QCue cue) { client.sendMessage(addressForCue(cue, QOSCKey.PlaybackPositionId)); } //EventHandler for this? can I use the CueListPlaybackPosition one?
public void go() { client.sendMessage($"{workspacePrefix}/go"); }
public void go(string cueNumber) { client.sendMessage($"{workspacePrefix}/go/{cueNumber}"); }
public void save() { client.sendMessage($"{workspacePrefix}/save"); }
public void undo() { client.sendMessage($"{workspacePrefix}/undo"); }
public void redo() { client.sendMessage($"{workspacePrefix}/redo"); }
@@ -271,39 +264,7 @@ namespace QControlKit
#endregion
#region Heartbeat
//TODO
public void startHeartbeat()
{
clearHeartbeatTimeout();
sendHeartbeat();
}
public void stopHeartbeat()
{
clearHeartbeatTimeout();
heartbeatAttempts = -1;
}
public void clearHeartbeatTimeout()
{
heartbeatTimer.Stop();
heartbeatTimer = null;
heartbeatAttempts = 0;
//TODO
}
public void sendHeartbeat()
{
client.sendMessage("/thump");
//TODO
}
public void heartbeatTimeout(object sender, ElapsedEventArgs e)
{
//TODO
}
//TODO? Do I even need this if I only allow TCP and just watch for a change in the state of the TCP connection
#endregion
@@ -356,23 +317,46 @@ namespace QControlKit
#endregion
#region Property Fetching
//TODO
public void fetchDefaultPropertiesForCue(QCue cue)
{
List<string> keys = new List<string> { QOSCKey.UID, QOSCKey.Number, QOSCKey.Name,
QOSCKey.ListName, QOSCKey.Type, QOSCKey.ColorName,
QOSCKey.Flagged, QOSCKey.Armed, QOSCKey.Notes };
List<string> keys = new List<string> {
QOSCKey.UID,
QOSCKey.Flagged,
QOSCKey.Armed,
QOSCKey.IsRunning,
QOSCKey.IsBroken,
QOSCKey.IsLoaded,
QOSCKey.Parent,
};
fetchPropertiesForCue(cue, keys.ToArray(), false);
}
public void fetchBasicPropertiesForCue(QCue cue)
{
List<string> keys = new List<string> { QOSCKey.Name, QOSCKey.ListName, QOSCKey.Number, QOSCKey.FileTarget, QOSCKey.CueTargetNumber,
QOSCKey.HasFileTargets, QOSCKey.HasCueTargets, QOSCKey.Armed, QOSCKey.ColorName,
QOSCKey.ContinueMode, QOSCKey.Flagged, QOSCKey.PreWait, QOSCKey.PostWait,
QOSCKey.Duration, QOSCKey.AllowsEditingDuration, QOSCKey.Notes };
List<string> keys = new List<string> {
QOSCKey.Name,
QOSCKey.ListName,
QOSCKey.Number,
QOSCKey.FileTarget,
QOSCKey.CueTargetNumber,
QOSCKey.HasFileTargets,
QOSCKey.HasCueTargets,
QOSCKey.Armed,
QOSCKey.ColorName,
QOSCKey.ContinueMode,
QOSCKey.Flagged,
QOSCKey.PreWait,
QOSCKey.PostWait,
QOSCKey.Duration,
QOSCKey.AllowsEditingDuration,
QOSCKey.IsBroken,
QOSCKey.IsRunning,
QOSCKey.IsLoaded,
QOSCKey.Notes
};
if (cue.IsGroup)
keys.Add(QOSCKey.Children);
fetchPropertiesForCue(cue, keys.ToArray(), false);
@@ -400,6 +384,11 @@ namespace QControlKit
client.sendMessage(address, args);
}
public void sendMessageForCue(QCue cue, string action)
{
client.sendMessage(addressForCue(cue, action));
}
private string addressForCue(QCue cue, string action)
{
return $"{workspacePrefix}/cue_id/{cue.propertyForKey(QOSCKey.UID)}/{action}";
@@ -431,26 +420,28 @@ namespace QControlKit
{
//clear passcode if there was one set in the connect() method
this.passcode = null;
Log.Error($"[workspace] *** Password for workspace {name} was incorrect!");
_log.Error($"*** Password for workspace <{name}> was incorrect!");
}
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)
{
Log.Information($"[workspace] Connection to <{name}> successful, finishing things up.");
_log.Information($"Connection to <{name}> successful, finishing things up.");
WorkspaceConnected?.Invoke(this, args);
finishConnection();
}
private void OnWorkspaceDisconnected(object source, QWorkspaceDisconnectedArgs args)
{
//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());
}
private void OnCueListsUpdated(object source, QCueListsUpdatedArgs args)
@@ -518,7 +509,7 @@ namespace QControlKit
//add Event handled? use CueUpdated one?
}
Log.Debug($"[workspace] cueLists finished processing. root updated? {rootCueUpdated}");
_log.Debug($"cueLists finished processing. root updated? {rootCueUpdated}");
OnWorkspaceUpdated();
@@ -536,8 +527,7 @@ namespace QControlKit
cueList.setProperty(args.cueID, QOSCKey.PlaybackPositionId, false);
}
//TODO: need to implement when playbackposition is "none"
Log.Debug($"[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 });
}
@@ -551,7 +541,7 @@ namespace QControlKit
{
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)
{
if (!cuelist.uid.Equals(QIdentifiers.ActiveCues))
@@ -573,10 +563,20 @@ namespace QControlKit
private void OnCueUpdated(object source, QCueUpdatedArgs args)
{
_log.Verbose($"OnCueUpdated: {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;
}
if (cue.ignoreUpdates)
{
return;
}
cue.updatePropertiesWithDictionary(args.data);
}
@@ -591,7 +591,7 @@ namespace QControlKit
#region Printing
public void Print()
{
Log.Information($"[workspace] {name}");
_log.Information($"{name}");
foreach (var cueList in root.cues)
{
cueList.Print();
-2
View File
@@ -26,8 +26,6 @@ namespace SharpOSC
private static OscMessage parseMessage(byte[] msg)
{
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;
char[] types = new char[0];
List<object> arguments = new List<object>();
+61
View File
@@ -0,0 +1,61 @@
using System.Collections.Generic;
namespace SharpOSC
{
public static class SlipFrame
{
public static readonly byte END = 0xc0;
public static readonly byte ESC = 0xdb;
public static readonly byte ESC_END = 0xDC;
public static readonly byte ESC_ESC = 0xDD;
public static List<byte[]> Decode(byte[] data)
{
List<byte[]> messages = new List<byte[]>();
List<byte> buffer = new List<byte>();
for (int i = 0; i < data.Length; i++)
{
if (data[i] == END && buffer.Count > 0)
{
messages.Add(buffer.ToArray());
buffer.Clear();
}
else if (data[i] != END)
{
buffer.Add(data[i]);
}
}
return messages;
}
public static byte[] Encode(byte[] data)
{
List<byte> slipData = new List<byte>();
byte[] esc_end = { ESC, ESC_END };
byte[] esc_esc = { ESC, ESC_ESC };
byte[] end = { END };
int length = data.Length;
for (int i = 0; i < length; i++)
{
if (data[i] == END)
{
slipData.AddRange(esc_end);
}
else if (data[i] == ESC)
{
slipData.AddRange(esc_esc);
}
else
{
slipData.Add(data[i]);
}
}
slipData.AddRange(end);
return slipData.ToArray();
}
}
}
+81 -100
View File
@@ -2,9 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using Serilog;
using TcpSharp.Events;
namespace SharpOSC
{
@@ -19,6 +18,8 @@ namespace SharpOSC
public class TCPClient
{
private ILogger _log = Log.Logger.ForContext<TCPClient>();
public int Port
{
get { return _port; }
@@ -31,15 +32,12 @@ namespace SharpOSC
}
public delegate void MessageReceivedHandler(object source, MessageEventArgs args);
public event MessageReceivedHandler MessageReceived;
private Thread receivingThread;
string _address;
TcpClient client;
byte END = 0xc0;
byte ESC = 0xdb;
byte ESC_END = 0xDC;
byte ESC_ESC = 0xDD;
TcpSharp.TcpSharpSocketClient tcpClient;
private List<byte> frameStream = new List<byte>();
public TCPClient(string address, int port)
{
@@ -49,27 +47,82 @@ namespace SharpOSC
public bool Connect()
{
try
{
client = new TcpClient(Address, Port);
receivingThread = new Thread(ReceiveLoop);
receivingThread.Start();
Log.Debug($"[tcpclient] connected to <{Address}:{Port}>");
tcpClient = new TcpSharp.TcpSharpSocketClient(Address, Port);
tcpClient.OnConnected += ClientConnected;
tcpClient.OnDataReceived += DataReceived;
tcpClient.OnDisconnected += ClientDisconneted;
tcpClient.OnError += ClientError;
tcpClient.Connect();
return true;
}
}
catch (Exception e)
{
Log.Error(e.Message);
_log.Error(e.Message);
return false;
}
}
private void ClientError(object sender, OnClientErrorEventArgs e)
{
_log.Error("client error");
_log.Error(e.Exception.ToString());
}
private void ClientDisconneted(object sender, OnClientDisconnectedEventArgs e)
{
_log.Information($"client disconnected: {e.Reason}");
Close();
}
private void DataReceived(object sender, OnClientDataReceivedEventArgs e)
{
frameStream.AddRange(e.Data);
int i = 0;
int frameEnd = frameStream.FindIndex(1, frameByte=>frameByte.Equals(SlipFrame.END));
while(frameEnd > 0)
{
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)
{
byte[] slipData = SlipEncode(message);
NetworkStream netStream = client.GetStream();
netStream.Write(slipData.ToArray(), 0, slipData.ToArray().Length);
byte[] slipData = SlipFrame.Encode(message);
tcpClient.SendBytes(slipData.ToArray());
}
public void Send(OscPacket packet)
@@ -82,96 +135,24 @@ namespace SharpOSC
{
get
{
if (client == null)
if (tcpClient == null)
return false;
else
return client.Connected;
return tcpClient.Connected;
}
}
public void ReceiveLoop()
{
while (client.Connected)
{
Receive();
}
//Log.Debug("[tcpclient] - ReceiveLoop has exited");
}
public void Receive()
{
Random random = new Random();
int num = random.Next(1000);
try
{
NetworkStream netStream = client.GetStream();
netStream.ReadTimeout = 250;
List<byte> responseData = new List<byte>();
if (netStream.CanRead)
{
//var watch = System.Diagnostics.Stopwatch.StartNew();
byte[] buffer = new byte[256];
int bytesRead = 0;
int reads = 0;
do
{
bytesRead = netStream.Read(buffer, 0, buffer.Length);
responseData.AddRange(buffer);
reads += 1;
Thread.Sleep(1);
//Log.Debug("Thread " + num + ": Bytes read: " + bytesRead + " - " + Encoding.UTF8.GetString(buffer));
} while (netStream.DataAvailable);
//Console.WriteLine("Raw TCP In: " + System.Text.Encoding.UTF8.GetString(responseData.ToArray()));
OscMessage response = (OscMessage)OscPacket.GetPacket(responseData.Skip(1).ToArray());
//watch.Stop();
//Console.WriteLine($"TCPCLient - message receive took {watch.ElapsedMilliseconds}ms and {reads} reads");
OnMessageReceived(response);
}
} catch(Exception e)
{
//Console.WriteLine("TCPSENDER - Receive Exception: " + e.ToString());
}
}
public byte[] SlipEncode(byte[] data)
{
List<byte> slipData = new List<byte>();
byte[] esc_end = { ESC, ESC_END };
byte[] esc_esc = { ESC, ESC_ESC };
byte[] end = { END };
int length = data.Length;
for (int i = 0; i < length; i++)
{
if (data[i] == END)
{
slipData.AddRange(esc_end);
}
else if (data[i] == ESC)
{
slipData.AddRange(esc_esc);
}
else
{
slipData.Add(data[i]);
}
}
slipData.AddRange(end);
return slipData.ToArray();
}
public void Close()
{
if (client != null)
if (tcpClient != null)
{
if (client.Connected)
tcpClient.OnConnected -= ClientConnected;
tcpClient.OnDataReceived -= DataReceived;
tcpClient.OnDisconnected -= ClientDisconneted;
if (tcpClient.Connected)
{
Log.Debug($"[tcpClient] closing connection to {Address}");
client.GetStream().Close();
client.Close();
_log.Debug($"closing connection to {Address}");
tcpClient.Disconnect();
}
}
}
@@ -181,4 +162,4 @@ namespace SharpOSC
MessageReceived?.Invoke(this, new MessageEventArgs() { Message = msg });
}
}
}
}
-2
View File
@@ -100,7 +100,6 @@ namespace SharpOSC
Log.Error(e.ToString());
// 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);
}
else
@@ -151,7 +150,6 @@ namespace SharpOSC
{
byte[] bytes = queue.Dequeue();
var packet = OscPacket.GetPacket(bytes);
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
return packet;
}
else
@@ -1,6 +1,6 @@
<?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">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<application android:label="QControlKitXamDemo.Android"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
@@ -1,109 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4F7F13C7-85EF-431F-AB03-B2494F0F4C7F}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType>
<RootNamespace>QControlKitXamDemo.Droid</RootNamespace>
<AssemblyName>QControlKitXamDemo.Android</AssemblyName>
<Deterministic>True</Deterministic>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.Sinks.Xamarin">
<Version>0.2.0.64</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2012" />
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.xml" />
<AndroidResource Include="Resources\layout\Toolbar.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\drawable\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\QControlKit\QControlKit.csproj">
<Project>{51cd2579-79c9-49a9-b68d-93cbe878baa1}</Project>
<Name>QControlKit</Name>
</ProjectReference>
<ProjectReference Include="..\QControlKitXamDemo\QControlKitXamDemo.csproj">
<Project>{20D2D0D8-A518-42DC-BAB8-CF098CDF03C4}</Project>
<Name>QControlKitXamDemo</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionQControlKitXamDemoAndroidHideInfoBar="True" />
</VisualStudio>
</ProjectExtensions>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4F7F13C7-85EF-431F-AB03-B2494F0F4C7F}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType>
<RootNamespace>QControlKitXamDemo.Droid</RootNamespace>
<AssemblyName>QControlKitXamDemo.Android</AssemblyName>
<Deterministic>True</Deterministic>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v13.0</TargetFrameworkVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.Sinks.Xamarin">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2622" />
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.xml" />
<AndroidResource Include="Resources\layout\Toolbar.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\drawable\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\QControlKit\QControlKit.csproj">
<Project>{51cd2579-79c9-49a9-b68d-93cbe878baa1}</Project>
<Name>QControlKit</Name>
</ProjectReference>
<ProjectReference Include="..\QControlKitXamDemo\QControlKitXamDemo.csproj">
<Project>{20D2D0D8-A518-42DC-BAB8-CF098CDF03C4}</Project>
<Name>QControlKitXamDemo</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\materialdesignicons-webfont.ttf" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionQControlKitXamDemoAndroidHideInfoBar="True" />
</VisualStudio>
</ProjectExtensions>
</Project>
@@ -5,6 +5,8 @@ using System.Linq;
using Foundation;
using UIKit;
using Serilog;
using Serilog.Events;
namespace QControlKitXamDemo.iOS
{
// 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)
{
global::Xamarin.Forms.Forms.Init();
Log.Logger = new LoggerConfiguration().WriteTo.NSLog().MinimumLevel.Debug().CreateLogger();
Log.Logger = new LoggerConfiguration().WriteTo.NSLog(outputTemplate: "[{Level}] ({SourceContext}) {Message}{NewLine}{Exception}").MinimumLevel.Verbose().CreateLogger();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
@@ -2,37 +2,47 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleDisplayName</key>
<string>QControlKitXamDemo</string>
<key>CFBundleIdentifier</key>
<string>com.companyname.QControlKitXamDemo</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleName</key>
<string>QControlKitXamDemo</string>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleDisplayName</key>
<string>QControlKitXamDemo</string>
<key>CFBundleIdentifier</key>
<string>com.companyname.QControlKitXamDemo</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleName</key>
<string>QControlKitXamDemo</string>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>NSBonjourServices</key>
<array>
<string>_qlab._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>Scan network for QLab instances</string>
<key>UIAppFonts</key>
<array>
<string>materialdesignicons-webfont.ttf</string>
</array>
</dict>
</plist>
@@ -51,6 +51,7 @@
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchLink>None</MtouchLink>
<MtouchInterpreter>-all</MtouchInterpreter>
<CodesignProvision>Wildcard</CodesignProvision>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
<DebugType>none</DebugType>
@@ -125,13 +126,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog">
<Version>2.10.0</Version>
<Version>2.12.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Xamarin">
<Version>0.2.0.64</Version>
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="SuperSimpleTcp">
<Version>3.0.0.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2012" />
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
<ItemGroup>
@@ -140,4 +144,7 @@
<Name>QControlKitXamDemo</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\materialdesignicons-webfont.ttf" />
</ItemGroup>
</Project>
@@ -6,6 +6,52 @@
mc:Ignorable="d"
x:Class="QControlKitXamDemo.App">
<Application.Resources>
<OnPlatform x:Key="MaterialFontFamily" x:TypeArguments="x:String">
<On Platform="iOS">Material Design Icons</On>
<On Platform="Android">materialdesignicons-webfont.ttf#Material Design Icons</On>
</OnPlatform>
<Style TargetType="Label"
Class="PrimaryText"
ApplyToDerivedTypes="true">
<Setter Property="TextColor"
Value="{DynamicResource PrimaryTextColor}" />
</Style>
<Style TargetType="Button"
Class="PrimaryButton"
ApplyToDerivedTypes="true">
<Setter Property="TextColor"
Value="{DynamicResource PrimaryTextColor}" />
</Style>
<x:Double x:Key="SmallPadding">4</x:Double>
<x:Double x:Key="MediumPadding">8</x:Double>
<x:Double x:Key="StandardPadding">16</x:Double>
<Thickness x:Key="SmallTopThickness" Top="{StaticResource SmallPadding}" />
<Thickness x:Key="MediumTopThickness" Top="{StaticResource MediumPadding}" />
<Thickness x:Key="StandardTopThickness" Top="{StaticResource StandardPadding}" />
<Thickness x:Key="SmallThickness"
Bottom="{StaticResource SmallPadding}"
Left="{StaticResource SmallPadding}"
Right="{StaticResource SmallPadding}"
Top="{StaticResource SmallPadding}" />
<Thickness x:Key="MediumThickness"
Bottom="{StaticResource MediumPadding}"
Left="{StaticResource MediumPadding}"
Right="{StaticResource MediumPadding}"
Top="{StaticResource MediumPadding}" />
<Thickness x:Key="StandardThickness"
Bottom="{StaticResource StandardPadding}"
Left="{StaticResource StandardPadding}"
Right="{StaticResource StandardPadding}"
Top="{StaticResource StandardPadding}" />
</Application.Resources>
</Application>
@@ -9,6 +9,8 @@ namespace QControlKitXamDemo
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
Current.RequestedThemeChanged += Current_RequestedThemeChanged;
SetAppResources();
}
protected override void OnStart()
@@ -22,5 +24,80 @@ namespace QControlKitXamDemo
protected override void OnResume()
{
}
private void Current_RequestedThemeChanged(object sender, AppThemeChangedEventArgs e)
{
SetAppResources();
}
private void SetAppResources()
{
switch (Current.RequestedTheme)
{
case OSAppTheme.Light:
SetLightResources();
break;
case OSAppTheme.Dark:
SetDarkResources();
break;
case OSAppTheme.Unspecified:
//Default to Light
SetLightResources();
break;
default:
SetLightResources();
break;
}
}
private void SetDarkResources()
{
Resources["NavigationBarColor"] = Color.FromHex("#3f6dab");
Resources["PageBackgroundColor"] = Color.FromHex("#525252");
Resources["PrimaryColor"] = Color.WhiteSmoke;
Resources["SecondaryColor"] = Color.White;
Resources["PrimaryTextColor"] = Color.LightGray;
Resources["SecondaryTextColor"] = Color.White;
Resources["TertiaryTextColor"] = Color.Gray;
Resources["TransparentColor"] = Color.Transparent;
Resources["IconTextColor"] = Color.WhiteSmoke;
Resources["ListViewBackgroundColor"] = Color.FromHex("#363636");
Resources["ServerCellBackgroundColor"] = Color.FromHex("#363636");
Resources["WorkspaceCellBackgroundColor"] = Color.FromHex("#737373");
Resources["SelectedCueCellBackgroundColor"] = Color.FromHex("#737373");
}
private void SetLightResources()
{
Resources["NavigationBarColor"] = Color.FromHex("#3089ff");
Resources["PageBackgroundColor"] = Color.LightGray;
Resources["PrimaryColor"] = Color.WhiteSmoke;
Resources["SecondaryColor"] = Color.White;
Resources["PrimaryTextColor"] = Color.Black;
Resources["SecondaryTextColor"] = Color.White;
Resources["TertiaryTextColor"] = Color.Gray;
Resources["TransparentColor"] = Color.Transparent;
Resources["IconTextColor"] = Color.Black;
Resources["ListViewBackgroundColor"] = Color.White;
Resources["ServerCellBackgroundColor"] = Color.White;
Resources["WorkspaceCellBackgroundColor"] = Color.FromHex("#D8D8D8");
Resources["SelectedCueCellBackgroundColor"] = Color.FromHex("#D8D8D8");
}
}
}
@@ -7,6 +7,7 @@ using Xamarin.Forms;
using QControlKit.Events;
using QControlKit.Constants;
using Serilog;
namespace QControlKitXamDemo
{
@@ -20,25 +21,44 @@ namespace QControlKitXamDemo
connectedWorkspace = workspace;
connectedWorkspace.WorkspaceUpdated += Workspace_WorkspaceUpdated;
connectedWorkspace.WorkspaceConnectionError += ConnectedWorkspace_WorkspaceConnectionError;
connectedWorkspace.WorkspaceConnected += ConnectedWorkspace_WorkspaceConnected; ;
connectedWorkspace.connect(passcode);
connectedWorkspace.defaultSendUpdatesOSC = true;
connectedWorkspace.CueListChangedPlaybackPosition += ConnectedWorkspace_CueListChangedPlaybackPosition;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
connectedWorkspace.disconnect();
connectedWorkspace.WorkspaceUpdated -= Workspace_WorkspaceUpdated;
connectedWorkspace.WorkspaceConnectionError -= ConnectedWorkspace_WorkspaceConnectionError;
connectedWorkspace.WorkspaceConnected -= ConnectedWorkspace_WorkspaceConnected;
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)
{
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)
{
connectedWorkspace.connect(passcode);
@@ -51,16 +71,31 @@ namespace QControlKitXamDemo
Device.BeginInvokeOnMainThread(() =>
{
QCue selectedCue = connectedWorkspace.cueWithID(args.cueID);
connectedWorkspace.fetchDefaultPropertiesForCue(selectedCue);
selectedCueGrid.BindingContext = new QCueViewModel(selectedCue, false);
if (cueGridDict.ContainsKey(args.cueID))
if(selectedCue != null) //TODO: I think this being null is the result of a race condition?
{
var cueGrid = cueGridDict[args.cueID]; //element to scroll to
cueListScrollView.ScrollToAsync(cueGrid, ScrollToPosition.Center, true);
connectedWorkspace.fetchBasicPropertiesForCue(selectedCue);
selectedCueGrid.BindingContext = new QCueViewModel(selectedCue, false);
if (cueGridDict.ContainsKey(args.cueID))
{
var cueGrid = cueGridDict[args.cueID]; //element to scroll to
cueListScrollView.ScrollToAsync(cueGrid, ScrollToPosition.Center, true);
}
}
});
}
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)
{
if(connectedWorkspace.cueLists.Count > 0)
@@ -68,6 +103,7 @@ namespace QControlKitXamDemo
List<Task> cueAddTasks = new List<Task>();
foreach(var aCue in connectedWorkspace.cueLists)
{
updateCuePropertes(aCue);
Grid cueGrid = cueToGrid(aCue);
cueGridDict.Add(aCue.uid, cueGrid);
MainThread.InvokeOnMainThreadAsync(() =>
@@ -3,14 +3,121 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="QControlKitXamDemo.MainPage">
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
mc:Ignorable="d"
x:Class="QControlKitXamDemo.MainPage"
ios:Page.UseSafeArea="true"
BackgroundColor="{DynamicResource PageBackgroundColor}">
<ContentPage.Content>
<ListView x:Name="serverListView" GroupDisplayBinding="{Binding name}" IsGroupingEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
<StackLayout>
<Frame BackgroundColor="{DynamicResource PageBackgroundColor}"
CornerRadius="0"
Margin="0"
Padding="0"
HasShadow="False">
<StackLayout Spacing="0" BackgroundColor="Transparent">
<Label Margin="0,10,0,10"
Padding="0"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HorizontalTextAlignment="Center"
FontSize="Title"
Text="Discovered QLab Instances" />
<!--Need to try and remove the group spacing caused by ios:GroupHeaderStyle-->
<ListView x:Name="serverListView"
ios:ListView.GroupHeaderStyle="Grouped"
GroupDisplayBinding="{Binding name}"
IsGroupingEnabled="True"
d:IsGroupingEnabled="False"
BackgroundColor="Transparent"
SeparatorVisibility="None"
ItemsSource="{Binding ServersGrouped}"
Margin="0">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Frame BackgroundColor="{DynamicResource ServerCellBackgroundColor}"
Margin="0,0,0,0"
Padding="10,0,10,0"
CornerRadius="0">
<Grid BackgroundColor="Transparent" VerticalOptions="FillAndExpand"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding GroupName}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start"
StyleClass="PrimaryText"/>
<Label Grid.Column="1"
Text="{Binding version}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
HorizontalTextAlignment="End"
StyleClass="PrimaryText"/>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BackgroundColor="{DynamicResource WorkspaceCellBackgroundColor}"
BorderColor="Black"
Margin="5,0,5,0"
Padding="10,0,10,0"
CornerRadius="0"
HasShadow="False">
<Grid Margin="0"
BackgroundColor="Transparent"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Text="{Binding Name}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start"
StyleClass="PrimaryText"/>
<Label Grid.Column="1" Text="&#xf1184;"
FontFamily="{StaticResource MaterialFontFamily}"
FontSize="Large"
IsVisible="{Binding HasPasscode}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
HorizontalTextAlignment="End"
StyleClass="PrimaryText"/>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
</StackLayout>
</ContentPage.Content>
</ContentPage>
@@ -2,146 +2,71 @@
using System.ComponentModel;
using Xamarin.Forms;
using QControlKit;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using QControlKit.Events;
using System.Threading.Tasks;
using Serilog;
using QControlKitXamDemo.ViewModels;
using Acr.UserDialogs;
namespace QControlKitXamDemo
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class MainPage : ContentPage
{
public ObservableCollection<ServerGroup> servers = new ObservableCollection<ServerGroup>();
private QBrowserViewModel qBrowserViewModel;
public MainPage()
{
InitializeComponent();
serverListView.ItemsSource = servers;
QBrowser browser = new QBrowser();
browser.ServerLost += Browser_ServerLost;
browser.ServerFound += Browser_ServerFound;
serverListView.ItemSelected += ServerListView_ItemSelected;
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
Task.Run(async () =>
{
browser.ProbeForQLabInstances();
});
return true;
});
qBrowserViewModel = new QBrowserViewModel(new QBrowser());
serverListView.BindingContext = qBrowserViewModel;
serverListView.ItemSelected += QWorkspaceSelected;
}
private void Browser_ServerFound(object source, QServerFoundArgs args)
async void QWorkspaceSelected(object sender, SelectedItemChangedEventArgs e)
{
List<ServerGroup> serverGroups = servers.ToList();
bool serverFound = false;
foreach (var serverGroup in serverGroups)
if (e.SelectedItem == null)
{
if (serverGroup.host == args.server.host)
{
serverFound = true;
}
return;
}
if (!serverFound)
{
Device.BeginInvokeOnMainThread(() =>
{
ServerGroup serverGroup = new ServerGroup(args.server.name);
args.server.WorkspaceAdded += Server_WorkspaceAdded;
args.server.WorkspaceRemoved += Server_WorkspaceRemoved;
serverGroup.host = args.server.host;
servers.Add(serverGroup);
});
}
}
qBrowserViewModel.autoUpdate = false;
QWorkspace selectedWorkspace = (e.SelectedItem as QWorkspaceViewModel).workspace;
Log.Debug($"workspace: {selectedWorkspace.nameWithoutPathExtension} has been selected");
((ListView)sender).SelectedItem = null;
private void Server_WorkspaceRemoved(object source, QServerWorkspaceChangedArgs args)
{
foreach (var server in servers)
if (selectedWorkspace.hasPasscode)
{
if (server.host == args.server.host)
UserDialogs.Instance.Prompt(new PromptConfig
{
Device.BeginInvokeOnMainThread(() =>
InputType = InputType.Number,
MaxLength = 4,
Title = "Workspace Requires Passcode",
OkText = "Connect",
IsCancellable = true,
OnTextChanged = args =>
{
server.Remove(args.workspace);
});
}
}
}
private void Server_WorkspaceAdded(object source, QServerWorkspaceChangedArgs args)
{
foreach(var server in servers)
{
if(server.host == args.server.host)
{
Device.BeginInvokeOnMainThread(() =>
args.IsValid = args.Value != null && !args.Value.Equals("") && args.Value.Length == 4;
},
OnAction = async (resp) =>
{
server.Add(args.workspace);
});
}
}
}
private void Browser_ServerLost(object source, QServerLostArgs args)
{
List<ServerGroup> serverGroups = servers.ToList();
foreach(var serverGroup in serverGroups)
{
if(serverGroup.host == args.server.host)
{
Device.BeginInvokeOnMainThread(() =>
{
servers.Remove(serverGroup);
});
}
}
}
async void ServerListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if(e.SelectedItem != null)
{
QWorkspace selectedWorkspace = e.SelectedItem as QWorkspace;
Log.Debug($"[demo] Workspace <{selectedWorkspace.nameWithoutPathExtension}> has been selected");
if (selectedWorkspace.hasPasscode)
{
string passcode = await DisplayPromptAsync("Workspace has passcode", "Enter Passcode", maxLength: 4, keyboard: Keyboard.Numeric);
if (passcode != null)
{
await Navigation.PushAsync(new CueListPage(selectedWorkspace, passcode));
if (resp.Ok)
{
await Navigation.PushAsync(new CueListPage(selectedWorkspace, resp.Value));
}
}
}
else
{
await Navigation.PushAsync(new CueListPage(selectedWorkspace));
}
serverListView.SelectedItem = null;
});
}
else
{
await Navigation.PushAsync(new CueListPage(selectedWorkspace));
}
}
}
public class ServerGroup : ObservableCollection<QWorkspace>{
public string name { get; set; }
public string host { get; set; }
public ServerGroup(string name)
protected override void OnAppearing()
{
this.name = name;
base.OnAppearing();
qBrowserViewModel.autoUpdate = true;
}
}
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@@ -11,9 +11,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2012" />
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2622" />
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
<PackageReference Include="Acr.UserDialogs" Version="7.2.0.564" />
</ItemGroup>
<ItemGroup>
@@ -28,4 +29,7 @@
<ItemGroup>
<ProjectReference Include="..\..\QControlKit\QControlKit.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Acr.UserDialogs" />
</ItemGroup>
</Project>
@@ -0,0 +1,98 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using QControlKit;
using QControlKit.Events;
using System;
using System.Threading.Tasks;
using Serilog;
namespace QControlKitXamDemo.ViewModels
{
public class QBrowserViewModel : INotifyPropertyChanged
{
private ILogger _log = Log.Logger.ForContext<QBrowserViewModel>();
QBrowser browser;
public event PropertyChangedEventHandler PropertyChanged;
public bool autoUpdate = false;
public ObservableCollection<QServerViewModel> ServersGrouped { get; set; }
bool connected = false;
public QBrowserViewModel(QBrowser browser)
{
this.browser = browser;
ServersGrouped = new ObservableCollection<QServerViewModel>();
this.browser.ServerFound += OnServerFound;
this.browser.ServerLost += OnServerLost;
Device.StartTimer(TimeSpan.FromSeconds(4), () =>
{
Task.Run(() =>
{
if (autoUpdate)
{
_log.Verbose($"Auto Update is enabled running probe");
Device.BeginInvokeOnMainThread(() =>
{
_log.Debug("QBrowser probe triggered");
this.browser.ProbeForQLabInstances();
});
}
});
return true;
});
}
private void OnServerFound(object source, QServerFoundArgs args)
{
Device.BeginInvokeOnMainThread(() =>
{
_log.Debug($"adding server: {args.server.description}");
ServersGrouped.Add(new QServerViewModel(args.server));
});
}
private void OnServerLost(object source, QServerLostArgs args)
{
QServerViewModel serverToRemove = null;
foreach (QServerViewModel serverGroup in ServersGrouped)
{
if (serverGroup.host.Equals(args.server.host))
{
serverToRemove = serverGroup;
break;
}
}
if (serverToRemove != null)
{
Device.BeginInvokeOnMainThread(() =>
{
_log.Debug($"removing server: {args.server.description}");
ServersGrouped.Remove(serverToRemove);
});
}
}
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public void InitiateScan()
{
if (!autoUpdate)
{
_log.Debug("Manual Scan Initiated");
browser.ProbeForQLabInstances();
}
}
}
}
@@ -5,11 +5,14 @@ using Xamarin.Forms;
using Serilog;
using QControlKit.Events;
using QControlKit.Constants;
using System;
namespace QControlKitXamDemo.ViewModels
{
public class QCueViewModel : INotifyPropertyChanged
{
private ILogger _log = Log.Logger.ForContext<QCueViewModel>();
QCue cue;
bool isSelected = false;
public event PropertyChangedEventHandler PropertyChanged;
@@ -17,22 +20,32 @@ namespace QControlKitXamDemo.ViewModels
public QCueViewModel(QCue cue, bool checkPlayback)
{
this.cue = cue;
if(checkPlayback)
if (checkPlayback)
{
this.cue.workspace.CueListChangedPlaybackPosition += Workspace_CueListChangedPlaybackPosition;
}
this.cue.CuePropertiesUpdated += OnCuePropertiesUpdated;
}
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.");
_log.Debug($"property <{property}> has been updated for cue {name}.");
if (property.Equals(QOSCKey.Name) || property.Equals(QOSCKey.ListName))
{
OnPropertyChanged("name");
}else if (property.Equals(QOSCKey.ColorName))
{
OnPropertyChanged("color");
}else if (property.Equals(QOSCKey.ActionElapsed))
{
OnPropertyChanged("actionElapsed");
}else if (property.Equals(QOSCKey.IsBroken) || property.Equals(QOSCKey.IsRunning))
{
OnPropertyChanged("status");
}
}
}
@@ -84,6 +97,34 @@ namespace QControlKitXamDemo.ViewModels
}
}
public string actionElapsed
{
get
{
TimeSpan time = TimeSpan.FromSeconds(cue.actionElapsed);
return time.ToString(@"mm\:ss\:ff");
}
}
public string status
{
get
{
if (cue.IsRunning)
{
return "running";
}else if (cue.IsBroken)
{
return "broken";
}
else
{
return " ";
}
}
}
public bool IsSelected {
get
{
@@ -0,0 +1,90 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using QControlKit;
using QControlKit.Events;
using Serilog;
namespace QControlKitXamDemo.ViewModels
{
public class QServerViewModel : ObservableCollection<QWorkspaceViewModel>, INotifyPropertyChanged
{
private ILogger _log = Log.Logger.ForContext<QServerViewModel>();
QServer server;
public new event PropertyChangedEventHandler PropertyChanged;
public string name
{
get => server.name;
set => server.name = value;
}
public string host => server.host;
public string version => server.version == null ? "" : $"v{server.version}";
public string GroupName => $"{name} ({host})";
public QServerViewModel(QServer server)
{
this.server = server;
name = this.server.name;
foreach (QWorkspace workspace in this.server.workspaces)
{
Device.BeginInvokeOnMainThread(() =>
{
Add(new QWorkspaceViewModel(workspace));
});
}
this.server.WorkspaceAdded += Server_WorkspaceAdded;
this.server.WorkspaceRemoved += Server_WorkspaceRemoved;
this.server.ServerUpdated += OnServerUpdated;
}
private void Server_WorkspaceRemoved(object source, QServerWorkspaceChangedArgs args)
{
QWorkspaceViewModel workspaceToRemove = null;
foreach (QWorkspaceViewModel workspaceViewModel in this)
{
if (workspaceViewModel.workspace.uniqueID == args.workspace.uniqueID)
{
workspaceToRemove = workspaceViewModel;
break;
}
}
if (workspaceToRemove != null)
{
Device.BeginInvokeOnMainThread(() =>
{
Remove(workspaceToRemove);
});
}
}
private void Server_WorkspaceAdded(object source, QServerWorkspaceChangedArgs args)
{
QWorkspaceViewModel workspaceViewModel = new QWorkspaceViewModel(args.workspace);
Device.BeginInvokeOnMainThread(() =>
{
Add(workspaceViewModel);
});
}
private void OnServerUpdated(object source, QServerUpdatedArgs args)
{
OnPropertyChanged("GroupName");
OnPropertyChanged("version");
}
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
@@ -0,0 +1,17 @@
using QControlKit;
namespace QControlKitXamDemo.ViewModels
{
public class QWorkspaceViewModel
{
public QWorkspace workspace;
public QWorkspaceViewModel(QWorkspace workspace)
{
this.workspace = workspace;
}
public string Name => workspace.name;
public bool HasPasscode => workspace.hasPasscode;
}
}
+9
View File
@@ -2,6 +2,15 @@
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