diff --git a/CameraApi.Core/CameraApi.Core.csproj b/CameraApi.Core/CameraApi.Core.csproj new file mode 100644 index 0000000..a87967e --- /dev/null +++ b/CameraApi.Core/CameraApi.Core.csproj @@ -0,0 +1,12 @@ + + + + netstandard1.4 + + + + + + + + \ No newline at end of file diff --git a/CameraApi.Core/CameraMode.cs b/CameraApi.Core/CameraMode.cs new file mode 100644 index 0000000..9f0aa1a --- /dev/null +++ b/CameraApi.Core/CameraMode.cs @@ -0,0 +1,23 @@ +namespace CameraApi.Core +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Camera Mode wheel + /// + public enum CameraMode + { + iA, + P, + A, + S, + M, + vP, + vA, + vS, + vM, + Unknown + } +} diff --git a/CameraApi.Core/CameraState.cs b/CameraApi.Core/CameraState.cs new file mode 100644 index 0000000..daeafcd --- /dev/null +++ b/CameraApi.Core/CameraState.cs @@ -0,0 +1,48 @@ +namespace CameraApi.Core +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Runtime.CompilerServices; + using JetBrains.Annotations; + + public interface ICameraState : INotifyPropertyChanged + { + string Aperture { get; } + + string CameraMode { get; } + + bool CanCapture { get; } + + bool CanChangeAperture { get; } + + bool CanChangeShutter { get; } + + bool CanManualFocus { get; } + + float Focus { get; } + + float ExposureShift { get; } + + string FocusMode { get; } + + bool IsBusy { get; } + + string Iso { get; } + + bool IsVideoMode { get; } + + int MaximumFocus { get; } + + RecState RecState { get; } + + string Shutter { get; } + + int Zoom { get; } + + ObservableCollection Apertures { get; } + + ObservableCollection Shutters { get; } + } +} \ No newline at end of file diff --git a/CameraApi.Core/ChangeDirection.cs b/CameraApi.Core/ChangeDirection.cs new file mode 100644 index 0000000..d297399 --- /dev/null +++ b/CameraApi.Core/ChangeDirection.cs @@ -0,0 +1,11 @@ +namespace CameraApi.Core +{ + public enum ChangeDirection + { + WideFast, + WideNormal, + ZoomStop, + TeleFast, + TeleNormal + } +} \ No newline at end of file diff --git a/Core/Camera/FloatPoint.cs b/CameraApi.Core/FloatPoint.cs similarity index 96% rename from Core/Camera/FloatPoint.cs rename to CameraApi.Core/FloatPoint.cs index 7c0ca29..ab3fb23 100644 --- a/Core/Camera/FloatPoint.cs +++ b/CameraApi.Core/FloatPoint.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Core { public struct FloatPoint { diff --git a/CameraApi.Core/FocusMode.cs b/CameraApi.Core/FocusMode.cs new file mode 100644 index 0000000..cd08fcd --- /dev/null +++ b/CameraApi.Core/FocusMode.cs @@ -0,0 +1,26 @@ +namespace CameraApi.Core +{ + /// + /// Camera AutoFocus mode + /// + public enum AutoFocusMode + { + Unknown, + Manual, + Face, + Track, + MultiArea, + FreeMultiArea, + OneArea, + Pinpoint + } + + public enum FocusMode + { + MF, + AFC, + AFF, + AFS, + Unknown + } +} diff --git a/CameraApi.Core/GeneralMode.cs b/CameraApi.Core/GeneralMode.cs new file mode 100644 index 0000000..000052d --- /dev/null +++ b/CameraApi.Core/GeneralMode.cs @@ -0,0 +1,32 @@ +namespace CameraApi.Core +{ + public abstract class GeneralMode + { + /// + /// Initializes a new instance of the class. + /// + /// Short Description. + /// Long Description. + public GeneralMode(string shortdesc, string longdesc) + { + ShortDescription = shortdesc; + LongDescription = longdesc; + } + + public string ShortDescription { get; } + + public string LongDescription { get; } + + public override bool Equals(object obj) + { + var mode = obj as GeneralMode; + return mode != null && + ShortDescription == mode.ShortDescription; + } + + public override int GetHashCode() + { + return ShortDescription.GetHashCode(); + } + } +} diff --git a/Core/GlobalSuppressions.cs b/CameraApi.Core/GlobalSuppressions.cs similarity index 100% rename from Core/GlobalSuppressions.cs rename to CameraApi.Core/GlobalSuppressions.cs diff --git a/CameraApi.Core/IActionItem.cs b/CameraApi.Core/IActionItem.cs new file mode 100644 index 0000000..f390695 --- /dev/null +++ b/CameraApi.Core/IActionItem.cs @@ -0,0 +1,11 @@ +namespace CameraApi.Core +{ + using System; + using System.Collections.Generic; + using System.Text; + + public interface IActionItem + { + string Text { get; } + } +} diff --git a/CameraApi.Core/ICamera.cs b/CameraApi.Core/ICamera.cs new file mode 100644 index 0000000..0dd9d75 --- /dev/null +++ b/CameraApi.Core/ICamera.cs @@ -0,0 +1,155 @@ +namespace CameraApi.Core +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + + public delegate void LiveviewReceiver(ArraySegment segment); + + public enum UpdateStateFailReason + { + RequestFailed = 0, + LumixException = 1, + NotConnected = 2 + } + + public interface ILiveviewProvider : ICamera + { + Task StartLiveview(LiveviewReceiver receiver, CancellationToken token); + + Task StopLiveview(CancellationToken token); + } + + public interface ICameraStateProvider + { + event Action StateChanged; + + event Action StateUpdateFailed; + + ICameraState State { get; } + } + + public interface ICameraUdpConnector + { + void ProcessUdpMessage(byte[] argsData); + + Task Connect(int liveViewPort); + } + + public interface ICameraMFAssistController + { + Task MfAssistZoom(PinchStage stop, FloatPoint floatPoint, float f); + + Task MfAssistMove(PinchStage stage, FloatPoint fp); + } + + public interface ICameraAutofocusController + { + Task FocusPointMove(FloatPoint fp); + + Task FocusPointResize(PinchStage stage, FloatPoint point, float extend); + } + + public interface ICameraStateController + { + Task CaptureStart(); + + Task CaptureStop(); + + Task ChangeFocus(ChangeDirection changeDirection); + } + + public interface IEthernetCamera : ICamera + { + string CameraHost { get; } + + string Usn { get; } + } + + public interface IUdpCamera : IEthernetCamera + { + Task ProcessMessage(byte[] data); + } + + public interface ICamera : IDisposable + { + Task Connect(CancellationToken token); + + Task Disconnect(CancellationToken token); + } + + public interface IFocusAreas + { + IReadOnlyList Boxes { get; } + } + + public enum PinchStage + { + Start = 0, + Stop = 1, + Continue = 2, + Single = 3 + } + + public interface IBox + { + float Height { get; } + BoxProps Props { get; } + float Width { get; } + float X1 { get; } + float X2 { get; } + float Y1 { get; } + float Y2 { get; } + + int GetHashCode(); + } + + public struct BoxProps + { + public bool Failed { get; internal set; } + + public FocusAreaType Type { get; internal set; } + + public bool Equals(BoxProps other) + { + return Failed == other.Failed && Type == other.Type; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is BoxProps && Equals((BoxProps)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Failed.GetHashCode() * 397) ^ (int)Type; + } + } + } + + public enum FocusAreaType + { + OneAreaSelected = 0x0001, + FaceOther = 0xff01, + MainFace = 0x0002, + Eye = 0xff09, + TrackUnlock = 0xff03, + TrackLock = 0x0003, + MfAssistSelection = 0x0005, + MfAssistPinP = 0x0006, + MfAssistFullscreen = 0x0007, + MfAssistLimit = 0x0008, + Box = 0xff02, + Cross = 0xff04 + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/RecState.cs b/CameraApi.Core/RecState.cs similarity index 76% rename from Core/Camera/LumixData/RecState.cs rename to CameraApi.Core/RecState.cs index 5d11b1e..fed9ca2 100644 --- a/Core/Camera/LumixData/RecState.cs +++ b/CameraApi.Core/RecState.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Core { public enum RecState { diff --git a/CameraApi.Panasonic/CameraApi.Panasonic.csproj b/CameraApi.Panasonic/CameraApi.Panasonic.csproj new file mode 100644 index 0000000..2f0c101 --- /dev/null +++ b/CameraApi.Panasonic/CameraApi.Panasonic.csproj @@ -0,0 +1,12 @@ + + + + netstandard1.4 + + + + + + + + \ No newline at end of file diff --git a/Core/Camera/CameraMenuItem256.cs b/CameraApi.Panasonic/CameraMenuItem256.cs similarity index 97% rename from Core/Camera/CameraMenuItem256.cs rename to CameraApi.Panasonic/CameraMenuItem256.cs index d435478..3b55e9b 100644 --- a/Core/Camera/CameraMenuItem256.cs +++ b/CameraApi.Panasonic/CameraMenuItem256.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { public class CameraMenuItem256 : ICameraMenuItem { diff --git a/CameraApi.Panasonic/CameraMenuItemText.cs b/CameraApi.Panasonic/CameraMenuItemText.cs new file mode 100644 index 0000000..a27e5c5 --- /dev/null +++ b/CameraApi.Panasonic/CameraMenuItemText.cs @@ -0,0 +1,70 @@ +namespace CameraApi.Panasonic +{ + using CameraApi.Panasonic.LumixData; + + public class CameraMenuItemText : ICameraMenuItem + { + public CameraMenuItemText(Item i, string text) + { + Id = i.Id; + Text = text; + Command = i.CmdMode; + CommandType = i.CmdType; + Value = i.CmdValue; + } + + public CameraMenuItemText(string id, string text, string command, string commandtype, string value) + { + Id = id; + Text = text; + Command = command; + CommandType = commandtype; + Value = value; + } + + public string Command { get; } + + public string CommandType { get; } + + public string Id { get; } + + public string Text { get; } + + public string Value { get; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ICameraMenuItem)obj); + } + + public override int GetHashCode() + { + return Text?.GetHashCode() ?? 0; + } + + public override string ToString() + { + return Text; + } + + protected bool Equals(ICameraMenuItem other) + { + return string.Equals(Text, other.Text); + } + } +} \ No newline at end of file diff --git a/Core/Camera/CameraOrientation.cs b/CameraApi.Panasonic/CameraOrientation.cs similarity index 83% rename from Core/Camera/CameraOrientation.cs rename to CameraApi.Panasonic/CameraOrientation.cs index 67f6589..f8e36c4 100644 --- a/Core/Camera/CameraOrientation.cs +++ b/CameraApi.Panasonic/CameraOrientation.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { public enum CameraOrientation { diff --git a/CameraApi.Panasonic/CameraParser.cs b/CameraApi.Panasonic/CameraParser.cs new file mode 100644 index 0000000..191f288 --- /dev/null +++ b/CameraApi.Panasonic/CameraParser.cs @@ -0,0 +1,264 @@ +namespace CameraApi.Panasonic +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using CameraApi.Panasonic.LumixData; + using GMaster.Core.Tools; + + public abstract class CameraParser + { + protected static readonly TitledList DefaultIsoValues = new List + { + "100", + "200", + "400", + "800", + "1600", + "3200", + "6400", + "12800" + }.Select(i => new CameraMenuItemText(i, i, "setsetting", "iso", i)).ToTitledList("ISO Values"); + + public IReadOnlyDictionary ApertureBinary { get; } = new Dictionary + { + { 392, "1.7" }, + { 427, "1.8" }, + { 512, "2.0" }, + { 598, "2.2" }, + { 683, "2.5" }, + { 768, "2.8" }, + { 854, "3.2" }, + { 938, "3.5" }, + { 1024, "4.0" }, + { 1110, "4.5" }, + { 1195, "5.0" }, + { 1280, "5.6" }, + { 1366, "6.3" }, + { 1451, "7.1" }, + { 1536, "8" }, + { 1622, "9" }, + { 1707, "10" }, + { 1792, "11" }, + { 1878, "13" }, + { 1963, "14" }, + { 2048, "16" }, + { 2134, "18" }, + { 2219, "20" }, + { 2304, "22" }, + }; + + public HashCollection CurrentLanguage { get; set; } + + public HashCollection<Title> DefaultLanguage { get; set; } + + public abstract IReadOnlyDictionary<int, string> IsoBinary { get; } + + // public IReadOnlyDictionary<int, string> OpenedApertureBinary { get; } = new Dictionary<int, string> + // { + // { 1024, "4" }, + // { 1042, "4.1" }, + // { 1060, "4.2" }, + // { 1077, "4.3" }, + // { 1094, "4.4" }, + // { 1110, "4.5" }, + // { 1111, "4.5" }, + // { 1143, "4.7" }, + // { 1189, "5" }, + // { 1195, "5" }, + // { 1232, "5.3" }, + // { 1259, "5.5" }, + // { 1273, "5.6" }, + // { 1280, "5.6" }, + // { 1286, "5.7" }, + // { 1298, "5.8" } + // }; + public IReadOnlyDictionary<int, string> ShutterBinary { get; } = new Dictionary<int, string> + { + { 3584, "16000" }, + { 3499, "13000" }, + { 3414, "10000" }, + { 3328, "8000" }, + { 3243, "6400" }, + { 3158, "5000" }, + { 3072, "4000" }, + { 2987, "3200" }, + { 2902, "2500" }, + { 2816, "2000" }, + { 2731, "1600" }, + { 2646, "1300" }, + { 2560, "1000" }, + { 2475, "800" }, + { 2390, "640" }, + { 2304, "500" }, + { 2219, "400" }, + { 2134, "320" }, + { 2048, "250" }, + { 1963, "200" }, + { 1878, "160" }, + { 1792, "125" }, + { 1707, "100" }, + { 1622, "80" }, + { 1536, "60" }, + { 1451, "50" }, + { 1366, "40" }, + { 1280, "30" }, + { 1195, "25" }, + { 1110, "20" }, + { 1024, "15" }, + { 939, "13" }, + { 854, "10" }, + { 768, "8" }, + { 683, "6" }, + { 598, "5" }, + { 512, "4" }, + { 427, "3.2" }, + { 342, "2.5" }, + { 256, "2" }, + { 171, "1.6" }, + { 86, "1.3" }, + { 0, "1" }, + { -85, "1.3ˮ" }, + { -170, "1.6ˮ" }, + { -256, "2ˮ" }, + { -341, "2.5ˮ" }, + { -426, "3.2ˮ" }, + { -512, "4ˮ" }, + { -600, "5ˮ" }, + { -682, "6ˮ" }, + { -768, "8ˮ" }, + { -853, "10ˮ" }, + { -938, "13ˮ" }, + { -1024, "15ˮ" }, + { -1109, "20ˮ" }, + { -1194, "25ˮ" }, + { -1280, "30ˮ" }, + { -1365, "40ˮ" }, + { -1450, "50ˮ" }, + { -1536, "60ˮ" }, + { 16384, "B" } + }; + + public static string ApertureBinToText(int bin) + { + return Math.Pow(2, bin / 512.0).ToString("F1", CultureInfo.InvariantCulture); + } + + public static CameraParser TryParseMenuSet(RawMenuSet resultMenuSet, string lang, out MenuSet menuset, CameraParser[] parsers = null) + { + var innerParsers = parsers ?? new CameraParser[] + { + new GH4Parser(), + new GH3Parser() + }; + + var exceptions = new List<Exception>(); + foreach (var p in innerParsers) + { + try + { + var ms = p.ParseMenuSet(resultMenuSet, lang); + if (ms == null) + { + continue; + } + + menuset = ms; + return p; + } + catch (Exception ex) + { + exceptions.Add(ex); + } + } + + throw new AggregateException("Could not parse MenuSet", exceptions); + } + + public string GetText(string id) + { + if (CurrentLanguage != null && CurrentLanguage.TryGetValue(id, out var text)) + { + return text.Text; + } + + if (DefaultLanguage.TryGetValue(id, out var text2)) + { + return text2.Text; + } + + throw new KeyNotFoundException("Title not found: " + id); + } + + public abstract CurMenu ParseCurMenu(MenuInfo resultMenuInfo); + + public FocusPosition ParseFocus(string focus) + { + var values = focus.Split(','); + if (values.Length != 3) + { + throw new Exception("Strange focus values: " + focus); + } + + if (values[0] != "ok" || !int.TryParse(values[2], out var max) || !int.TryParse(values[1], out var val)) + { + Debug.WriteLine("Cannont parse focus: " + focus, "CameraParser"); + return null; + } + + return new FocusPosition { Value = max - val, Maximum = max }; + } + + public LensInfo ParseLensInfo(string raw) + { + Debug.WriteLine(raw, "LensInfo"); + var values = raw.Split(','); + return new LensInfo + { + MaxZoom = int.TryParse(values[7], out var res1) ? res1 : 0, + MinZoom = int.TryParse(values[8], out var res2) ? res2 : 0, + OpenedAperture = int.TryParse(values[2].Replace("/256", string.Empty), out var res3) ? res3 : 0, + ClosedAperture = int.TryParse(values[1].Replace("/256", string.Empty), out var res4) ? res4 : int.MaxValue, + HasPowerZoom = values[6] == "on" + }; + } + + public virtual MenuSet ParseMenuSet(RawMenuSet menuset, string lang) + { + var result = new MenuSet + { + ShutterSpeeds = new TitledList<CameraMenuItemText>(ShutterBinary.Select(p => new CameraMenuItemText(p.Key.ToString(), p.Value, "setsetting", "shtrspeed", p.Key + "/256")), "Shutter Speed"), + Apertures = new TitledList<CameraMenuItem256>(ApertureBinary.Select(p => new CameraMenuItem256(p.Key.ToString(), p.Value, "setsetting", "focal", p.Key)), "Aperture") + }; + + return InnerParseMenuSet(result, menuset, lang) ? result : null; + } + + protected abstract bool InnerParseMenuSet(MenuSet result, RawMenuSet menuset, string lang); + + protected CameraMenuItemText ToMenuItem(Item item) + { + if (item == null) + { + return null; + } + + return new CameraMenuItemText(item, GetText(item.TitleId)); + } + + protected TitledList<CameraMenuItemText> ToMenuItems(Item menuitem) + { + try + { + return menuitem?.Items + .Select(i => new CameraMenuItemText(i, GetText(i.TitleId))) + .ToTitledList(GetText(menuitem.TitleId)); + } + catch (KeyNotFoundException) + { + return new TitledList<CameraMenuItemText>(); + } + } + } +} \ No newline at end of file diff --git a/Core/Camera/CameraProfile.cs b/CameraApi.Panasonic/CameraProfile.cs similarity index 98% rename from Core/Camera/CameraProfile.cs rename to CameraApi.Panasonic/CameraProfile.cs index 29f647b..10fdad3 100644 --- a/Core/Camera/CameraProfile.cs +++ b/CameraApi.Panasonic/CameraProfile.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { using System.Collections.Generic; diff --git a/Core/Camera/CurMenu.cs b/CameraApi.Panasonic/CurMenu.cs similarity index 83% rename from Core/Camera/CurMenu.cs rename to CameraApi.Panasonic/CurMenu.cs index 9f96eae..3062985 100644 --- a/Core/Camera/CurMenu.cs +++ b/CameraApi.Panasonic/CurMenu.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { using System.Collections.Generic; diff --git a/Core/Camera/DeviceInfo.cs b/CameraApi.Panasonic/DeviceInfo.cs similarity index 97% rename from Core/Camera/DeviceInfo.cs rename to CameraApi.Panasonic/DeviceInfo.cs index f814b26..d6d4779 100644 --- a/Core/Camera/DeviceInfo.cs +++ b/CameraApi.Panasonic/DeviceInfo.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { using Rssdp; diff --git a/CameraApi.Panasonic/FloatPoint.cs b/CameraApi.Panasonic/FloatPoint.cs new file mode 100644 index 0000000..50ac0d8 --- /dev/null +++ b/CameraApi.Panasonic/FloatPoint.cs @@ -0,0 +1,36 @@ +namespace CameraApi.Panasonic +{ + public struct FloatPoint + { + public FloatPoint(float x, float y) + { + X = x; + Y = y; + } + + public FloatPoint(double x, double y) + { + X = (float)x; + Y = (float)y; + } + + public float X { get; } + + public float Y { get; } + + public static FloatPoint operator -(FloatPoint p, float a) + { + return new FloatPoint(p.X - a, p.Y - a); + } + + public static FloatPoint operator +(FloatPoint p, float a) + { + return new FloatPoint(p.X + a, p.Y + a); + } + + public bool IsInRange(float min, float max) + { + return X >= min && Y >= min && X <= max && Y <= max; + } + } +} \ No newline at end of file diff --git a/Core/Camera/FocusAreas.cs b/CameraApi.Panasonic/FocusAreas.cs similarity index 98% rename from Core/Camera/FocusAreas.cs rename to CameraApi.Panasonic/FocusAreas.cs index 355b51b..7cb40d1 100644 --- a/Core/Camera/FocusAreas.cs +++ b/CameraApi.Panasonic/FocusAreas.cs @@ -1,7 +1,8 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { using System.Collections.Generic; - using LumixData; + using CameraApi.Core; + using CameraApi.Panasonic.LumixData; public class FocusAreas { diff --git a/CameraApi.Panasonic/GH3Parser.cs b/CameraApi.Panasonic/GH3Parser.cs new file mode 100644 index 0000000..dcae59e --- /dev/null +++ b/CameraApi.Panasonic/GH3Parser.cs @@ -0,0 +1,87 @@ +namespace CameraApi.Panasonic +{ + using System.Collections.Generic; + using System.Linq; + using CameraApi.Panasonic.LumixData; + + public class GH3Parser : CameraParser + { + public override IReadOnlyDictionary<int, string> IsoBinary { get; } = new Dictionary<int, string> + { + { 7167, "25600" }, + { 6911, "20000" }, + { 6655, "16000" }, + { 6399, "12800" }, + { 5887, "10000" }, + { 5375, "8000" }, + { 5119, "6400" }, + { 4863, "5000" }, + { 4607, "4000" }, + { 4351, "3200" }, + { 4095, "2500" }, + { 3839, "2000" }, + { 3583, "1600" }, + { 3327, "1250" }, + { 3071, "1000" }, + { 2815, "800" }, + { 2559, "640" }, + { 2303, "500" }, + { 2047, "400" }, + { 1791, "320" }, + { 1535, "250" }, + { 1279, "200" }, + { 1023, "160" }, + { 767, "125" }, + { -1, "auto" } + }; + + public override CurMenu ParseCurMenu(MenuInfo info) + { + var result = new CurMenu(); + + foreach (var item in info.MainMenu.Concat(info.Photosettings).Concat(info.Qmenu)) + { + result.Enabled[item.Id] = item.Enable == YesNo.Yes; + } + + return result; + } + + protected override bool InnerParseMenuSet(MenuSet result, RawMenuSet menuset, string lang) + { + DefaultLanguage = menuset.TitleList.Languages.Single(l => l.Default == YesNo.Yes).Titles; + CurrentLanguage = menuset.TitleList.Languages.TryGetValue(lang, out var cur) ? cur.Titles : null; + var photosettings = menuset.MainMenu.Items["menu_item_id_photo_settings"].Items; + var qmenu = menuset.Qmenu.Items; + var driveMode = menuset.DriveMode.Items; + + result.LiveviewQuality = ToMenuItems(menuset.MainMenu + .Items["menu_item_id_liveview_settings"] + .Items["menu_item_id_liveview_quality"]); + + result.CreativeControls = ToMenuItems(photosettings["menu_item_id_crtv_ctrl"]); + result.AutofocusModes = ToMenuItems(photosettings["menu_item_id_afmode"]); + result.PhotoStyles = ToMenuItems(photosettings["menu_item_id_ph_sty"]); + result.FlashModes = ToMenuItems(photosettings["menu_item_id_flash"]); + result.PhotoAspects = ToMenuItems(photosettings["menu_item_id_asprat"]); + result.PhotoSizes = ToMenuItems(photosettings["menu_item_id_pctsiz"]); + result.VideoQuality = ToMenuItems(photosettings["menu_item_id_v_quality"]); + result.MeteringMode = ToMenuItems(photosettings["menu_item_id_lightmet"]); + + result.ExposureShifts = ToMenuItems(qmenu["menu_item_id_exposure2"]); + result.IsoValues = ToMenuItems(qmenu["menu_item_id_sensitivity"]) ?? DefaultIsoValues; + result.WhiteBalances = ToMenuItems(qmenu["menu_item_id_whitebalance"]); + + result.SingleShootMode = ToMenuItem(driveMode["menu_item_id_1shoot"]); + result.BurstModes = ToMenuItems(driveMode["menu_item_id_burst"]) ?? ToMenuItems(photosettings["menu_item_id_burst"]); + + result.VideoFormat = null; + + result.Angles = null; + result.CustomMultiModes = null; + result.DbValues = null; + result.PeakingModes = null; + return true; + } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/GH4Parser.cs b/CameraApi.Panasonic/GH4Parser.cs new file mode 100644 index 0000000..3b64b32 --- /dev/null +++ b/CameraApi.Panasonic/GH4Parser.cs @@ -0,0 +1,89 @@ +namespace CameraApi.Panasonic +{ + using System.Collections.Generic; + using System.Linq; + using CameraApi.Panasonic.LumixData; + + public class GH4Parser : CameraParser + { + public override IReadOnlyDictionary<int, string> IsoBinary { get; } = new Dictionary<int, string> + { + { 3, "auto" }, + { 8963, "100" }, + { 515, "125" }, + { 771, "160" }, + { 1027, "200" }, + { 1283, "250" }, + { 1539, "320" }, + { 1795, "400" }, + { 2051, "500" }, + { 2307, "640" }, + { 2563, "800" }, + { 2819, "1000" }, + { 3075, "1250" }, + { 3331, "1600" }, + { 3587, "2000" }, + { 3843, "2500" }, + { 4099, "3200" }, + { 4355, "4000" }, + { 4611, "5000" }, + { 4867, "6400" }, + { 5123, "8000" }, + { 5635, "10000" }, + { 6147, "12800" }, + { 8195, "16000" }, + { 8451, "20000" }, + { 8707, "25600" } + }; + + public override CurMenu ParseCurMenu(MenuInfo info) + { + var result = new CurMenu(); + + foreach (var item in info.MainMenu.Concat(info.Photosettings).Concat(info.Qmenu2)) + { + result.Enabled[item.Id] = item.Enable == YesNo.Yes; + } + + return result; + } + + protected override bool InnerParseMenuSet(MenuSet result, RawMenuSet menuset, string lang) + { + var photosettings = menuset?.Photosettings?.Items; + if (photosettings == null) + { + return false; + } + + var qmenu = menuset.Qmenu2.Items; + DefaultLanguage = menuset.TitleList.Languages.Single(l => l.Default == YesNo.Yes).Titles; + CurrentLanguage = menuset.TitleList.Languages.TryGetValue(lang, out var cur) ? cur.Titles : null; + + result.LiveviewQuality = ToMenuItems(menuset.MainMenu + .Items["menu_item_id_liveview_settings"] + .Items["menu_item_id_liveview_quality"]); + + result.CreativeControls = ToMenuItems(photosettings["menu_item_id_crtv_ctrl"]); + result.PhotoStyles = ToMenuItems(photosettings["menu_item_id_ph_sty"]); + result.PhotoSizes = ToMenuItems(photosettings["menu_item_id_pctsiz"]); + result.PhotoQuality = ToMenuItems(photosettings["menu_item_id_quality"]); + result.MeteringMode = ToMenuItems(photosettings["menu_item_id_lightmet"]); + result.VideoFormat = ToMenuItems(photosettings["menu_item_id_videoformat"]); + result.VideoQuality = ToMenuItems(photosettings["menu_item_id_v_quality"]); + result.FlashModes = ToMenuItems(photosettings["menu_item_id_flash"]); + + result.Angles = ToMenuItems(qmenu["menu_item_id_f_and_ss_angle"]); + result.ExposureShifts = ToMenuItems(qmenu["menu_item_id_exposure3"] ?? qmenu["menu_item_id_exposure2"]); + result.AutofocusModes = ToMenuItems(qmenu["menu_item_id_afmode"]); + result.CustomMultiModes = ToMenuItems(qmenu["menu_item_id_afmode"].Items["menu_item_id_afmode_custom_multi"]); + result.IsoValues = ToMenuItems(qmenu["menu_item_id_sensitivity"]); + result.DbValues = ToMenuItems(qmenu["menu_item_id_sensitivity_db"]); + result.WhiteBalances = ToMenuItems(qmenu["menu_item_id_whitebalance"]); + result.BurstModes = ToMenuItems(qmenu["menu_item_id_burst"]); + result.PeakingModes = ToMenuItems(qmenu["menu_item_id_peaking"]); + + return true; + } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/GlobalSuppressions.cs b/CameraApi.Panasonic/GlobalSuppressions.cs new file mode 100644 index 0000000..afa37fc --- /dev/null +++ b/CameraApi.Panasonic/GlobalSuppressions.cs @@ -0,0 +1,10 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "My style", Scope = "module")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1652:Enable XML documentation output", Justification = "My style")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File must have header", Justification = "My style")] \ No newline at end of file diff --git a/CameraApi.Panasonic/Http.cs b/CameraApi.Panasonic/Http.cs new file mode 100644 index 0000000..1113638 --- /dev/null +++ b/CameraApi.Panasonic/Http.cs @@ -0,0 +1,129 @@ +namespace CameraApi.Panasonic +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Xml.Serialization; + using CameraApi.Panasonic.LumixData; + using GMaster.Core.Network; + using GMaster.Core.Tools; + + public class Http : IDisposable + { + private readonly Uri baseUri; + private readonly IHttpClient camcgi; + + public Http(Uri baseUrl, IHttpClient client) + { + baseUri = baseUrl; + camcgi = client; + } + + public static TResponse ReadResponse<TResponse>(string str) + { + var serializer = new XmlSerializer(typeof(TResponse)); + return (TResponse)serializer.Deserialize(new StringReader(str)); + } + + public void Dispose() + { + camcgi.Dispose(); + } + + //public async Task<TResponse> Get<TResponse>(string path) + // where TResponse : BaseRequestResult + //{ + // try + // { + // using (var source = new CancellationTokenSource(1000)) + // { + // return await Get<TResponse>(path, source.Token); + // } + // } + // catch (OperationCanceledException) + // { + // throw new TimeoutException(); + // } + //} + + public async Task<TResponse> Get<TResponse>(string path, CancellationToken token) + where TResponse : BaseRequestResult + { + var uri = new Uri(baseUri, path); + using (var response = await camcgi.GetAsync(uri, token)) + { + using (var content = await response.GetContent()) + { + var str = await content.ReadToEndAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LumixException($"Request failed with code {response.Code}: {path}\r\n{str}"); + } + + try + { + var product = ReadResponse<TResponse>(str); + if (product.Result != "ok") + { + throw new LumixException( + ValueToEnum<LumixError>.Parse(product.Result, LumixError.Unknown), + $"Not ok result. Request deserialize failed: {path}\r\n{str}"); + } + + return product; + } + catch (LumixException) + { + throw; + } + catch (Exception ex) + { + throw new LumixException($"Request deserialize failed: {path}\r\n{str}", ex); + } + } + } + } + + public async Task<TResponse> Get<TResponse>(Dictionary<string, string> parameters, CancellationToken cancel) + where TResponse : BaseRequestResult + { + return await Get<TResponse>("?" + string.Join("&", parameters.Select(p => p.Key + "=" + p.Value)), cancel); + } + + //public async Task<string> GetString(string path) + //{ + // try + // { + // using (var source = new CancellationTokenSource(1000)) + // { + // return await GetString(path, source.Token); + // } + // } + // catch (OperationCanceledException) + // { + // throw new TimeoutException(); + // } + //} + + public async Task<string> GetString(string path, CancellationToken token) + { + var uri = new Uri(baseUri, path); + using (var response = await camcgi.GetAsync(uri, token)) + { + using (var content = await response.GetContent()) + { + var str = await content.ReadToEndAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LumixException($"Request failed with code {response.Code}: {path}\r\n{str}"); + } + + return str; + } + } + } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/ICameraMenuItem.cs b/CameraApi.Panasonic/ICameraMenuItem.cs new file mode 100644 index 0000000..35ecf4e --- /dev/null +++ b/CameraApi.Panasonic/ICameraMenuItem.cs @@ -0,0 +1,15 @@ +namespace CameraApi.Panasonic +{ + using CameraApi.Core; + + public interface ICameraMenuItem : IActionItem + { + string Id { get; } + + string Command { get; } + + string CommandType { get; } + + string Value { get; } + } +} \ No newline at end of file diff --git a/Core/Camera/IntPoint.cs b/CameraApi.Panasonic/IntPoint.cs similarity index 73% rename from Core/Camera/IntPoint.cs rename to CameraApi.Panasonic/IntPoint.cs index 7124913..4730fd9 100644 --- a/Core/Camera/IntPoint.cs +++ b/CameraApi.Panasonic/IntPoint.cs @@ -1,6 +1,6 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { - using static System.Math; + using System; public struct IntPoint { @@ -22,7 +22,7 @@ public IntPoint(FloatPoint p1, float m) public IntPoint Clamp(int min, int max) { - return new IntPoint(Max(Min(X, max), min), Max(Min(Y, max), min)); + return new IntPoint(Math.Max(Math.Min(X, max), min), Math.Max(Math.Min(Y, max), min)); } } } \ No newline at end of file diff --git a/Core/Camera/LensInfo.cs b/CameraApi.Panasonic/LensInfo.cs similarity index 97% rename from Core/Camera/LensInfo.cs rename to CameraApi.Panasonic/LensInfo.cs index 38d14bb..d8638be 100644 --- a/Core/Camera/LensInfo.cs +++ b/CameraApi.Panasonic/LensInfo.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { public class LensInfo { diff --git a/CameraApi.Panasonic/Lumix.Internal.cs b/CameraApi.Panasonic/Lumix.Internal.cs new file mode 100644 index 0000000..6d4eef3 --- /dev/null +++ b/CameraApi.Panasonic/Lumix.Internal.cs @@ -0,0 +1,423 @@ +namespace CameraApi.Panasonic +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Threading; + using System.Threading.Tasks; + using CameraApi.Panasonic.LumixData; + using GMaster.Core.Network; + using GMaster.Core.Tools; + using Nito.AsyncEx; + + public partial class Lumix + { + private static readonly Dictionary<string, RunnableCommandInfo> RunnableCommands; + + private readonly Http http; + + private readonly Timer stateTimer; + private readonly AsyncLock stateUpdatingLock = new AsyncLock(); + private bool autoreviewUnlocked; + private bool firstconnect = true; + private bool isConnecting = true; + private int isUpdatingState; + private string language; + private bool reportingAction; + private int stateFiledTimes; + + static Lumix() + { + RunnableCommands = new Dictionary<string, RunnableCommandInfo>(20); + foreach (var method in typeof(Lumix).GetRuntimeMethods()) + { + var runnable = method.GetCustomAttribute<RunnableActionAttribute>(); + if (runnable != null) + { + var rett = method.ReturnType; + var info = new RunnableCommandInfo + { + Method = method, + Group = runnable.Group, + Async = rett == typeof(Task) || (rett.IsConstructedGenericType && rett.GetGenericTypeDefinition() == typeof(Task<>)) + }; + + RunnableCommands[method.Name] = info; + } + } + } + + public Lumix(DeviceInfo device, IHttpClient client) + { + Device = device; + Profile = CameraProfile.Profiles.TryGetValue(device.ModelName, out var prof) ? prof : new CameraProfile(); + + stateTimer = new Timer(StateTimer_Tick, null, -1, -1); + var baseUri = new Uri($"http://{CameraHost}/cam.cgi"); + + http = new Http(baseUri, client); + } + + public delegate Task<IntPoint?> LiveViewUpdatedDelegate(ArraySegment<byte> data); + + public event Action<Lumix, string, object[]> ActionCalled; + + public event LiveViewUpdatedDelegate LiveViewUpdated; + + public event Action ProfileUpdated; + + public event Action<Lumix, UpdateStateFailReason> StateUpdateFailed; + + public enum UpdateStateFailReason + { + RequestFailed = 0, + LumixException = 1, + NotConnected = 2 + } + + public string CameraHost => Device.Host; + + public string Usn => Device.Usn; + + public DeviceInfo Device { get; } + + public LumixState LumixState { get; } = new LumixState(); + + public OffFrameProcessor OffFrameProcessor { get; private set; } + + public CameraProfile Profile { get; } + + public string Uuid => Device.Uuid; + + public static MethodGroup GetCommandCroup(string method) => RunnableCommands[method].Group; + + public void Dispose() + { + http.Dispose(); + stateTimer.Dispose(); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return (obj.GetType() == GetType()) && Equals((Lumix)obj); + } + + public override int GetHashCode() + { + return Uuid?.GetHashCode() ?? 0; + } + + public async Task ProcessMessage(byte[] buf) + { + try + { + if (OffFrameProcessor == null) + { + return; + } + + var slice = new Slice(buf); + + var imageStart = OffFrameProcessor.CalcImageStart(slice); + + if (LiveViewUpdated != null && imageStart > 60 && imageStart < buf.Length - 100 && + buf[imageStart] == 0xff && buf[imageStart + 1] == 0xd8 && buf[buf.Length - 2] == 0xff && + buf[buf.Length - 1] == 0xd9) + { + IntPoint? size = null; + foreach (var ev in LiveViewUpdated.GetInvocationList().Cast<LiveViewUpdatedDelegate>()) + { + size = await ev(new ArraySegment<byte>(buf, imageStart, buf.Length - imageStart)); + } + + if (size != null) + { + OffFrameProcessor.Process(new Slice(slice, 0, imageStart), size.Value); + } + + if (firstconnect) + { + firstconnect = false; + LogTrace($"Camera connected and got first frame {Device.ModelName}"); + } + } + } + catch (Exception ex) + { + if (firstconnect) + { + firstconnect = false; + LogError($"Camera failed first frame {Device.ModelName}", ex); + } + } + } + + protected bool Equals(Lumix other) + { + return Equals(Uuid, other.Uuid); + } + + private bool CheckAlreadyConnected(string raw) + { + if (raw == null) + { + return false; + } + + if (!raw.StartsWith("<xml>")) + { + return false; + } + + try + { + var res = Http.ReadResponse<BaseRequestResult>(raw); + return res.Result == "err_already_connected"; + } + catch (Exception) + { + return false; + } + } + + private void LogError(string message, string place = null, [CallerFilePath] string fileName = null, [CallerMemberName] string methodName = null) + { + var placeText = place != null ? $"place.{place}," : string.Empty; + Log.Error(message, $"{placeText}camera.{Device.ModelName}", fileName, methodName); + } + + private void LogError(string message, Exception ex, [CallerFilePath] string fileName = null, [CallerMemberName] string methodName = null) + { + Log.Error(message, ex, $"camera.{Device.ModelName}", fileName, methodName); + } + + private void LogError(string message, object obj, [CallerFilePath] string fileName = null, [CallerMemberName] string methodName = null) + { + Log.Trace(message, Log.Severity.Error, obj, $"camera.{Device.ModelName}", fileName, methodName); + } + + private void LogError(Exception ex, [CallerFilePath] string fileName = null, [CallerMemberName] string methodName = null) + { + Log.Error(ex.Message, ex, $"camera.{Device.ModelName}", fileName, methodName); + } + + private void LogTrace(string message, string place = null, [CallerFilePath] string fileName = null, [CallerMemberName] string methodName = null) + { + var placeText = place != null ? $"place.{place}," : string.Empty; + Log.Trace(message, Log.Severity.Trace, new { Camera = Device.ModelName }, $"{placeText}camera.{Device.ModelName}", fileName, methodName); + } + + private async Task OffFrameProcessor_LensChanged(CancellationToken cancel) + { + try + { + LumixState.MenuSet = await GetMenuSet(cancel); + } + catch (Exception ex) + { + LogError(ex); + } + } + + private void ReportAction(string method, object[] prm) + { + if (!reportingAction) + { + try + { + reportingAction = true; + ActionCalled?.Invoke(this, method, prm); + } + finally + { + reportingAction = false; + } + } + } + + private void ReportAction(object p1, [CallerMemberName] string method = null) + { + ReportAction(method, new[] { p1 }); + } + + private void ReportAction(object p1, object p2, [CallerMemberName] string method = null) + { + ReportAction(method, new[] { p1, p2 }); + } + + private void ReportAction(object p1, object p2, object p3, [CallerMemberName] string method = null) + { + ReportAction(method, new[] { p1, p2, p3 }); + } + + private void ReportAction([CallerMemberName] string method = null) + { + ReportAction(method, new object[0]); + } + + private async Task<bool> RequestAccess(CancellationToken token) + { + var noconnection = 0; + do + { + try + { + var str = await http.GetString($"?mode=accctrl&type=req_acc&value={Device.Uuid}&value2=SM-G9350", token); + if (str.StartsWith("<?xml")) + { + break; + } + + var fields = str.Split(','); + if (fields.FirstOrDefault() == "ok") + { + break; + } + + noconnection = 0; + } + catch (COMException ex) + { + Log.Error(ex); + if ((uint)ex.HResult == 0x80072efd) + { + if (++noconnection > 2) + { + Log.Error("Cannot connect to " + CameraHost); + return false; + } + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + LogError(ex); + } + + await Task.Delay(1000, token); + } + while (true); + + return true; + } + + private async void StateTimer_Tick(object sender) + { + if (isConnecting) + { + return; + } + + if (Interlocked.CompareExchange(ref isUpdatingState, 1, 0) == 0) + { + try + { + var lastState = LumixState.State; + using (var cancellation = new CancellationTokenSource(100)) + { + var state = LumixState.State = await GetState(cancellation.Token); + if (lastState.Operate != null && state.Operate == null) + { + Debug.WriteLine("Not connected?", "StateUpdate"); + StateUpdateFailed?.Invoke(this, UpdateStateFailReason.NotConnected); + return; + } + } + + stateFiledTimes = 0; + } + catch (ConnectionLostException) + { + Debug.WriteLine("Connection lost", "Connection"); + if (++stateFiledTimes > 3) + { + LogTrace("Connection lost"); + StateUpdateFailed?.Invoke(this, UpdateStateFailReason.RequestFailed); + } + } + catch (LumixException ex) + { + Debug.WriteLine(ex); + StateUpdateFailed?.Invoke(this, UpdateStateFailReason.LumixException); + } + catch (Exception) + { + if (++stateFiledTimes > 3) + { + StateUpdateFailed?.Invoke(this, UpdateStateFailReason.RequestFailed); + } + } + finally + { + isUpdatingState = 0; + } + } + } + + private async Task<bool> Try<T>(Func<CancellationToken, Task<T>> act, CancellationToken token) + { + try + { + await act(token); + return true; + } + catch (ConnectionLostException) + { + Debug.WriteLine("Connection lost", "Connection"); + return false; + } + catch (Exception ex) + { + LogError("Camera action failed", ex); + return false; + } + } + + private async Task<bool> TryGet(string path, CancellationToken token) + { + return await Try(async cancel => await http.Get<BaseRequestResult>(path, cancel), token); + } + + private async Task<bool> TryGet(Dictionary<string, string> dict, CancellationToken token) + { + return await Try(async cancel => await http.Get<BaseRequestResult>(dict, cancel), token); + } + + private async Task<bool> TryGetString(string path, CancellationToken token) + { + return await Try( + async cancel => + { + var res = await http.GetString(path, cancel); + Debug.WriteLine(res, "GetString"); + return res; + }, + token); + } + + private class RunnableCommandInfo + { + public bool Async { get; set; } + + public MethodGroup Group { get; set; } + + public MethodInfo Method { get; set; } + } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/Lumix.cs b/CameraApi.Panasonic/Lumix.cs new file mode 100644 index 0000000..a69b33f --- /dev/null +++ b/CameraApi.Panasonic/Lumix.cs @@ -0,0 +1,677 @@ +namespace CameraApi.Panasonic +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using CameraApi.Core; + using CameraApi.Panasonic.LumixData; + using GMaster.Core.Network; + using GMaster.Core.Tools; + + public partial class Lumix : ILiveviewProvider, IUdpCamera + { + private CancellationTokenSource connectCancellation; + + public Lumix(string lang) + { + language = lang; + } + + private float lastOldPinchSize; + + public CameraParser Parser { get; private set; } + + [RunnableAction(MethodGroup.Capture)] + public async Task<bool> CaptureStart(CancellationToken token) + { + ReportAction(); + return await TryGet("?mode=camcmd&value=capture", token); + } + + [RunnableAction(MethodGroup.Capture)] + public async Task<bool> CaptureStop(CancellationToken token) + { + ReportAction(); + return await TryGet("?mode=camcmd&value=capture_cancel", token); + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> ChangeFocus(ChangeDirection focusDirection, CancellationToken token) + { + ReportAction(focusDirection); + return await Try( + async cancel => + { + var focus = await http.GetString("?mode=camctrl&type=focus&value=" + focusDirection.GetString(), cancel); + + var fp = Parser.ParseFocus(focus); + if (fp == null) + { + return false; + } + + if (fp.Maximum != LumixState.MaximumFocus) + { + LumixState.MaximumFocus = fp.Maximum; + } + + if (fp.Value != LumixState.CurrentFocus) + { + LumixState.CurrentFocus = fp.Value; + } + + return true; + }, token); + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> ChangeZoom(ChangeDirection zoomDirection, CancellationToken token) + { + ReportAction(zoomDirection); + return await TryGet("?mode=camcmd&value=" + zoomDirection.GetString(), token); + } + + public async Task StartLiveview(LiveviewReceiver receiver, CancellationToken cancellation) + { + if (!isLiveview) + { + isLiveview = true; + const int liveviewport = 23456; + await http.Get<BaseRequestResult>($"?mode=startstream&value={liveviewport}", cancellation); + } + } + + public async Task Connect(CancellationToken cancel) + { + + var connectStage = 0; + + connectCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancel); + var token = connectCancellation.Token; + + try + { + LogTrace("Connecting camera " + Device.ModelName); + do + { + try + { + if (Profile.RequestConnection) + { + await RequestAccess(token); + } + + connectStage = 1; + + if (Profile.SetDeviceName) + { + await TryGet("?mode=setsetting&type=device_name&value=SM-G9350", token); + } + + connectStage = 2; + + LumixState.Reset(); + + LumixState.MenuSet = await GetMenuSet(token); + if (LumixState.MenuSet == null) + { + LogError($"Camera connection failed on Stage {connectStage} for camera {Device.ModelName}", "CameraConnect"); + LumixState.IsLimited = true; + } + + if (!LumixState.IsLimited) + { + LumixState.CurMenu = await GetCurMenu(token); + } + + connectStage = 3; + await SwitchToRec(token); + + connectStage = 4; + LumixState.LensInfo = await GetLensInfo(token); + + connectStage = 5; + LumixState.State = await GetState(token); + + token.ThrowIfCancellationRequested(); + break; + } + catch (ConnectionLostException) + { + Debug.WriteLine("Connection lost", "Connection"); + } + catch (TimeoutException) + { + Debug.WriteLine("Timeout", "Connection"); + } + catch (Exception ex) + { + LogError($"Camera connection failed on Stage {connectStage} for camera {Device.ModelName}", ex); + } + + await Task.Delay(1000, token); + } + while (true); + + if (OffFrameProcessor == null) + { + connectStage = 7; + OffFrameProcessor = new OffFrameProcessor(Device.ModelName, Parser, LumixState); + OffFrameProcessor.LensChanged += OffFrameProcessor_LensChanged; + } + + stateTimer.Change(2000, 2000); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + LogError($"Camera connection failed on Stage {connectStage} for camera {Device.ModelName}", e); + throw; + } + finally + { + var temp = connectCancellation; + connectCancellation = null; + temp.Dispose(); + } + } + + public async Task Disconnect(CancellationToken token) + { + try + { + connectCancellation?.Cancel(); + + stateTimer.Change(-1, -1); + Debug.WriteLine("Timer stopped", "Disconnect"); + + await StopLiveview(token); + } + catch (Exception e) + { + LogError(e); + } + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> FocusPointMove(FloatPoint p, CancellationToken cancel) + { + ReportAction(p); + var point = $"{(int)(p.X * 1000)}/{(int)(p.Y * 1000)}"; + return await OldNewAction( + async c => await TryGet($"?mode=camctrl&type=touch&value={point}&value2=on", c), + async c => await TryGet($"?mode=camctrl&type=touchaf&value={point}", c), + Profile.NewTouch, + f => Profile.NewTouch = f, + cancel); + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> FocusPointResize(PinchStage stage, FloatPoint p, float size, CancellationToken cancel) + { + ReportAction(size); + return await OldNewAction( + async c => await PinchZoom(stage, p, size, c), + async c => + { + var dif = size - lastOldPinchSize; + if (Math.Abs(dif) > 0.03f) + { + lastOldPinchSize = size; + var val = dif > 0 ? "up" : "down"; + await http.Get<BaseRequestResult>($"?mode=camctrl&type=touchaf_chg_area&value={val}", c); + } + if (stage == PinchStage.Stop || stage == PinchStage.Single) + { + lastOldPinchSize = 0; + } + + return true; + }, + Profile.NewTouch, + f => Profile.NewTouch = f, + cancel); + } + + private readonly IDictionary<LumixFocusMode, FocusMode> ToFocusMode = new Dictionary<LumixFocusMode, FocusMode> + { + { LumixFocusMode.MF, FocusMode.MF }, + { LumixFocusMode.AFC, FocusMode.AFC }, + { LumixFocusMode.AFF, FocusMode.AFF }, + { LumixFocusMode.AFS, FocusMode.AFS }, + { LumixFocusMode.Unknown, FocusMode.Unknown } + }; + + private bool isLiveview; + + public async Task<FocusMode> GetFocusMode(CancellationToken cancel) + { + try + { + var result = await http.Get<FocusModeRequestResult>("?mode=getsetting&type=focusmode", cancel); + return ToFocusMode[result.Value.FocusMode]; + } + catch (Exception e) + { + Debug.WriteLine(e); + return ToFocusMode[LumixFocusMode.Unknown]; + } + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> MfAssistAf(CancellationToken cancel) + { + ReportAction(); + if (Profile.ManualFocusAF) + { + return await Try( + async c => + { + try + { + await http.Get<BaseRequestResult>("?mode=camcmd&value=oneshot_af", c); + return true; + } + catch (LumixException ex) + { + if (ex.Error == LumixError.ErrorParam) + { + Profile.ManualFocusAF = false; + ProfileUpdated?.Invoke(); + return false; + } + throw; + } + }, cancel); + } + + return false; + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> MfAssistMove(PinchStage stage, FloatPoint p, CancellationToken cancel) + { + ReportAction(stage, p); + return await OldNewAction( + async c => await NewMfAssistMove(stage, p, c), + async c => await TryGetString($"?mode=camctrl&type=mf_asst&value={(int)(p.X * 1000)}/{(int)(p.Y * 1000)}", c), + Profile.NewTouch, + f => Profile.NewTouch = f, + cancel); + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> MfAssistOff(CancellationToken cancel) + { + ReportAction(); + return await OldNewAction( + async c => await TryGetString("?mode=camctrl&type=asst_disp&value=off&value2=mf_asst/0/0", c), + async c => await TryGet("?mode=setsetting&type=mf_asst_mag&value=1", c), + Profile.NewTouch, + f => Profile.NewTouch = f, + cancel); + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> MfAssistPinp(bool pInP, CancellationToken cancel) + { + ReportAction(pInP); + var val = pInP ? "pinp" : "full"; + return await TryGetString($"?mode=camctrl&type=asst_disp&value={val}&value2=mf_asst/0/0", cancel); + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> MfAssistZoom(PinchStage stage, FloatPoint p, float size, CancellationToken cancel) + { + ReportAction(stage, p, size); + return await OldNewAction( + async c => await PinchZoom(stage, p, size, c), + async c => await OldMfAssistZoom(stage, p, size, c), + Profile.NewTouch, + f => Profile.NewTouch = f, + cancel); + } + + [RunnableAction(MethodGroup.Capture)] + public async Task<bool> RecStart(CancellationToken cancel) + { + ReportAction(); + return await Try( + async c => + { + await http.Get<BaseRequestResult>("?mode=camcmd&value=video_recstart", c); + if (Profile.RecStop) + { + LumixState.RecState = RecState.Unknown; + await Task.Delay(100); + LumixState.State = await GetState(c); + return true; + } + else + { + LumixState.RecState = RecState.StopNotSupported; + } + + return true; + }, cancel); + } + + [RunnableAction(MethodGroup.Capture)] + public async Task<bool> RecStop(CancellationToken cancel) + { + ReportAction(); + if (Profile.RecStop) + { + try + { + await http.Get<BaseRequestResult>("?mode=camcmd&value=video_recstop", cancel); + await Task.Delay(500); + LumixState.State = await GetState(cancel); + return true; + } + catch (LumixException ex) + { + if (ex.Error == LumixError.ErrorParam) + { + Debug.WriteLine("RecStop not supported", "RecStop"); + Profile.RecStop = false; + LumixState.RecState = RecState.StopNotSupported; + } + else + { + Debug.WriteLine(ex); + } + } + } + + return false; + } + + [RunnableAction(MethodGroup.Focus)] + public async Task<bool> ReleaseTouchAF(CancellationToken cancel) + { + ReportAction(); + return await TryGet("?mode=camcmd&value=touchafrelease", cancel); + } + + public async Task RunCommand(string methodName, object[] prm) + { + if (!reportingAction) + { + try + { + reportingAction = true; + if (!RunnableCommands.TryGetValue(methodName, out var command)) + { + throw new ArgumentException("Wrong command: " + methodName); + } + + if (command.Async) + { + await (Task)command.Method.Invoke(this, prm); + } + else + { + command.Method.Invoke(this, prm); + } + } + finally + { + reportingAction = false; + } + } + } + + [RunnableAction(MethodGroup.Properties)] + public async Task<bool> SendMenuItem(ICameraMenuItem value, CancellationToken cancel) + { + ReportAction(value); + if (value != null) + { + return await TryGet( + new Dictionary<string, string> + { + { "mode", value.Command }, + { "type", value.CommandType }, + { "value", value.Value } + }, cancel); + } + + return false; + } + + public async Task StopLiveview(CancellationToken token) + { + if (isLiveview) + { + await Try(async cancel => await http.Get<BaseRequestResult>("?mode=stopstream", cancel), token); + } + } + + public async Task<bool> SwitchToRec(CancellationToken cancel) + { + return await TryGet("?mode=camcmd&value=recmode", cancel); + } + + private async Task<CurMenu> GetCurMenu(CancellationToken cancel) + { + var curmenuString = await http.GetString("?mode=getinfo&type=curmenu", cancel); + var response = Http.ReadResponse<CurMenuRequestResult>(curmenuString); + + if (response.MenuInfo == null) + { + return null; + } + + try + { + var result = Parser.ParseCurMenu(response.MenuInfo); + return result; + } + catch (AggregateException) + { + LogError("Cannot parse CurMenu", (object)curmenuString); + return null; + } + } + + private async Task<LensInfo> GetLensInfo(CancellationToken cancel) + { + string raw = null; + try + { + raw = await http.GetString("?mode=getinfo&type=lens", cancel); + return Parser.ParseLensInfo(raw); + } + catch (Exception) + { + if (CheckAlreadyConnected(raw)) + { + return null; + } + + Debug.WriteLine("LensInfo: " + raw, "LensInfo"); + throw; + } + } + + private async Task<MenuSet> GetMenuSet(CancellationToken cancel) + { + var allmenuString = await http.GetString("?mode=getinfo&type=allmenu", cancel); + var result = Http.ReadResponse<MenuSetRequestResult>(allmenuString); + + if (result.MenuSet == null) + { + return null; + } + + try + { + if (Parser == null) + { + Parser = CameraParser.TryParseMenuSet(result.MenuSet, language, out var menuset); + return menuset; + } + + return Parser.ParseMenuSet(result.MenuSet, language); + } + catch (AggregateException) + { + LogError("Cannot parse MenuSet", (object)allmenuString); + return null; + } + } + + private async Task<CameraState> GetState(CancellationToken cancel) + { + var response = await http.Get<CameraStateRequestResult>("?mode=getstate", cancel); + var newState = response.State; + if (newState.Rec == OnOff.On) + { + LumixState.RecState = Profile.RecStop ? RecState.Started : RecState.StopNotSupported; + } + else + { + LumixState.RecState = RecState.Stopped; + } + + return newState; + } + + [RunnableAction(MethodGroup.Focus)] + private async Task<bool> NewMfAssistMove(PinchStage stage, FloatPoint p, CancellationToken cancel) + { + ReportAction(stage, p); + if (!autoreviewUnlocked) + { + autoreviewUnlocked = true; + await TryGet("?mode=camcmd&value=autoreviewunlock", cancel); + } + + var val2 = $"{(int)(p.X * 1000)}/{(int)(p.Y * 1000)}"; + if (stage != PinchStage.Single) + { + var res = await TryGetString($"?mode=camctrl&type=touch_trace&value={stage.GetString()}&value2={val2}", cancel); + if (stage == PinchStage.Stop) + { + autoreviewUnlocked = false; + } + + return res; + } + + await TryGetString($"?mode=camctrl&type=touch_trace&value=start&value2={val2}", cancel); + await TryGetString($"?mode=camctrl&type=touch_trace&value=continue&value2={val2}", cancel); + await TryGetString($"?mode=camctrl&type=touch_trace&value=stop&value2={val2}", cancel); + autoreviewUnlocked = false; + return true; + } + + private async Task<bool> OldMfAssistZoom(PinchStage stage, FloatPoint floatPoint, float size, CancellationToken cancel) + { + if (stage == PinchStage.Start) + { + lastOldPinchSize = size; + return true; + } + + if (LumixState.LumixCameraMode != LumixCameraMode.MFAssist) + { + return await MfAssistMove(stage, floatPoint, cancel); + } + + var val = size - lastOldPinchSize > 0 ? 10 : 5; + Debug.WriteLine("Mag val:" + val, "MFAssist"); + return await TryGet($"?mode=setsetting&type=mf_asst_mag&value={val}", cancel); + } + + private async Task<bool> OldNewAction( + Func<CancellationToken, Task<bool>> newAction, + Func<CancellationToken, Task<bool>> oldAction, + bool flag, + Action<bool> flagSet, + CancellationToken cancel) + { + return await Try( + async c => + { + if (flag) + { + try + { + return await newAction(c); + } + catch (LumixException ex) + { + if (ex.Error == LumixError.ErrorParam) + { + LogTrace("New action not supported", "NewAction"); + flagSet(false); + } + else + { + throw; + } + } + } + + return await oldAction(c); + }, cancel); + } + + [RunnableAction(MethodGroup.Focus)] + private async Task<bool> PinchZoom(PinchStage stage, FloatPoint p, float size, CancellationToken cancel) + { + try + { + if (!autoreviewUnlocked) + { + autoreviewUnlocked = true; + await TryGet("?mode=camcmd&value=autoreviewunlock", cancel); + } + + var pp1 = new IntPoint(p - size, 1000f).Clamp(0, 1000); + var pp2 = new IntPoint(p + size, 1000f).Clamp(0, 1000); + + var url = $"?mode=camctrl&type=pinch&value={stage.GetString()}&value2={pp1.X}/{pp1.Y}/{pp2.X}/{pp2.Y}"; + Debug.WriteLine(url, "PinchZoom"); + var resstring = await http.GetString(url, cancel); + if (resstring.StartsWith("<xml>")) + { + return false; + } + + var csv = resstring.Split(','); + if (csv[0] != "ok") + { + return false; + } + + if (stage == PinchStage.Stop) + { + autoreviewUnlocked = false; + } + + return true; + } + catch (LumixException) + { + throw; + } + catch (ConnectionLostException) + { + Debug.WriteLine("Connection lost", "Connection"); + return false; + } + catch (Exception ex) + { + LogError("Camera action failed", ex); + return false; + } + } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/AutoFocusMode.cs b/CameraApi.Panasonic/LumixData/AutoFocusMode.cs new file mode 100644 index 0000000..1ca46cb --- /dev/null +++ b/CameraApi.Panasonic/LumixData/AutoFocusMode.cs @@ -0,0 +1,39 @@ +namespace CameraApi.Panasonic.LumixData +{ + using GMaster.Core.Tools; + + public enum AutoFocusModeFlags + { + None, + TouchAFRelease + } + + public enum LumixAutoFocusMode + { + [EnumValue(AutoFocusModeFlags.None)] + Unknown = -1, + + [EnumValue(AutoFocusModeFlags.None)] + Manual = 0, + + [EnumException(4)] + [EnumValue(AutoFocusModeFlags.TouchAFRelease)] + Face = 3, + + [EnumValue(AutoFocusModeFlags.TouchAFRelease)] + Track = 5, + + [EnumException(6)] + [EnumValue(AutoFocusModeFlags.TouchAFRelease)] + MultiArea = 8, + + [EnumValue(AutoFocusModeFlags.TouchAFRelease)] + FreeMultiArea = 11, + + [EnumValue(AutoFocusModeFlags.None)] + OneArea = 1, + + [EnumValue(AutoFocusModeFlags.None)] + Pinpoint = 7 + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/BaseRequestResult.cs b/CameraApi.Panasonic/LumixData/BaseRequestResult.cs similarity index 84% rename from Core/Camera/LumixData/BaseRequestResult.cs rename to CameraApi.Panasonic/LumixData/BaseRequestResult.cs index 5656f4d..33185bb 100644 --- a/Core/Camera/LumixData/BaseRequestResult.cs +++ b/CameraApi.Panasonic/LumixData/BaseRequestResult.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/CameraApi.Panasonic/LumixData/CameraMode.cs b/CameraApi.Panasonic/LumixData/CameraMode.cs new file mode 100644 index 0000000..eb35245 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/CameraMode.cs @@ -0,0 +1,59 @@ +// ReSharper disable InconsistentNaming + +#pragma warning disable SA1300 // Element must begin with upper-case letter + +namespace CameraApi.Panasonic.LumixData +{ + using System; + using GMaster.Core.Tools; + + [Flags] + public enum LumixCameraModeFlags + { + None = 0, + Shutter = 1 << 0, + Aperture = 1 << 1, + Video = 1 << 2, + Photo = 1 << 3 + } + + public enum LumixCameraMode + { + [EnumValue(LumixCameraModeFlags.Photo)] + iA = 09, + + [EnumValue(LumixCameraModeFlags.Photo)] + P = 01, + + [EnumValue(LumixCameraModeFlags.Aperture | LumixCameraModeFlags.Photo)] + A = 02, + + [EnumValue(LumixCameraModeFlags.Shutter | LumixCameraModeFlags.Photo)] + S = 03, + + [EnumValue(LumixCameraModeFlags.Aperture | LumixCameraModeFlags.Shutter | LumixCameraModeFlags.Photo)] + M = 04, + + VideoRecording = 05, + + [EnumValue(LumixCameraModeFlags.Video)] + vP = 0x3c, + + [EnumValue(LumixCameraModeFlags.Aperture | LumixCameraModeFlags.Video)] + vA = 0x3d, + + [EnumValue(LumixCameraModeFlags.Shutter | LumixCameraModeFlags.Video)] + vS = 0x3e, + + [EnumValue(LumixCameraModeFlags.Aperture | LumixCameraModeFlags.Shutter | LumixCameraModeFlags.Video)] + vM = 0x3f, + + [EnumValue(LumixCameraModeFlags.Aperture | LumixCameraModeFlags.Shutter | LumixCameraModeFlags.Photo)] + Unknown = 0, + + [EnumValue(LumixCameraModeFlags.None)] + MFAssist = 0xff + } +} + +#pragma warning restore SA1300 // Element must begin with upper-case letter \ No newline at end of file diff --git a/Core/Camera/LumixData/CameraState.cs b/CameraApi.Panasonic/LumixData/CameraState.cs similarity index 90% rename from Core/Camera/LumixData/CameraState.cs rename to CameraApi.Panasonic/LumixData/CameraState.cs index 6c3efa8..1e8e7e9 100644 --- a/Core/Camera/LumixData/CameraState.cs +++ b/CameraApi.Panasonic/LumixData/CameraState.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; @@ -32,6 +32,9 @@ public class CameraState [XmlElement(ElementName = "batt")] public string Battery { get; set; } + [XmlElement(ElementName = "batt_grip")] + public string GripBattery { get; set; } + [XmlElement(ElementName = "burst_interval_status")] public string BurstIntervalStatus { get; set; } @@ -60,17 +63,26 @@ public class CameraState public RemainDisplayType RemainDisplayType { get; set; } [XmlElement(ElementName = "sd_access")] - public OnOff SdAccess { get; set; } = OnOff.On; + public OnOff SdAccess { get; set; } [XmlElement(ElementName = "sdcardstatus")] public SdCardStatus SdCardStatus { get; set; } + [XmlElement(ElementName = "sd2_access")] + public OnOff Sd2Access { get; set; } + + [XmlElement(ElementName = "sd2_cardstatus")] + public SdCardStatus Sd2CardStatus { get; set; } + [XmlElement(ElementName = "sdi_state")] public string SdiState { get; set; } [XmlElement(ElementName = "sd_memory")] public SdMemorySet SdMemory { get; set; } + [XmlElement(ElementName = "sd2_memory")] + public SdMemorySet Sd2Memory { get; set; } + [XmlElement(ElementName = "stop_motion")] public OnOff StopMotion { get; set; } @@ -144,10 +156,10 @@ protected bool Equals(CameraState other) string.Equals(Cammode, other.Cammode) && RemainCapacity == other.RemainCapacity && Equals(SdCardStatus, other.SdCardStatus) && - string.Equals(SdMemory, other.SdMemory) && + Equals(SdMemory, other.SdMemory) && VideoRemainCapacity == other.VideoRemainCapacity && Rec == other.Rec && string.Equals(BurstIntervalStatus, other.BurstIntervalStatus) && - string.Equals(SdAccess, other.SdAccess) && + Equals(SdAccess, other.SdAccess) && Equals(RemainDisplayType, other.RemainDisplayType) && ProgressTime == other.ProgressTime && string.Equals(Operate, other.Operate) && diff --git a/Core/Camera/LumixData/CameraStateRequestResult.cs b/CameraApi.Panasonic/LumixData/CameraStateRequestResult.cs similarity index 85% rename from Core/Camera/LumixData/CameraStateRequestResult.cs rename to CameraApi.Panasonic/LumixData/CameraStateRequestResult.cs index a02bf30..08a635e 100644 --- a/Core/Camera/LumixData/CameraStateRequestResult.cs +++ b/CameraApi.Panasonic/LumixData/CameraStateRequestResult.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/CameraApi.Panasonic/LumixData/ChangeDirection.cs b/CameraApi.Panasonic/LumixData/ChangeDirection.cs new file mode 100644 index 0000000..fe42521 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/ChangeDirection.cs @@ -0,0 +1,22 @@ +namespace CameraApi.Panasonic.LumixData +{ + using GMaster.Core.Tools; + + public enum LumixChangeDirection + { + [EnumValue("wide-fast")] + WideFast, + + [EnumValue("wide-normal")] + WideNormal, + + [EnumValue("zoomstop")] + ZoomStop, + + [EnumValue("tele-fast")] + TeleFast, + + [EnumValue("tele-normal")] + TeleNormal + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/CurMenuItem.cs b/CameraApi.Panasonic/LumixData/CurMenuItem.cs new file mode 100644 index 0000000..f7742a2 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/CurMenuItem.cs @@ -0,0 +1,24 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + [XmlRoot(ElementName = "item")] + public class CurMenuItem : IStringIdItem + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + [XmlAttribute(AttributeName = "enabled")] + public YesNo Enable { get; set; } + + [XmlAttribute(AttributeName = "value")] + public string Value { get; set; } + + [XmlAttribute(AttributeName = "option")] + public string Option { get; set; } + + [XmlAttribute(AttributeName = "option2")] + public string Option2 { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/CurMenuRequestResult.cs b/CameraApi.Panasonic/LumixData/CurMenuRequestResult.cs similarity index 84% rename from Core/Camera/LumixData/CurMenuRequestResult.cs rename to CameraApi.Panasonic/LumixData/CurMenuRequestResult.cs index 7145a56..5b8f1c8 100644 --- a/Core/Camera/LumixData/CurMenuRequestResult.cs +++ b/CameraApi.Panasonic/LumixData/CurMenuRequestResult.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/CameraApi.Panasonic/LumixData/FocusMode.cs b/CameraApi.Panasonic/LumixData/FocusMode.cs new file mode 100644 index 0000000..7d70625 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/FocusMode.cs @@ -0,0 +1,26 @@ +// ReSharper disable InconsistentNaming + +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using CameraApi.Core; + using GMaster.Core.Tools; + + public enum LumixFocusMode + { + [EnumException(0x5)] + [XmlEnum(Name = "mf")] + MF = 0xff, + + [XmlEnum(Name = "afc")] + AFC = 0x3, + + [XmlEnum(Name = "aff")] + AFF = 0x2, + + [XmlEnum(Name = "afs")] + AFS = 0x1, + + Unknown = 0 + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/FocusModeRequestResult.cs b/CameraApi.Panasonic/LumixData/FocusModeRequestResult.cs new file mode 100644 index 0000000..5656dd3 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/FocusModeRequestResult.cs @@ -0,0 +1,20 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + + [XmlRoot(ElementName = "camrply")] + public class FocusModeRequestResult : BaseRequestResult + { + [XmlElement(ElementName = "state")] + public CameraState State { get; set; } + + [XmlElement("settingvalue")] + public FocusModeElement Value { get; set; } + + public class FocusModeElement + { + [XmlAttribute("focusmode")] + public LumixFocusMode FocusMode { get; set; } + } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/FocusPosition.cs b/CameraApi.Panasonic/LumixData/FocusPosition.cs similarity index 74% rename from Core/Camera/LumixData/FocusPosition.cs rename to CameraApi.Panasonic/LumixData/FocusPosition.cs index 0cc21d2..9c251f6 100644 --- a/Core/Camera/LumixData/FocusPosition.cs +++ b/CameraApi.Panasonic/LumixData/FocusPosition.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { public class FocusPosition { diff --git a/CameraApi.Panasonic/LumixData/Item.cs b/CameraApi.Panasonic/LumixData/Item.cs new file mode 100644 index 0000000..9b39a86 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/Item.cs @@ -0,0 +1,79 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System; + using System.Xml.Serialization; + using GMaster.Core.Tools; + + [XmlRoot(ElementName = "item")] + public class Item : IStringIdItem + { + [XmlAttribute(AttributeName = "cmd_mode")] + public string CmdMode { get; set; } + + [XmlAttribute(AttributeName = "cmd_type")] + public string CmdType { get; set; } + + [XmlAttribute(AttributeName = "cmd_value")] + public string CmdValue { get; set; } + + [XmlAttribute(AttributeName = "cmd_value2")] + public string CmdValue2 { get; set; } + + [XmlAttribute(AttributeName = "func_type")] + public string FuncType { get; set; } + + [XmlArray("group")] + [XmlArrayItem("item")] + public HashCollection<Item> GroupItems + { + get + { + return Items; + } + + set + { + if (value != null && value.Count > 0) + { + if (Items != null && Items.Count > 0) + { + throw new Exception("Items is aready assigned"); + } + + Items = value; + } + } + } + + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + public HashCollection<Item> Items { get; set; } + + [XmlArray("menu")] + [XmlArrayItem("item")] + public HashCollection<Item> MenuItems + { + get + { + return Items; + } + + set + { + if (value != null && value.Count > 0) + { + if (Items != null && Items.Count > 0) + { + throw new Exception("Items is aready assigned"); + } + + Items = value; + } + } + } + + [XmlAttribute(AttributeName = "title_id")] + public string TitleId { get; set; } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/Language.cs b/CameraApi.Panasonic/LumixData/Language.cs new file mode 100644 index 0000000..cc1ba7e --- /dev/null +++ b/CameraApi.Panasonic/LumixData/Language.cs @@ -0,0 +1,18 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + [XmlRoot(ElementName = "language")] + public class Language : IStringIdItem + { + [XmlAttribute(AttributeName = "default")] + public YesNo Default { get; set; } + + [XmlAttribute(AttributeName = "code")] + public string Id { get; set; } + + [XmlElement(ElementName = "title")] + public HashCollection<Title> Titles { get; set; } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/MenuHolder.cs b/CameraApi.Panasonic/LumixData/MenuHolder.cs new file mode 100644 index 0000000..6e271c2 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/MenuHolder.cs @@ -0,0 +1,12 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + public class MenuHolder + { + [XmlArray("menu")] + [XmlArrayItem("item")] + public HashCollection<Item> Items { get; set; } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/MenuInfo.cs b/CameraApi.Panasonic/LumixData/MenuInfo.cs new file mode 100644 index 0000000..ac65cde --- /dev/null +++ b/CameraApi.Panasonic/LumixData/MenuInfo.cs @@ -0,0 +1,24 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + public class MenuInfo + { + [XmlArray("mainmenu")] + [XmlArrayItem("item")] + public HashCollection<CurMenuItem> MainMenu { get; set; } + + [XmlArray("photosettings")] + [XmlArrayItem("item")] + public HashCollection<CurMenuItem> Photosettings { get; set; } + + [XmlArray("qmenu2")] + [XmlArrayItem("item")] + public HashCollection<CurMenuItem> Qmenu2 { get; set; } + + [XmlArray("qmenu")] + [XmlArrayItem("item")] + public HashCollection<CurMenuItem> Qmenu { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/MenuSetRequestResult.cs b/CameraApi.Panasonic/LumixData/MenuSetRequestResult.cs similarity index 84% rename from Core/Camera/LumixData/MenuSetRequestResult.cs rename to CameraApi.Panasonic/LumixData/MenuSetRequestResult.cs index e4d7714..0ae8cf3 100644 --- a/Core/Camera/LumixData/MenuSetRequestResult.cs +++ b/CameraApi.Panasonic/LumixData/MenuSetRequestResult.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/CameraApi.Panasonic/LumixData/OnOff.cs b/CameraApi.Panasonic/LumixData/OnOff.cs new file mode 100644 index 0000000..95ab68a --- /dev/null +++ b/CameraApi.Panasonic/LumixData/OnOff.cs @@ -0,0 +1,16 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + public enum OnOff + { + [EnumValue("on")] + [XmlEnum(Name = "on")] + On, + + [EnumValue("off")] + [XmlEnum(Name = "off")] + Off + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/RawMenuSet.cs b/CameraApi.Panasonic/LumixData/RawMenuSet.cs similarity index 95% rename from Core/Camera/LumixData/RawMenuSet.cs rename to CameraApi.Panasonic/LumixData/RawMenuSet.cs index cc99a1e..3610a10 100644 --- a/Core/Camera/LumixData/RawMenuSet.cs +++ b/CameraApi.Panasonic/LumixData/RawMenuSet.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/Core/Camera/LumixData/SdMemorySet.cs b/CameraApi.Panasonic/LumixData/SdMemorySet.cs similarity index 66% rename from Core/Camera/LumixData/SdMemorySet.cs rename to CameraApi.Panasonic/LumixData/SdMemorySet.cs index 3e096da..489d6c6 100644 --- a/Core/Camera/LumixData/SdMemorySet.cs +++ b/CameraApi.Panasonic/LumixData/SdMemorySet.cs @@ -1,13 +1,13 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; public enum SdMemorySet { [XmlEnum(Name = "set")] - Set, + Set = 1, [XmlEnum(Name = "unset")] - Unset + Unset = 0 } } \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/Title.cs b/CameraApi.Panasonic/LumixData/Title.cs new file mode 100644 index 0000000..2fa7351 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/Title.cs @@ -0,0 +1,15 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + [XmlRoot(ElementName = "title")] + public class Title : IStringIdItem + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + [XmlText] + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/LumixData/TitleList.cs b/CameraApi.Panasonic/LumixData/TitleList.cs new file mode 100644 index 0000000..d571804 --- /dev/null +++ b/CameraApi.Panasonic/LumixData/TitleList.cs @@ -0,0 +1,21 @@ +namespace CameraApi.Panasonic.LumixData +{ + using System.Xml.Serialization; + using GMaster.Core.Tools; + + [XmlRoot(ElementName = "titlelist")] + public class TitleList + { + [XmlAttribute(AttributeName = "date")] + public string Date { get; set; } + + [XmlElement(ElementName = "language")] + public HashCollection<Language> Languages { get; set; } + + [XmlAttribute(AttributeName = "model")] + public string Model { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/YesNo.cs b/CameraApi.Panasonic/LumixData/YesNo.cs similarity index 79% rename from Core/Camera/LumixData/YesNo.cs rename to CameraApi.Panasonic/LumixData/YesNo.cs index e6eac74..a24ad5f 100644 --- a/Core/Camera/LumixData/YesNo.cs +++ b/CameraApi.Panasonic/LumixData/YesNo.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace CameraApi.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/CameraApi.Panasonic/LumixError.cs b/CameraApi.Panasonic/LumixError.cs new file mode 100644 index 0000000..5395b63 --- /dev/null +++ b/CameraApi.Panasonic/LumixError.cs @@ -0,0 +1,12 @@ +namespace CameraApi.Panasonic +{ + using GMaster.Core.Tools; + + public enum LumixError + { + [EnumValue("err_param")] + ErrorParam, + + Unknown + } +} \ No newline at end of file diff --git a/Core/Camera/LumixException.cs b/CameraApi.Panasonic/LumixException.cs similarity index 93% rename from Core/Camera/LumixException.cs rename to CameraApi.Panasonic/LumixException.cs index adada4a..6bb6522 100644 --- a/Core/Camera/LumixException.cs +++ b/CameraApi.Panasonic/LumixException.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { using System; diff --git a/CameraApi.Panasonic/LumixState.cs b/CameraApi.Panasonic/LumixState.cs new file mode 100644 index 0000000..603e083 --- /dev/null +++ b/CameraApi.Panasonic/LumixState.cs @@ -0,0 +1,382 @@ +namespace CameraApi.Panasonic +{ + using System.Collections.Generic; + using System.ComponentModel; + using System.Runtime.CompilerServices; + using CameraApi.Core; + using CameraApi.Panasonic.LumixData; + using GMaster.Core.Tools; + using JetBrains.Annotations; + + public class LumixState : ICameraState + { + private TextBinValue aperture; + private LumixAutoFocusMode lumixAutoFocusMode; + private LumixCameraMode lumixCameraMode = LumixCameraMode.Unknown; + private CurMenu curMenu; + private int currentFocus; + private int exposureShift; + private LumixFocusMode focusMode; + private FocusAreas focusPoints; + private bool isBusy = true; + private TextBinValue iso; + private LensInfo lensInfo; + private int maximumFocus; + private MenuSet menuSet; + private CameraOrientation orientation; + private RecState recState; + private TextBinValue shutter; + private CameraState state; + private int zoom; + + public event PropertyChangedEventHandler PropertyChanged; + + public string Aperture + { + get => aperture.Text; + } + + public TextBinValue LumixAperture + { + get => aperture; + set + { + if (value.Equals(aperture)) + { + return; + } + + aperture = value; + OnPropertyChanged(); + } + } + + private readonly Dictionary<LumixAutoFocusMode, AutoFocusMode> ToAutoFocusMode = new Dictionary<LumixAutoFocusMode, AutoFocusMode> + { + { LumixAutoFocusMode.Face, AutoFocusMode.Face} + }; + + public AutoFocusMode AutoFocusMode + { + get => ToAutoFocusMode[lumixAutoFocusMode]; + } + + public LumixAutoFocusMode LumixAutoFocusMode + { + get => lumixAutoFocusMode; + set + { + if (value == lumixAutoFocusMode) + { + return; + } + + lumixAutoFocusMode = value; + OnPropertyChanged(); + } + } + + private readonly Dictionary<LumixCameraMode, CameraMode> ToCameraMode = new Dictionary<LumixCameraMode, CameraMode> + { + { LumixCameraMode.A, CameraMode.A}, + { LumixCameraMode.S, CameraMode.S}, + { LumixCameraMode.M, CameraMode.M}, + { LumixCameraMode.iA, CameraMode.iA}, + { LumixCameraMode.vA, CameraMode.vA}, + { LumixCameraMode.vS, CameraMode.vS}, + { LumixCameraMode.vM, CameraMode.vM}, + }; + + public CameraMode CameraMode + { + get => ToCameraMode[lumixCameraMode]; + } + + public LumixCameraMode LumixCameraMode + { + get => lumixCameraMode; + set + { + if (value == lumixCameraMode) + { + return; + } + + lumixCameraMode = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(CanChangeAperture)); + OnPropertyChanged(nameof(CanChangeShutter)); + OnPropertyChanged(nameof(CanCapture)); + OnPropertyChanged(nameof(IsVideoMode)); + } + } + + public bool CanCapture => CameraMode.ToValue<LumixCameraModeFlags>().HasFlag(LumixCameraModeFlags.Photo); + + public bool CanChangeAperture => CameraMode.ToValue<LumixCameraModeFlags>().HasFlag(LumixCameraModeFlags.Aperture); + + public bool CanChangeShutter => CameraMode.ToValue<LumixCameraModeFlags>().HasFlag(LumixCameraModeFlags.Shutter); + + public bool CanManualFocus => FocusMode == FocusMode.MF; + + public CurMenu CurMenu + { + get => curMenu; + set + { + curMenu = value; + OnPropertyChanged(); + } + } + + public int CurrentFocus + { + get => currentFocus; + set + { + if (value == currentFocus) + { + return; + } + + currentFocus = value; + OnPropertyChanged(); + } + } + + public int ExposureShift + { + get => exposureShift; + set + { + if (value == exposureShift) + { + return; + } + + exposureShift = value; + OnPropertyChanged(); + } + } + + public FocusAreas FocusAreas + { + get => focusPoints; + set + { + if (Equals(value, focusPoints)) + { + return; + } + + focusPoints = value; + OnPropertyChanged(); + } + } + + private readonly Dictionary<LumixFocusMode, FocusMode> ToFocusMode = new Dictionary<LumixFocusMode, FocusMode> + { + {LumixFocusMode.AFC, FocusMode.AFC } + }; + + public FocusMode FocusMode + { + get => ToFocusMode[focusMode]; + + } + + public LumixFocusMode LumixFocusMode + { + get => focusMode; + set + { + if (value == focusMode) + { + return; + } + + focusMode = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(CanManualFocus)); + } + } + + public bool IsBusy + { + get => isBusy; + set + { + if (value == isBusy) + { + return; + } + + isBusy = value; + OnPropertyChanged(); + } + } + + public bool IsLimited { get; set; } + + public TextBinValue Iso + { + get => iso; + set + { + if (value.Equals(iso)) + { + return; + } + + iso = value; + OnPropertyChanged(); + } + } + + public bool IsVideoMode => CameraMode.ToValue<LumixCameraModeFlags>().HasFlag(LumixCameraModeFlags.Video); + + public LensInfo LensInfo + { + get => lensInfo; + set + { + lensInfo = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(OpenedAperture)); + } + } + + public int MaximumFocus + { + get => maximumFocus; + set + { + if (value == maximumFocus) + { + return; + } + + maximumFocus = value; + OnPropertyChanged(); + } + } + + public MenuSet MenuSet + { + get => menuSet; + set + { + menuSet = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(Aperture)); + OnPropertyChanged(nameof(Shutter)); + OnPropertyChanged(nameof(Iso)); + } + } + + public CameraMenuItem256 OpenedAperture + { + get + { + var open = LensInfo.OpenedAperture; + var opentext = CameraParser.ApertureBinToText(open); + return new CameraMenuItem256(open.ToString(), opentext, "setsetting", "focal", open); + } + } + + public CameraOrientation Orientation + { + get => orientation; + set + { + if (value == orientation) + { + return; + } + + orientation = value; + OnPropertyChanged(); + } + } + + public RecState RecState + { + get => recState; + set + { + if (value == recState) + { + return; + } + + recState = value; + Debug.WriteLine("Ser RecState: " + value, "RecState"); + OnPropertyChanged(); + } + } + + public TextBinValue LumixShutter + { + get => shutter; + set + { + if (shutter.Text == value.Text) + { + return; + } + + shutter = value; + OnPropertyChanged(); + } + } + + public CameraState State + { + get => state; + set + { + if (Equals(value, state)) + { + return; + } + + state = value; + OnPropertyChanged(); + } + } + + public int Zoom + { + get => zoom; + set + { + if (value == zoom) + { + return; + } + + zoom = value; + OnPropertyChanged(); + } + } + + public void Reset() + { + LumixAperture = default(TextBinValue); + LumixShutter = default(TextBinValue); + Iso = default(TextBinValue); + LumixCameraMode = LumixCameraMode.Unknown; + FocusAreas = null; + LumixFocusMode = LumixFocusMode.Unknown; + Zoom = 0; + RecState = RecState.Unknown; + Orientation = CameraOrientation.Undefined; + } + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/MFAssistMoveStage.cs b/CameraApi.Panasonic/MFAssistMoveStage.cs new file mode 100644 index 0000000..f9fb09c --- /dev/null +++ b/CameraApi.Panasonic/MFAssistMoveStage.cs @@ -0,0 +1,18 @@ +namespace CameraApi.Panasonic +{ + using GMaster.Core.Tools; + + public enum PinchStage + { + [EnumValue("start")] + Start = 0, + + [EnumValue("stop")] + Stop = 1, + + [EnumValue("continue")] + Continue = 2, + + Single = 3 + } +} \ No newline at end of file diff --git a/Core/Camera/MenuSet.cs b/CameraApi.Panasonic/MenuSet.cs similarity index 98% rename from Core/Camera/MenuSet.cs rename to CameraApi.Panasonic/MenuSet.cs index 95ec29e..67dff84 100644 --- a/Core/Camera/MenuSet.cs +++ b/CameraApi.Panasonic/MenuSet.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { public class MenuSet { diff --git a/CameraApi.Panasonic/OffFrameProcessor.cs b/CameraApi.Panasonic/OffFrameProcessor.cs new file mode 100644 index 0000000..36ddd7b --- /dev/null +++ b/CameraApi.Panasonic/OffFrameProcessor.cs @@ -0,0 +1,202 @@ +namespace CameraApi.Panasonic +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using CameraApi.Panasonic.LumixData; + using GMaster.Core.Tools; + + public class OffFrameProcessor + { + private readonly string deviceName; + + private readonly LumixState lumixState; + private readonly CameraParser parser; + + private Slice lastPrint; + + public OffFrameProcessor(string deviceName, CameraParser parser, LumixState lumixState) + { + this.parser = parser; + this.lumixState = lumixState; + this.deviceName = deviceName; + } + + public event Func<CancellationToken, Task> LensChanged; + + public bool OffFrameBytesSupported { get; } = true; + + public int CalcImageStart(Slice slice) + { + return slice.ToShort(30) + 32; + } + + public void Process(Slice slice, IntPoint size) + { + try + { + var state = new ProcessState(slice, GetMultiplier(slice)); + + if (!OffFrameBytesSupported || slice.Length < 130) + { + return; + } + + PrintBytes(slice); + lumixState.Iso = GetFromShort(state.Main, 127, parser.IsoBinary); + + lumixState.Shutter = GetFromShort(state.Main, 68, parser.ShutterBinary); + + lumixState.Aperture = GetFromShort(state.Main, 56, parser.ApertureBinary); + + var newmode = state.Main[92].ToEnum(LumixCameraMode.Unknown); + if (newmode != LumixCameraMode.VideoRecording) + { + lumixState.CameraMode = newmode; + } + + lumixState.Orientation = state.Original[42].ToEnum(CameraOrientation.Undefined); + + var newzoom = state.Main.ToShort(85); + if (lumixState.Zoom == 0 && newzoom != 0) + { + LensChanged?.Invoke(); + } + + lumixState.Zoom = newzoom; + + lumixState.ExposureShift = state.Main.ToShort(128); + + lumixState.FocusAreas = GetFocusPoint(state.Original, size); + + lumixState.FocusMode = state.Main[107].ToEnum(LumixFocusMode.Unknown); + + lumixState.AutoFocusMode = state.Main[109].ToEnum(LumixAutoFocusMode.Unknown); + } + catch (Exception e) + { + Log.Error(new Exception("Cannot parse off-frame bytes for camera: " + deviceName, e)); + } + } + + private static string FindClosest(IReadOnlyDictionary<int, string> dict, int value) + { + var list = dict.Keys.ToList(); + list.Sort(); + var index = list.BinarySearch(value); + if (index > 0) + { + return dict[list[index]]; + } + + index = ~index; + if (index >= list.Count) + { + return dict[list[list.Count - 1]]; + } + + if (index <= 0) + { + return dict[list[0]]; + } + + var val1 = list[index - 1]; + var val2 = list[index]; + return val2 - value > value - val1 ? dict[val1] : dict[val2]; + } + + private static FocusAreas GetFocusPoint(Slice slice, IntPoint size) + { + var pointsNum = slice[47]; + var focusSlice = new Slice(slice, 48); + var multiplier = GetMultiplier(slice); + if (pointsNum > 0) + { + var result = new FocusAreas(pointsNum, size, slice[46] == 0xff); + + for (var i = 0; i < pointsNum; i++) + { + var x1 = focusSlice.ToShort(0 + (i * multiplier)); + var y1 = focusSlice.ToShort(2 + (i * multiplier)); + var x2 = focusSlice.ToShort(4 + (i * multiplier)); + var y2 = focusSlice.ToShort(6 + (i * multiplier)); + var typeval = (int)focusSlice.ToUShort(10 + (i * multiplier)); + var type = Enum.IsDefined(typeof(FocusAreaType), typeval) ? (FocusAreaType)typeval : FocusAreaType.FaceOther; + var failed = focusSlice[9 + (i * multiplier)] == 0; + result.AddBox(x1, y1, x2, y2, type, failed); + } + + return result; + } + + return null; + } + + private static int GetMultiplier(Slice slice) + { + return slice[46] == 0xff ? 12 : 16; + } + + private TextBinValue GetFromShort(Slice slice, int index, IReadOnlyDictionary<int, string> dict) + { + var bin = slice.ToShort(index); + try + { + if (dict.TryGetValue(bin, out var val)) + { + return new TextBinValue(val, bin); + } + + val = FindClosest(dict, bin); + return new TextBinValue(val, bin); + } + catch (KeyNotFoundException e) + { + Log.Error(new Exception("Cannot parse off-frame bytes for camera: " + deviceName, e)); + return new TextBinValue("!", bin); + } + } + + private void PrintBytes(Slice slice) + { + Debug.WriteLine( + () => + { + if (lastPrint == null || slice.Length != lastPrint.Length) + { + lastPrint = slice; + return string.Join(",", slice.Skip(32).Select(a => a.ToString("X2"))); + } + + var str = new StringBuilder(); + for (int i = 0; i < slice.Length; i++) + { + str.Append(slice[i].ToString("X2")); + if (i < slice.Length - 1) + { + str.Append(slice[i] != lastPrint[i] || slice[i + 1] != lastPrint[i + 1] ? '#' : ','); + } + } + lastPrint = slice; + return str.ToString(); + }, "OffFrameBytes"); + } + + private class ProcessState + { + public ProcessState(Slice array, int multiplier) + { + Original = array; + + Main = new Slice(array, Original[47] * multiplier); + } + + public Slice Main { get; } + + public Slice Original { get; } + } + } +} \ No newline at end of file diff --git a/Core/Camera/Slice.cs b/CameraApi.Panasonic/Slice.cs similarity index 98% rename from Core/Camera/Slice.cs rename to CameraApi.Panasonic/Slice.cs index a57a516..75e842f 100644 --- a/Core/Camera/Slice.cs +++ b/CameraApi.Panasonic/Slice.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { using System; using System.Collections; diff --git a/CameraApi.Panasonic/SsdpManager.cs b/CameraApi.Panasonic/SsdpManager.cs new file mode 100644 index 0000000..8633b73 --- /dev/null +++ b/CameraApi.Panasonic/SsdpManager.cs @@ -0,0 +1,259 @@ +namespace CameraApi.Panasonic +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using CameraApi.Core; + using GMaster.Core.Network; + using GMaster.Core.Tools; + using Rssdp; + using Rssdp.Infrastructure; + + public class SsdpManager + { + private const int LiveViewPort = 49152; + + private readonly HashSet<string> foundDevices = new HashSet<string>(); + private readonly string lang; + private readonly INetwork network; + private readonly ConcurrentDictionary<string, IEthernetCamera> ipToLumix = new ConcurrentDictionary<string, IEthernetCamera>(); + private readonly ConcurrentDictionary<string, IEthernetCamera> usnToLumix = new ConcurrentDictionary<string, IEthernetCamera>(); + private List<SsdpDeviceLocator> deviceLocators; + private List<IDatagramSocket> liveviewUdpSockets; + + public SsdpManager(string lang, INetwork network) + { + this.lang = lang; + this.network = network; + network.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged; + } + + public event Action<DeviceInfo, IEthernetCamera> DeviceDiscovered2; + + public async Task<bool> ConnectCamera(IEthernetCamera camera, CancellationToken cancel) + { + try + { + await camera.Connect(cancel); + ipToLumix[camera.CameraHost] = camera; + usnToLumix[camera.Usn] = camera; + Debug.WriteLine("Add listener: " + camera.CameraHost, "UDP"); + + return true; + } + catch (Exception ex) + { + Log.Error(new Exception("Connection failed", ex)); + return false; + } + } + + public void SearchCameras() + { + try + { + if (deviceLocators != null) + { + foreach (var dev in deviceLocators) + { + if (!dev.IsSearching) + { + var task = Task.Run(async () => + { + try + { + await dev.SearchAsync(); + } + catch (ObjectDisposedException) + { + // Ignore due RSSDP lacks Cancellation + } + catch (Exception ex) + { + Log.Error(ex); + } + }); + } + } + } + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + public async Task StartListening() + { + liveviewUdpSockets = new List<IDatagramSocket>(); + var confirmedHosts = new List<string>(); + foreach (var profile in network.GetHostNames()) + { + var liveviewUdp = network.CreateDatagramSocket(); + try + { + liveviewUdp.MessageReceived += LiveviewUdp_MessageReceived; + + await liveviewUdp.Bind(profile, LiveViewPort); + liveviewUdpSockets.Add(liveviewUdp); + confirmedHosts.Add(profile); + } + catch (Exception) + { + liveviewUdp.Dispose(); + } + } + + lock (foundDevices) + { + foundDevices.Clear(); + } + + deviceLocators = new List<SsdpDeviceLocator>(); + + foreach (var host in confirmedHosts) + { + var deviceLocator = + new SsdpDeviceLocator(new SsdpCommunicationsServer(new SocketFactory(host))) + { + NotificationFilter = "urn:schemas-upnp-org:device:MediaServer:1" + }; + deviceLocator.DeviceAvailable += DeviceLocator_DeviceAvailable; + deviceLocator.StartListeningForNotifications(); + deviceLocators.Add(deviceLocator); + } + } + + public void StopListening() + { + if (liveviewUdpSockets != null) + { + foreach (var liveviewUdpSocket in liveviewUdpSockets) + { + liveviewUdpSocket.Dispose(); + } + } + + liveviewUdpSockets = null; + + if (deviceLocators != null) + { + foreach (var deviceLocator in deviceLocators) + { + deviceLocator.StopListeningForNotifications(); + deviceLocator.Dispose(); + } + } + + deviceLocators = null; + } + + public void ForgetDiscovery(DeviceInfo dev) + { + var usn = dev.Usn; + var host = dev.Host; + lock (foundDevices) + { + foundDevices.Remove(usn + host); + } + } + + public void ForgetCamera(Lumix obj) + { + var usn = obj.Device.Usn; + var host = obj.Device.Host; + ipToLumix.TryRemove(host, out _); + usnToLumix.TryRemove(usn, out _); + } + + private async void DeviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs arg) + { + try + { + var usn = arg.DiscoveredDevice.Usn; + var host = arg.DiscoveredDevice.DescriptionLocation.Host; + lock (foundDevices) + { + if (foundDevices.Contains(usn + host)) + { + Debug.WriteLine("Discovered but already found: " + usn, "Discovery"); + return; + } + + Debug.WriteLine("Discovered new: " + usn, "Discovery"); + + foundDevices.Add(usn + host); + } + + if (!arg.DiscoveredDevice.ResponseHeaders.TryGetValues("SERVER", out var values) || !values.Any(s => s.Contains("Panasonic"))) + { + return; + } + + var info = await arg.DiscoveredDevice.GetDeviceInfo() as SsdpRootDevice; + if (info == null) + { + return; + } + + if (info.ModelName != "LUMIX") + { + return; + } + + var dev = new DeviceInfo(info, usn); + Log.Trace("Discovered " + dev.ModelName, tags: "camera." + dev.ModelName); + + if (usnToLumix.TryGetValue(usn, out var oldcamera)) + { + DeviceDiscovered2?.Invoke(dev, oldcamera); + } + else + { + DeviceDiscovered2?.Invoke(dev, null); + } + } + catch (HttpRequestException e) + { + // var status = WebSocketError.GetStatus(ex.GetBaseException().HResult); + Debug.WriteLine(e); + + // Ignore because GetDeviceInfo has problems + } + catch (Exception e) + { + Log.Error(e); + } + } + + private void LiveviewUdp_MessageReceived(DatagramSocketMessage args) + { + try + { + if (!ipToLumix.TryGetValue(args.RemoteAddress, out var camera)) + { + return; + } + + if (camera is IUdpCamera udpCamera) + { + Task.Run(() => udpCamera.ProcessMessage(args.Data)); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private async void NetworkInformation_NetworkStatusChanged() + { + StopListening(); + await StartListening(); + } + } +} \ No newline at end of file diff --git a/Core/Camera/TextBinValue.cs b/CameraApi.Panasonic/TextBinValue.cs similarity index 97% rename from Core/Camera/TextBinValue.cs rename to CameraApi.Panasonic/TextBinValue.cs index f066046..54ca6eb 100644 --- a/Core/Camera/TextBinValue.cs +++ b/CameraApi.Panasonic/TextBinValue.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace CameraApi.Panasonic { public struct TextBinValue { diff --git a/CameraApi.Panasonic/TitledList.cs b/CameraApi.Panasonic/TitledList.cs new file mode 100644 index 0000000..31910e1 --- /dev/null +++ b/CameraApi.Panasonic/TitledList.cs @@ -0,0 +1,21 @@ +namespace CameraApi.Panasonic +{ + using System.Collections.Generic; + using GMaster.Core.Tools; + + public class TitledList<TItem> : HashCollection<TItem> + where TItem : ICameraMenuItem + { + public TitledList() + { + } + + public TitledList(IEnumerable<TItem> collection, string title) + : base(collection) + { + Title = title; + } + + public string Title { get; } + } +} \ No newline at end of file diff --git a/CameraApi.Panasonic/TitledListExtensions.cs b/CameraApi.Panasonic/TitledListExtensions.cs new file mode 100644 index 0000000..f1a2993 --- /dev/null +++ b/CameraApi.Panasonic/TitledListExtensions.cs @@ -0,0 +1,14 @@ +namespace CameraApi.Panasonic +{ + using System.Collections.Generic; + using GMaster.Core.Tools; + + public static class TitledListExtensions + { + public static TitledList<TItem> ToTitledList<TItem>(this IEnumerable<TItem> items, string title) + where TItem : ICameraMenuItem + { + return new TitledList<TItem>(items, title); + } + } +} \ No newline at end of file diff --git a/Core.Panasonic/CameraMenuItem256.cs b/Core.Panasonic/CameraMenuItem256.cs new file mode 100644 index 0000000..11c5f4f --- /dev/null +++ b/Core.Panasonic/CameraMenuItem256.cs @@ -0,0 +1,61 @@ +namespace GMaster.Core.Camera.Panasonic +{ + public class CameraMenuItem256 : ICameraMenuItem + { + public CameraMenuItem256(string id, string text, string command, string commandtype, int value) + { + Id = id; + Text = text; + Command = command; + CommandType = commandtype; + IntValue = value; + } + + public string Command { get; } + + public string CommandType { get; } + + public string Id { get; } + + public int IntValue { get; } + + public string Text { get; } + + public string Value => IntValue + "/256"; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ICameraMenuItem)obj); + } + + public override int GetHashCode() + { + return Text?.GetHashCode() ?? 0; + } + + public override string ToString() + { + return Text; + } + + protected bool Equals(ICameraMenuItem other) + { + return string.Equals(Text, other.Id); + } + } +} \ No newline at end of file diff --git a/Core/Camera/CameraMenuItemText.cs b/Core.Panasonic/CameraMenuItemText.cs similarity index 97% rename from Core/Camera/CameraMenuItemText.cs rename to Core.Panasonic/CameraMenuItemText.cs index ed7d9e3..ce41db5 100644 --- a/Core/Camera/CameraMenuItemText.cs +++ b/Core.Panasonic/CameraMenuItemText.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using LumixData; diff --git a/Core.Panasonic/CameraOrientation.cs b/Core.Panasonic/CameraOrientation.cs new file mode 100644 index 0000000..43f775b --- /dev/null +++ b/Core.Panasonic/CameraOrientation.cs @@ -0,0 +1,11 @@ +namespace GMaster.Core.Camera.Panasonic +{ + public enum CameraOrientation + { + Normal = 1, + UpsideDown = 3, + LeftUp = 6, + RightUp = 8, + Undefined = 0, + } +} \ No newline at end of file diff --git a/Core/Camera/CameraParser.cs b/Core.Panasonic/CameraParser.cs similarity index 99% rename from Core/Camera/CameraParser.cs rename to Core.Panasonic/CameraParser.cs index dce72e0..11e35aa 100644 --- a/Core/Camera/CameraParser.cs +++ b/Core.Panasonic/CameraParser.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System; using System.Collections.Generic; diff --git a/Core.Panasonic/CameraProfile.cs b/Core.Panasonic/CameraProfile.cs new file mode 100644 index 0000000..a52daf9 --- /dev/null +++ b/Core.Panasonic/CameraProfile.cs @@ -0,0 +1,94 @@ +namespace GMaster.Core.Camera.Panasonic +{ + using System.Collections.Generic; + + public class CameraProfile + { + private static readonly CameraParser GH3Parser = new GH3Parser(); + private static readonly CameraParser GH4Parser = new GH4Parser(); + + public static Dictionary<string, CameraProfile> Profiles { get; } = new Dictionary<string, CameraProfile> + { + { + "DMC-GH3", new CameraProfile + { + RecStop = false, + NewTouch = false, + RequestConnection = false, + SetDeviceName = false, + Parser = GH3Parser, + ManualFocusAF = false + } + }, + { + "DMC-GH4", new CameraProfile + { + RequestConnection = false, + SetDeviceName = false, + Parser = GH4Parser + } + }, + { + "DMC-GX7", new CameraProfile + { + SetDeviceName = false + } + }, + { + "DMC-GX80", new CameraProfile + { + SetDeviceName = false + } + }, + { + "DMC-GX85", new CameraProfile + { + SetDeviceName = false + } + }, + { + "DMC-G80", new CameraProfile + { + SetDeviceName = false + } + }, + { + "DMC-G7", new CameraProfile + { + SetDeviceName = false + } + }, + { + "DMC-LX100", new CameraProfile + { + SetDeviceName = false + } + }, + { + "DMC-TS5", new CameraProfile + { + SetDeviceName = false, + Parser = GH3Parser + } + }, + { + "DMC-GM1", new CameraProfile + { + SetDeviceName = false, + } + } + }; + + public bool NewTouch { get; set; } = true; + + public CameraParser Parser { get; set; } + + public bool RecStop { get; set; } = true; + + public bool RequestConnection { get; set; } = true; + + public bool SetDeviceName { get; set; } = true; + + public bool ManualFocusAF { get; set; } = true; + } +} \ No newline at end of file diff --git a/Core.Panasonic/Core.Panasonic.csproj b/Core.Panasonic/Core.Panasonic.csproj new file mode 100644 index 0000000..e19ee28 --- /dev/null +++ b/Core.Panasonic/Core.Panasonic.csproj @@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard1.4</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> + <PackageReference Include="Nito.AsyncEx.Coordination" Version="1.0.2" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> + <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> + <PackageReference Include="System.Net.Http" Version="4.3.2" /> + <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Core\Core.csproj" /> + </ItemGroup> + +</Project> \ No newline at end of file diff --git a/Core.Panasonic/CurMenu.cs b/Core.Panasonic/CurMenu.cs new file mode 100644 index 0000000..8185f63 --- /dev/null +++ b/Core.Panasonic/CurMenu.cs @@ -0,0 +1,9 @@ +namespace GMaster.Core.Camera.Panasonic +{ + using System.Collections.Generic; + + public class CurMenu + { + public Dictionary<string, bool> Enabled { get; } = new Dictionary<string, bool>(); + } +} \ No newline at end of file diff --git a/Core.Panasonic/FocusAreas.cs b/Core.Panasonic/FocusAreas.cs new file mode 100644 index 0000000..fbfbc36 --- /dev/null +++ b/Core.Panasonic/FocusAreas.cs @@ -0,0 +1,154 @@ +namespace GMaster.Core.Camera.Panasonic +{ + using System.Collections.Generic; + using LumixData; + + public class FocusAreas : IFocusAreas + { + private static readonly Dictionary<int, IntPoint> FocusPointShifts = new Dictionary<int, IntPoint> + { + { 13, new IntPoint(0, 0) }, + { 17, new IntPoint(0, 125) }, + { 15, new IntPoint(0, 58) }, + { 10, new IntPoint(125, 0) } + }; + + private readonly List<Box> boxes; + private readonly IntPoint focusPointShift; + private int hashcode; + + public FocusAreas(int number, IntPoint size, bool fix) + { + boxes = new List<Box>(number); + if (fix) + { + var intaspect = size.X * 10 / size.Y; + focusPointShift = FocusPointShifts.TryGetValue(intaspect, out var val) ? val : FocusPointShifts[13]; + } + } + + public IReadOnlyList<Box> Boxes => boxes; + + public void AddBox(int x1, int y1, int x2, int y2, LumixFocusAreaType type, bool failed) + { + var box = new Box(x1, y1, x2, y2, type, failed); + hashcode = (hashcode * 397) ^ box.GetHashCode(); + box.Fix(focusPointShift); + if (box.X1 >= 0 && box.X1 <= 1 && box.X2 >= 0 && box.X2 <= 1 && box.Y1 >= 0 && box.Y1 <= 1 && box.Y1 >= 0 && box.Y2 <= 1 && box.X2 >= box.X1 && box.Y2 >= box.Y1) + { + boxes.Add(box); + } + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((FocusAreas)obj); + } + + public override int GetHashCode() => hashcode; + + protected bool Equals(FocusAreas other) + { + return hashcode == other.hashcode; + } + + public class Box : IBox + { + private readonly BoxProps props; + private int x1; + private int x2; + private float xDivider = 1000; + private int y1; + private int y2; + private float yDivider = 1000; + + internal Box(int x1, int y1, int x2, int y2, LumixFocusAreaType type, bool failed) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + props.Type = type; + props.Failed = failed; + } + + public float Height => (y2 - y1) / yDivider; + + public BoxProps Props => props; + + public float Width => (x2 - x1) / xDivider; + + public float X1 => x1 / xDivider; + + public float X2 => x2 / xDivider; + + public float Y1 => y1 / yDivider; + + public float Y2 => y2 / yDivider; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((Box)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = props.GetHashCode(); + hashCode = (hashCode * 397) ^ x1; + hashCode = (hashCode * 397) ^ x2; + hashCode = (hashCode * 397) ^ y1; + hashCode = (hashCode * 397) ^ y2; + return hashCode; + } + } + + internal void Fix(IntPoint p) + { + x1 -= p.X; + x2 -= p.X; + y1 -= p.Y; + y2 -= p.Y; + + xDivider = 1000 - (p.X * 2); + yDivider = 1000 - (p.Y * 2); + } + + protected bool Equals(Box other) + { + return props.Equals(other.props) && x1 == other.x1 && x2 == other.x2 && y1 == other.y1 && y2 == other.y2; + } + } + } +} \ No newline at end of file diff --git a/Core/Camera/GH3Parser.cs b/Core.Panasonic/GH3Parser.cs similarity index 98% rename from Core/Camera/GH3Parser.cs rename to Core.Panasonic/GH3Parser.cs index 4db0d72..801f1ac 100644 --- a/Core/Camera/GH3Parser.cs +++ b/Core.Panasonic/GH3Parser.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System.Collections.Generic; using System.Linq; diff --git a/Core/Camera/GH4Parser.cs b/Core.Panasonic/GH4Parser.cs similarity index 98% rename from Core/Camera/GH4Parser.cs rename to Core.Panasonic/GH4Parser.cs index 32b9e67..049e3b3 100644 --- a/Core/Camera/GH4Parser.cs +++ b/Core.Panasonic/GH4Parser.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System.Collections.Generic; using System.Linq; diff --git a/Core/Camera/Http.cs b/Core.Panasonic/Http.cs similarity index 98% rename from Core/Camera/Http.cs rename to Core.Panasonic/Http.cs index 7ebede4..7164bdb 100644 --- a/Core/Camera/Http.cs +++ b/Core.Panasonic/Http.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System; using System.Collections.Generic; diff --git a/Core.Panasonic/IBox.cs b/Core.Panasonic/IBox.cs new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/Core.Panasonic/IBox.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Core/Camera/ICameraMenuItem.cs b/Core.Panasonic/ICameraMenuItem.cs similarity index 83% rename from Core/Camera/ICameraMenuItem.cs rename to Core.Panasonic/ICameraMenuItem.cs index 0a30a1c..894613d 100644 --- a/Core/Camera/ICameraMenuItem.cs +++ b/Core.Panasonic/ICameraMenuItem.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using Tools; diff --git a/Core.Panasonic/IntPoint.cs b/Core.Panasonic/IntPoint.cs new file mode 100644 index 0000000..71f08fe --- /dev/null +++ b/Core.Panasonic/IntPoint.cs @@ -0,0 +1,28 @@ +namespace GMaster.Core.Camera.Panasonic +{ + using System; + + public struct IntPoint + { + public IntPoint(int x, int y) + { + X = x; + Y = y; + } + + public IntPoint(FloatPoint p1, float m) + { + X = (int)(p1.X * m); + Y = (int)(p1.Y * m); + } + + public int X { get; } + + public int Y { get; } + + public IntPoint Clamp(int min, int max) + { + return new IntPoint(Math.Max(Math.Min(X, max), min), Math.Max(Math.Min(Y, max), min)); + } + } +} \ No newline at end of file diff --git a/Core.Panasonic/LensInfo.cs b/Core.Panasonic/LensInfo.cs new file mode 100644 index 0000000..585e7eb --- /dev/null +++ b/Core.Panasonic/LensInfo.cs @@ -0,0 +1,53 @@ +namespace GMaster.Core.Camera.Panasonic +{ + public class LensInfo + { + public int ClosedAperture { get; set; } = int.MaxValue; + + public bool HasPowerZoom { get; set; } + + public int MaxZoom { get; set; } + + public int MinZoom { get; set; } + + public int OpenedAperture { get; set; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((LensInfo)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = ClosedAperture; + hashCode = (hashCode * 397) ^ MaxZoom; + hashCode = (hashCode * 397) ^ MinZoom; + hashCode = (hashCode * 397) ^ OpenedAperture; + hashCode = (hashCode * 397) ^ HasPowerZoom.GetHashCode(); + return hashCode; + } + } + + protected bool Equals(LensInfo other) + { + return ClosedAperture == other.ClosedAperture && MaxZoom == other.MaxZoom && MinZoom == other.MinZoom && OpenedAperture == other.OpenedAperture && HasPowerZoom == other.HasPowerZoom; + } + } +} \ No newline at end of file diff --git a/Core/Camera/Lumix.Internal.cs b/Core.Panasonic/Lumix.Internal.cs similarity index 97% rename from Core/Camera/Lumix.Internal.cs rename to Core.Panasonic/Lumix.Internal.cs index a556864..d3274e0 100644 --- a/Core/Camera/Lumix.Internal.cs +++ b/Core.Panasonic/Lumix.Internal.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System; using System.Collections.Generic; @@ -10,10 +10,10 @@ using System.Threading.Tasks; using LumixData; using Network; - using Nito.AsyncEx; using Tools; + using Nito.AsyncEx; - public partial class Lumix + public partial class Lumix: ICamera { private static readonly Dictionary<string, RunnableCommandInfo> RunnableCommands; @@ -70,15 +70,8 @@ public Lumix(DeviceInfo device, IHttpClient client) public event Action ProfileUpdated; - public event Action<Lumix, UpdateStateFailReason> StateUpdateFailed; - - public enum UpdateStateFailReason - { - RequestFailed = 0, - LumixException = 1, - NotConnected = 2 - } - + public event Action<ICamera, UpdateStateFailReason> StateUpdateFailed; + public string CameraHost => Device.Host; public DeviceInfo Device { get; } diff --git a/Core/Camera/Lumix.cs b/Core.Panasonic/Lumix.cs similarity index 99% rename from Core/Camera/Lumix.cs rename to Core.Panasonic/Lumix.cs index ab1bb01..12437d1 100644 --- a/Core/Camera/Lumix.cs +++ b/Core.Panasonic/Lumix.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System; using System.Collections.Generic; diff --git a/Core/Camera/LumixData/AutoFocusMode.cs b/Core.Panasonic/LumixData/AutoFocusMode.cs similarity index 93% rename from Core/Camera/LumixData/AutoFocusMode.cs rename to Core.Panasonic/LumixData/AutoFocusMode.cs index 33c0a1b..c60a5db 100644 --- a/Core/Camera/LumixData/AutoFocusMode.cs +++ b/Core.Panasonic/LumixData/AutoFocusMode.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using Tools; diff --git a/Core.Panasonic/LumixData/BaseRequestResult.cs b/Core.Panasonic/LumixData/BaseRequestResult.cs new file mode 100644 index 0000000..6212972 --- /dev/null +++ b/Core.Panasonic/LumixData/BaseRequestResult.cs @@ -0,0 +1,11 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + [XmlRoot(ElementName = "camrply")] + public class BaseRequestResult + { + [XmlElement(ElementName = "result")] + public string Result { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/CameraMode.cs b/Core.Panasonic/LumixData/CameraMode.cs similarity index 96% rename from Core/Camera/LumixData/CameraMode.cs rename to Core.Panasonic/LumixData/CameraMode.cs index f1a48cf..e4778ad 100644 --- a/Core/Camera/LumixData/CameraMode.cs +++ b/Core.Panasonic/LumixData/CameraMode.cs @@ -2,7 +2,7 @@ #pragma warning disable SA1300 // Element must begin with upper-case letter -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System; using Tools; diff --git a/Core.Panasonic/LumixData/CameraState.cs b/Core.Panasonic/LumixData/CameraState.cs new file mode 100644 index 0000000..a4383ae --- /dev/null +++ b/Core.Panasonic/LumixData/CameraState.cs @@ -0,0 +1,177 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + public enum SdCardStatus + { + Unknown = 0, + + [XmlEnum(Name = "write_protected")] + WriteProtected = 2, + + [XmlEnum(Name = "write_enable")] + WriteEnable = 1 + } + + public enum RemainDisplayType + { + Unknown = 0, + + [XmlEnum(Name = "time")] + Time = 1, + + [XmlEnum(Name = "num")] + Num = 2 + } + + public class CameraState + { + [XmlElement(ElementName = "add_location_data")] + public OnOff AddLocationData { get; set; } + + [XmlElement(ElementName = "batt")] + public string Battery { get; set; } + + [XmlElement(ElementName = "batt_grip")] + public string GripBattery { get; set; } + + [XmlElement(ElementName = "burst_interval_status")] + public string BurstIntervalStatus { get; set; } + + [XmlElement(ElementName = "cammode")] + public string Cammode { get; set; } + + [XmlElement(ElementName = "interval_status")] + public OnOff IntervalStatus { get; set; } + + [XmlElement(ElementName = "lens")] + public string Lens { get; set; } + + [XmlElement(ElementName = "operate")] + public string Operate { get; set; } + + [XmlElement(ElementName = "progress_time")] + public int ProgressTime { get; set; } + + [XmlElement(ElementName = "rec")] + public OnOff Rec { get; set; } + + [XmlElement(ElementName = "remaincapacity")] + public int RemainCapacity { get; set; } + + [XmlElement(ElementName = "rem_disp_typ")] + public RemainDisplayType RemainDisplayType { get; set; } + + [XmlElement(ElementName = "sd_access")] + public OnOff SdAccess { get; set; } + + [XmlElement(ElementName = "sdcardstatus")] + public SdCardStatus SdCardStatus { get; set; } + + [XmlElement(ElementName = "sd2_access")] + public OnOff Sd2Access { get; set; } + + [XmlElement(ElementName = "sd2_cardstatus")] + public SdCardStatus Sd2CardStatus { get; set; } + + [XmlElement(ElementName = "sdi_state")] + public string SdiState { get; set; } + + [XmlElement(ElementName = "sd_memory")] + public SdMemorySet SdMemory { get; set; } + + [XmlElement(ElementName = "sd2_memory")] + public SdMemorySet Sd2Memory { get; set; } + + [XmlElement(ElementName = "stop_motion")] + public OnOff StopMotion { get; set; } + + [XmlElement(ElementName = "stop_motion_num")] + public int StopMotionNum { get; set; } + + [XmlElement(ElementName = "temperature")] + public string Temperature { get; set; } + + [XmlElement(ElementName = "version")] + public string Version { get; set; } + + [XmlElement(ElementName = "video_remaincapacity")] + public int VideoRemainCapacity { get; set; } + + [XmlElement(ElementName = "warn_disp")] + public string WarnDisp { get; set; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CameraState)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Battery?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (Cammode?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ RemainCapacity; + hashCode = (hashCode * 397) ^ (int)SdCardStatus; + hashCode = (hashCode * 397) ^ (int)SdMemory; + hashCode = (hashCode * 397) ^ VideoRemainCapacity; + hashCode = (hashCode * 397) ^ (int)Rec; + hashCode = (hashCode * 397) ^ (BurstIntervalStatus?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (int)SdAccess; + hashCode = (hashCode * 397) ^ (int)RemainDisplayType; + hashCode = (hashCode * 397) ^ ProgressTime; + hashCode = (hashCode * 397) ^ (Operate?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ StopMotionNum; + hashCode = (hashCode * 397) ^ (int)StopMotion; + hashCode = (hashCode * 397) ^ (Temperature?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Lens?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (int)AddLocationData; + hashCode = (hashCode * 397) ^ (int)IntervalStatus; + hashCode = (hashCode * 397) ^ (SdiState?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (WarnDisp?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Version?.GetHashCode() ?? 0); + return hashCode; + } + } + + protected bool Equals(CameraState other) + { + return string.Equals(Battery, other.Battery) && + string.Equals(Cammode, other.Cammode) && + RemainCapacity == other.RemainCapacity && + Equals(SdCardStatus, other.SdCardStatus) && + Equals(SdMemory, other.SdMemory) && + VideoRemainCapacity == other.VideoRemainCapacity && Rec == other.Rec && + string.Equals(BurstIntervalStatus, other.BurstIntervalStatus) && + Equals(SdAccess, other.SdAccess) && + Equals(RemainDisplayType, other.RemainDisplayType) && + ProgressTime == other.ProgressTime && + string.Equals(Operate, other.Operate) && + StopMotionNum == other.StopMotionNum && + StopMotion == other.StopMotion && + string.Equals(Temperature, other.Temperature) && + Lens == other.Lens && + AddLocationData == other.AddLocationData && + Equals(IntervalStatus, other.IntervalStatus) && + string.Equals(SdiState, other.SdiState) && + string.Equals(WarnDisp, other.WarnDisp) && + string.Equals(Version, other.Version); + } + } +} \ No newline at end of file diff --git a/Core.Panasonic/LumixData/CameraStateRequestResult.cs b/Core.Panasonic/LumixData/CameraStateRequestResult.cs new file mode 100644 index 0000000..3269b0b --- /dev/null +++ b/Core.Panasonic/LumixData/CameraStateRequestResult.cs @@ -0,0 +1,11 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + [XmlRoot(ElementName = "camrply")] + public class CameraStateRequestResult : BaseRequestResult + { + [XmlElement(ElementName = "state")] + public CameraState State { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/ChangeDirection.cs b/Core.Panasonic/LumixData/ChangeDirection.cs similarity index 86% rename from Core/Camera/LumixData/ChangeDirection.cs rename to Core.Panasonic/LumixData/ChangeDirection.cs index f849833..5bce6bc 100644 --- a/Core/Camera/LumixData/ChangeDirection.cs +++ b/Core.Panasonic/LumixData/ChangeDirection.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using Tools; diff --git a/Core/Camera/LumixData/CurMenuItem.cs b/Core.Panasonic/LumixData/CurMenuItem.cs similarity index 91% rename from Core/Camera/LumixData/CurMenuItem.cs rename to Core.Panasonic/LumixData/CurMenuItem.cs index 2f0f69e..b351730 100644 --- a/Core/Camera/LumixData/CurMenuItem.cs +++ b/Core.Panasonic/LumixData/CurMenuItem.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core.Panasonic/LumixData/CurMenuRequestResult.cs b/Core.Panasonic/LumixData/CurMenuRequestResult.cs new file mode 100644 index 0000000..6cfb825 --- /dev/null +++ b/Core.Panasonic/LumixData/CurMenuRequestResult.cs @@ -0,0 +1,11 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + [XmlRoot(ElementName = "camrply")] + public class CurMenuRequestResult : BaseRequestResult + { + [XmlElement("menuinfo")] + public MenuInfo MenuInfo { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/FocusAreaType.cs b/Core.Panasonic/LumixData/FocusAreaType.cs similarity index 81% rename from Core/Camera/LumixData/FocusAreaType.cs rename to Core.Panasonic/LumixData/FocusAreaType.cs index f6eadb1..aedf3f5 100644 --- a/Core/Camera/LumixData/FocusAreaType.cs +++ b/Core.Panasonic/LumixData/FocusAreaType.cs @@ -1,6 +1,6 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { - public enum FocusAreaType + public enum LumixFocusAreaType { OneAreaSelected = 0x0001, FaceOther = 0xff01, diff --git a/Core/Camera/LumixData/FocusMode.cs b/Core.Panasonic/LumixData/FocusMode.cs similarity index 88% rename from Core/Camera/LumixData/FocusMode.cs rename to Core.Panasonic/LumixData/FocusMode.cs index 72c71e0..f324ae0 100644 --- a/Core/Camera/LumixData/FocusMode.cs +++ b/Core.Panasonic/LumixData/FocusMode.cs @@ -1,6 +1,6 @@ // ReSharper disable InconsistentNaming -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core/Camera/LumixData/FocusModeRequestResult.cs b/Core.Panasonic/LumixData/FocusModeRequestResult.cs similarity index 90% rename from Core/Camera/LumixData/FocusModeRequestResult.cs rename to Core.Panasonic/LumixData/FocusModeRequestResult.cs index b5c2c82..ff656e5 100644 --- a/Core/Camera/LumixData/FocusModeRequestResult.cs +++ b/Core.Panasonic/LumixData/FocusModeRequestResult.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; diff --git a/Core.Panasonic/LumixData/FocusPosition.cs b/Core.Panasonic/LumixData/FocusPosition.cs new file mode 100644 index 0000000..924e9b5 --- /dev/null +++ b/Core.Panasonic/LumixData/FocusPosition.cs @@ -0,0 +1,9 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + public class FocusPosition + { + public int Maximum { get; set; } + + public int Value { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/Item.cs b/Core.Panasonic/LumixData/Item.cs similarity index 97% rename from Core/Camera/LumixData/Item.cs rename to Core.Panasonic/LumixData/Item.cs index af90856..2d4d230 100644 --- a/Core/Camera/LumixData/Item.cs +++ b/Core.Panasonic/LumixData/Item.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System; using System.Xml.Serialization; diff --git a/Core/Camera/LumixData/Language.cs b/Core.Panasonic/LumixData/Language.cs similarity index 89% rename from Core/Camera/LumixData/Language.cs rename to Core.Panasonic/LumixData/Language.cs index e916873..dfd27de 100644 --- a/Core/Camera/LumixData/Language.cs +++ b/Core.Panasonic/LumixData/Language.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core/Camera/LumixData/MenuHolder.cs b/Core.Panasonic/LumixData/MenuHolder.cs similarity index 79% rename from Core/Camera/LumixData/MenuHolder.cs rename to Core.Panasonic/LumixData/MenuHolder.cs index f970e19..bd24639 100644 --- a/Core/Camera/LumixData/MenuHolder.cs +++ b/Core.Panasonic/LumixData/MenuHolder.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core/Camera/LumixData/MenuInfo.cs b/Core.Panasonic/LumixData/MenuInfo.cs similarity index 92% rename from Core/Camera/LumixData/MenuInfo.cs rename to Core.Panasonic/LumixData/MenuInfo.cs index 9047f4b..c81e5b9 100644 --- a/Core/Camera/LumixData/MenuInfo.cs +++ b/Core.Panasonic/LumixData/MenuInfo.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core.Panasonic/LumixData/MenuSetRequestResult.cs b/Core.Panasonic/LumixData/MenuSetRequestResult.cs new file mode 100644 index 0000000..6a7f1bb --- /dev/null +++ b/Core.Panasonic/LumixData/MenuSetRequestResult.cs @@ -0,0 +1,11 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + [XmlRoot(ElementName = "camrply")] + public class MenuSetRequestResult : BaseRequestResult + { + [XmlElement(ElementName = "menuset")] + public RawMenuSet MenuSet { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/OnOff.cs b/Core.Panasonic/LumixData/OnOff.cs similarity index 81% rename from Core/Camera/LumixData/OnOff.cs rename to Core.Panasonic/LumixData/OnOff.cs index 4aaefcd..b8bc0b4 100644 --- a/Core/Camera/LumixData/OnOff.cs +++ b/Core.Panasonic/LumixData/OnOff.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core.Panasonic/LumixData/RawMenuSet.cs b/Core.Panasonic/LumixData/RawMenuSet.cs new file mode 100644 index 0000000..9db66f3 --- /dev/null +++ b/Core.Panasonic/LumixData/RawMenuSet.cs @@ -0,0 +1,35 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + [XmlRoot(ElementName = "menuset")] + public class RawMenuSet + { + [XmlAttribute(AttributeName = "date")] + public string Date { get; set; } + + [XmlElement(ElementName = "drivemode")] + public MenuHolder DriveMode { get; set; } + + [XmlElement(ElementName = "mainmenu")] + public MenuHolder MainMenu { get; set; } + + [XmlAttribute(AttributeName = "model")] + public string Model { get; set; } + + [XmlElement(ElementName = "photosettings")] + public MenuHolder Photosettings { get; set; } + + [XmlElement(ElementName = "qmenu")] + public MenuHolder Qmenu { get; set; } + + [XmlElement(ElementName = "qmenu2")] + public MenuHolder Qmenu2 { get; set; } + + [XmlElement(ElementName = "titlelist")] + public TitleList TitleList { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/Core.Panasonic/LumixData/RecState.cs b/Core.Panasonic/LumixData/RecState.cs new file mode 100644 index 0000000..b192753 --- /dev/null +++ b/Core.Panasonic/LumixData/RecState.cs @@ -0,0 +1,10 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + public enum RecState + { + Unknown = 0, + Stopped = 1, + Started = 2, + StopNotSupported = 3 + } +} \ No newline at end of file diff --git a/Core.Panasonic/LumixData/SdMemorySet.cs b/Core.Panasonic/LumixData/SdMemorySet.cs new file mode 100644 index 0000000..70ba837 --- /dev/null +++ b/Core.Panasonic/LumixData/SdMemorySet.cs @@ -0,0 +1,13 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + public enum SdMemorySet + { + [XmlEnum(Name = "set")] + Set = 1, + + [XmlEnum(Name = "unset")] + Unset = 0 + } +} \ No newline at end of file diff --git a/Core/Camera/LumixData/Title.cs b/Core.Panasonic/LumixData/Title.cs similarity index 84% rename from Core/Camera/LumixData/Title.cs rename to Core.Panasonic/LumixData/Title.cs index e176bfa..4a9ca77 100644 --- a/Core/Camera/LumixData/Title.cs +++ b/Core.Panasonic/LumixData/Title.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core/Camera/LumixData/TitleList.cs b/Core.Panasonic/LumixData/TitleList.cs similarity index 91% rename from Core/Camera/LumixData/TitleList.cs rename to Core.Panasonic/LumixData/TitleList.cs index bd5ec97..9e4ff8e 100644 --- a/Core/Camera/LumixData/TitleList.cs +++ b/Core.Panasonic/LumixData/TitleList.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera.LumixData +namespace GMaster.Core.Camera.Panasonic.LumixData { using System.Xml.Serialization; using Tools; diff --git a/Core.Panasonic/LumixData/YesNo.cs b/Core.Panasonic/LumixData/YesNo.cs new file mode 100644 index 0000000..79bbffd --- /dev/null +++ b/Core.Panasonic/LumixData/YesNo.cs @@ -0,0 +1,13 @@ +namespace GMaster.Core.Camera.Panasonic.LumixData +{ + using System.Xml.Serialization; + + public enum YesNo + { + [XmlEnum(Name = "no")] + No = 0, + + [XmlEnum(Name = "yes")] + Yes = 1 + } +} \ No newline at end of file diff --git a/Core/Camera/LumixError.cs b/Core.Panasonic/LumixError.cs similarity index 76% rename from Core/Camera/LumixError.cs rename to Core.Panasonic/LumixError.cs index 7a507f7..755369f 100644 --- a/Core/Camera/LumixError.cs +++ b/Core.Panasonic/LumixError.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using Tools; diff --git a/Core.Panasonic/LumixException.cs b/Core.Panasonic/LumixException.cs new file mode 100644 index 0000000..c9ed126 --- /dev/null +++ b/Core.Panasonic/LumixException.cs @@ -0,0 +1,20 @@ +namespace GMaster.Core.Camera.Panasonic +{ + using System; + + public class LumixException : Exception + { + public LumixException(LumixError error, string result, Exception exception = null) + : base(result, exception) + { + Error = error; + } + + public LumixException(string result, Exception exception = null) + : this(LumixError.Unknown, result, exception) + { + } + + public LumixError Error { get; } + } +} \ No newline at end of file diff --git a/Core/Camera/LumixManager.cs b/Core.Panasonic/LumixManager.cs similarity index 98% rename from Core/Camera/LumixManager.cs rename to Core.Panasonic/LumixManager.cs index 3ca61e7..645c2e1 100644 --- a/Core/Camera/LumixManager.cs +++ b/Core.Panasonic/LumixManager.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System; using System.Collections.Concurrent; @@ -6,10 +6,10 @@ namespace GMaster.Core.Camera using System.Linq; using System.Net.Http; using System.Threading.Tasks; - using Network; + using GMaster.Core.Network; + using GMaster.Core.Tools; using Rssdp; using Rssdp.Infrastructure; - using Tools; public class LumixManager { diff --git a/Core/Camera/LumixState.cs b/Core.Panasonic/LumixState.cs similarity index 99% rename from Core/Camera/LumixState.cs rename to Core.Panasonic/LumixState.cs index e6e2daa..c21d92d 100644 --- a/Core/Camera/LumixState.cs +++ b/Core.Panasonic/LumixState.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System.ComponentModel; using System.Runtime.CompilerServices; diff --git a/Core/Camera/MFAssistMoveStage.cs b/Core.Panasonic/MFAssistMoveStage.cs similarity index 84% rename from Core/Camera/MFAssistMoveStage.cs rename to Core.Panasonic/MFAssistMoveStage.cs index b635a9d..103f0cc 100644 --- a/Core/Camera/MFAssistMoveStage.cs +++ b/Core.Panasonic/MFAssistMoveStage.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using Tools; diff --git a/Core.Panasonic/MenuSet.cs b/Core.Panasonic/MenuSet.cs new file mode 100644 index 0000000..d1bfaa2 --- /dev/null +++ b/Core.Panasonic/MenuSet.cs @@ -0,0 +1,51 @@ +namespace GMaster.Core.Camera.Panasonic +{ + public class MenuSet + { + public TitledList<CameraMenuItemText> Angles { get; set; } + + public TitledList<CameraMenuItem256> Apertures { get; set; } + + public TitledList<CameraMenuItemText> AutobracketModes { get; set; } + + public TitledList<CameraMenuItemText> AutofocusModes { get; set; } + + public TitledList<CameraMenuItemText> BurstModes { get; set; } + + public TitledList<CameraMenuItemText> CreativeControls { get; set; } + + public TitledList<CameraMenuItemText> CustomMultiModes { get; set; } + + public TitledList<CameraMenuItemText> DbValues { get; set; } + + public TitledList<CameraMenuItemText> ExposureShifts { get; set; } + + public TitledList<CameraMenuItemText> FlashModes { get; set; } + + public TitledList<CameraMenuItemText> IsoValues { get; set; } + + public TitledList<CameraMenuItemText> LiveviewQuality { get; set; } + + public TitledList<CameraMenuItemText> MeteringMode { get; set; } + + public TitledList<CameraMenuItemText> PeakingModes { get; set; } + + public TitledList<CameraMenuItemText> PhotoAspects { get; set; } + + public TitledList<CameraMenuItemText> PhotoQuality { get; set; } + + public TitledList<CameraMenuItemText> PhotoSizes { get; set; } + + public TitledList<CameraMenuItemText> PhotoStyles { get; set; } + + public TitledList<CameraMenuItemText> ShutterSpeeds { get; set; } + + public CameraMenuItemText SingleShootMode { get; set; } + + public TitledList<CameraMenuItemText> VideoFormat { get; set; } + + public TitledList<CameraMenuItemText> VideoQuality { get; set; } + + public TitledList<CameraMenuItemText> WhiteBalances { get; set; } + } +} \ No newline at end of file diff --git a/Core/Camera/OffFrameProcessor.cs b/Core.Panasonic/OffFrameProcessor.cs similarity index 97% rename from Core/Camera/OffFrameProcessor.cs rename to Core.Panasonic/OffFrameProcessor.cs index de7e232..6e0ded7 100644 --- a/Core/Camera/OffFrameProcessor.cs +++ b/Core.Panasonic/OffFrameProcessor.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System; using System.Collections.Generic; @@ -122,7 +122,7 @@ private static FocusAreas GetFocusPoint(Slice slice, IntPoint size) var x2 = focusSlice.ToShort(4 + (i * multiplier)); var y2 = focusSlice.ToShort(6 + (i * multiplier)); var typeval = (int)focusSlice.ToUShort(10 + (i * multiplier)); - var type = Enum.IsDefined(typeof(FocusAreaType), typeval) ? (FocusAreaType)typeval : FocusAreaType.FaceOther; + var type = Enum.IsDefined(typeof(LumixFocusAreaType), typeval) ? (LumixFocusAreaType)typeval : LumixFocusAreaType.FaceOther; var failed = focusSlice[9 + (i * multiplier)] == 0; result.AddBox(x1, y1, x2, y2, type, failed); } diff --git a/Core.Panasonic/Slice.cs b/Core.Panasonic/Slice.cs new file mode 100644 index 0000000..2d001e0 --- /dev/null +++ b/Core.Panasonic/Slice.cs @@ -0,0 +1,70 @@ +namespace GMaster.Core.Camera.Panasonic +{ + using System; + using System.Collections; + using System.Collections.Generic; + + public class Slice : IEnumerable<byte> + { + private readonly byte[] array; + private readonly int offset; + + public Slice(byte[] array, int offset = 0) + { + this.array = array; + this.offset = offset; + Length = array.Length - offset; + } + + public Slice(Slice slice, int offset = 0, int length = -1) + { + array = slice.array; + this.offset = offset + slice.offset; + Length = slice.Length - offset; + if (length > -1) + { + if (length > Length) + { + throw new Exception("Slice length is wrong"); + } + + Length = length; + } + } + + public int Length { get; } + + public byte this[int index] => array[offset + index]; + + public IEnumerator<byte> GetEnumerator() + { + for (var i = offset; i < offset + Length; i++) + { + yield return array[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = offset; i < offset + Length; i++) + { + yield return array[i]; + } + } + + public short ToShort(int i) + { + return (short)((this[i] << 8) + this[i + 1]); + } + + public ushort ToUShort(int i) + { + return (ushort)((this[i] << 8) + this[i + 1]); + } + + public object ToInt(int i) + { + return (short)((this[i] << 24) + (this[i + 1] << 16) + (this[i + 2] << 8) + this[i + 3]); + } + } +} \ No newline at end of file diff --git a/Core.Panasonic/TextBinValue.cs b/Core.Panasonic/TextBinValue.cs new file mode 100644 index 0000000..895ee38 --- /dev/null +++ b/Core.Panasonic/TextBinValue.cs @@ -0,0 +1,48 @@ +namespace GMaster.Core.Camera.Panasonic +{ + public struct TextBinValue + { + public TextBinValue(string text, int bin) + { + Text = text; + Bin = bin; + } + + public int Bin { get; } + + public string Text { get; } + + public static bool operator !=(TextBinValue a, TextBinValue b) + { + return !Equals(a, b); + } + + public static bool operator ==(TextBinValue a, TextBinValue b) + { + return Equals(a, b); + } + + public bool Equals(TextBinValue other) + { + return string.Equals(Text, other.Text) && Bin == other.Bin; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is TextBinValue && Equals((TextBinValue)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Text?.GetHashCode() ?? 0) * 397) ^ Bin; + } + } + } +} \ No newline at end of file diff --git a/Core/Camera/TitledList.cs b/Core.Panasonic/TitledList.cs similarity index 90% rename from Core/Camera/TitledList.cs rename to Core.Panasonic/TitledList.cs index 2dd7e8d..03dae20 100644 --- a/Core/Camera/TitledList.cs +++ b/Core.Panasonic/TitledList.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System.Collections.Generic; using Tools; diff --git a/Core/Camera/TitledListExtensions.cs b/Core.Panasonic/TitledListExtensions.cs similarity index 88% rename from Core/Camera/TitledListExtensions.cs rename to Core.Panasonic/TitledListExtensions.cs index 4ed2e7c..6be3d08 100644 --- a/Core/Camera/TitledListExtensions.cs +++ b/Core.Panasonic/TitledListExtensions.cs @@ -1,4 +1,4 @@ -namespace GMaster.Core.Camera +namespace GMaster.Core.Camera.Panasonic { using System.Collections.Generic; using Tools; diff --git a/Core.Sony/Core.Sony.csproj b/Core.Sony/Core.Sony.csproj new file mode 100644 index 0000000..9eca547 --- /dev/null +++ b/Core.Sony/Core.Sony.csproj @@ -0,0 +1,7 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard1.4</TargetFramework> + </PropertyGroup> + +</Project> \ No newline at end of file diff --git a/Core.Tests/Core.Tests.csproj b/Core.Tests/Core.Tests.csproj index c3da647..a33da79 100644 --- a/Core.Tests/Core.Tests.csproj +++ b/Core.Tests/Core.Tests.csproj @@ -35,7 +35,7 @@ </PropertyGroup> <ItemGroup> <Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> - <HintPath>..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> + <HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> diff --git a/Core.Tests/MenuSetHelperTests.cs b/Core.Tests/MenuSetHelperTests.cs index 32773cd..2f35ba5 100644 --- a/Core.Tests/MenuSetHelperTests.cs +++ b/Core.Tests/MenuSetHelperTests.cs @@ -1,9 +1,10 @@ using System.Linq; -using GMaster.Core.Camera; -using GMaster.Core.Camera.LumixData; +using CameraApi; +using CameraApi.Panasonic; +using CameraApi.Panasonic.LumixData; using Newtonsoft.Json; -namespace Gmaster.Core.Camera +namespace CameraApi { using Xunit; using System.IO; diff --git a/Core.Tests/packages.config b/Core.Tests/packages.config index 8c2c59f..1acc36e 100644 --- a/Core.Tests/packages.config +++ b/Core.Tests/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Newtonsoft.Json" version="10.0.2" targetFramework="net47" /> + <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net47" /> <package id="xunit" version="2.2.0" targetFramework="net452" /> <package id="xunit.abstractions" version="2.0.1" targetFramework="net452" /> <package id="xunit.assert" version="2.2.0" targetFramework="net452" /> diff --git a/Core/Annotations.cs b/Core/Annotations.cs deleted file mode 100644 index 3302403..0000000 --- a/Core/Annotations.cs +++ /dev/null @@ -1,1252 +0,0 @@ -/* MIT License - -Copyright (c) 2016 JetBrains http://www.jetbrains.com - -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. */ - -#pragma warning disable - -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable IntroduceOptionalParameters.Global -// ReSharper disable MemberCanBeProtected.Global -// ReSharper disable InconsistentNaming -namespace GMaster.Annotations -{ - using System; - - /// <summary> - /// Indicates that the value of the marked element could be <c>null</c> sometimes, - /// so the check for <c>null</c> is necessary before its usage. - /// </summary> - /// <example> - /// <code> - /// [CanBeNull] object Test() => null; - /// - /// void UseTest() { - /// var p = Test(); - /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' - /// } - /// </code> - /// </example> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] - public sealed class CanBeNullAttribute : Attribute - { - } - - /// <summary> - /// Indicates that the value of the marked element could never be <c>null</c>. - /// </summary> - /// <example> - /// <code> - /// [NotNull] object Foo() { - /// return null; // Warning: Possible 'null' assignment - /// } - /// </code> - /// </example> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] - public sealed class NotNullAttribute : Attribute - { - } - - /// <summary> - /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can never be null. - /// </summary> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - public sealed class ItemNotNullAttribute : Attribute - { - } - - /// <summary> - /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can be null. - /// </summary> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - public sealed class ItemCanBeNullAttribute : Attribute - { - } - - /// <summary> - /// Indicates that the marked method builds string by format pattern and (optional) arguments. - /// Parameter, which contains format string, should be given in constructor. The format string - /// should be in <see cref="string.Format(IFormatProvider,string,object[])" />-like form. - /// </summary> - /// <example> - /// <code> - /// [StringFormatMethod("message")] - /// void ShowError(string message, params object[] args) { /* do something */ } - /// - /// void Foo() { - /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string - /// } - /// </code> - /// </example> - [AttributeUsage( - AttributeTargets.Constructor | AttributeTargets.Method | - AttributeTargets.Property | AttributeTargets.Delegate)] - public sealed class StringFormatMethodAttribute : Attribute - { - /// <param name="formatParameterName"> - /// Specifies which parameter of an annotated method should be treated as format-string - /// </param> - public StringFormatMethodAttribute([NotNull] string formatParameterName) - { - FormatParameterName = formatParameterName; - } - - [NotNull] - public string FormatParameterName { get; } - } - - /// <summary> - /// For a parameter that is expected to be one of the limited set of values. - /// Specify fields of which type should be used as values for this parameter. - /// </summary> - [AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, - AllowMultiple = true)] - public sealed class ValueProviderAttribute : Attribute - { - public ValueProviderAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] - public string Name { get; } - } - - /// <summary> - /// Indicates that the function argument should be string literal and match one - /// of the parameters of the caller function. For example, ReSharper annotates - /// the parameter of <see cref="System.ArgumentNullException" />. - /// </summary> - /// <example> - /// <code> - /// void Foo(string param) { - /// if (param == null) - /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class InvokerParameterNameAttribute : Attribute - { - } - - /// <summary> - /// Indicates that the method is contained in a type that implements - /// <c>System.ComponentModel.INotifyPropertyChanged</c> interface and this method - /// is used to notify that some property value changed. - /// </summary> - /// <remarks> - /// The method should be non-static and conform to one of the supported signatures: - /// <list> - /// <item> - /// <c>NotifyChanged(string)</c> - /// </item> - /// <item> - /// <c>NotifyChanged(params string[])</c> - /// </item> - /// <item> - /// <c>NotifyChanged{T}(Expression{Func{T}})</c> - /// </item> - /// <item> - /// <c>NotifyChanged{T,U}(Expression{Func{T,U}})</c> - /// </item> - /// <item> - /// <c>SetProperty{T}(ref T, T, string)</c> - /// </item> - /// </list> - /// </remarks> - /// <example> - /// <code> - /// public class Foo : INotifyPropertyChanged { - /// public event PropertyChangedEventHandler PropertyChanged; - /// - /// [NotifyPropertyChangedInvocator] - /// protected virtual void NotifyChanged(string propertyName) { ... } - /// - /// string _name; - /// - /// public string Name { - /// get { return _name; } - /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } - /// } - /// } - /// </code> - /// Examples of generated notifications: - /// <list> - /// <item> - /// <c>NotifyChanged("Property")</c> - /// </item> - /// <item> - /// <c>NotifyChanged(() => Property)</c> - /// </item> - /// <item> - /// <c>NotifyChanged((VM x) => x.Property)</c> - /// </item> - /// <item> - /// <c>SetProperty(ref myField, value, "Property")</c> - /// </item> - /// </list> - /// </example> - [AttributeUsage(AttributeTargets.Method)] - public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute - { - public NotifyPropertyChangedInvocatorAttribute() - { - } - - public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) - { - ParameterName = parameterName; - } - - [CanBeNull] - public string ParameterName { get; } - } - - /// <summary> - /// Describes dependency between method input and output. - /// </summary> - /// <syntax> - /// <p>Function Definition Table syntax:</p> - /// <list> - /// <item>FDT ::= FDTRow [;FDTRow]*</item> - /// <item>FDTRow ::= Input => Output | Output <= Input</item> - /// <item>Input ::= ParameterName: Value [, Input]*</item> - /// <item>Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item> - /// <item>Value ::= true | false | null | notnull | canbenull</item> - /// </list> - /// If method has single input parameter, it's name could be omitted.<br /> - /// Using <c>halt</c> (or <c>void</c>/<c>nothing</c>, which is the same) for method output - /// means that the methos doesn't return normally (throws or terminates the process).<br /> - /// Value <c>canbenull</c> is only applicable for output parameters.<br /> - /// You can use multiple <c>[ContractAnnotation]</c> for each FDT row, or use single attribute - /// with rows separated by semicolon. There is no notion of order rows, all rows are checked - /// for applicability and applied per each program state tracked by R# analysis.<br /> - /// </syntax> - /// <examples> - /// <list> - /// <item> - /// <code> - /// [ContractAnnotation("=> halt")] - /// public void TerminationMethod() - /// </code> - /// </item> - /// <item> - /// <code> - /// [ContractAnnotation("halt <= condition: false")] - /// public void Assert(bool condition, string text) // regular assertion method - /// </code> - /// </item> - /// <item> - /// <code> - /// [ContractAnnotation("s:null => true")] - /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() - /// </code> - /// </item> - /// <item> - /// <code> - /// // A method that returns null if the parameter is null, - /// // and not null if the parameter is not null - /// [ContractAnnotation("null => null; notnull => notnull")] - /// public object Transform(object data) - /// </code> - /// </item> - /// <item> - /// <code> - /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] - /// public bool TryParse(string s, out Person result) - /// </code> - /// </item> - /// </list> - /// </examples> - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class ContractAnnotationAttribute : Attribute - { - public ContractAnnotationAttribute([NotNull] string contract) - : this(contract, false) - { - } - - public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) - { - Contract = contract; - ForceFullStates = forceFullStates; - } - - [NotNull] - public string Contract { get; } - - public bool ForceFullStates { get; } - } - - /// <summary> - /// Indicates that marked element should be localized or not. - /// </summary> - /// <example> - /// <code> - /// [LocalizationRequiredAttribute(true)] - /// class Foo { - /// string str = "my string"; // Warning: Localizable string - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.All)] - public sealed class LocalizationRequiredAttribute : Attribute - { - public LocalizationRequiredAttribute() : this(true) - { - } - - public LocalizationRequiredAttribute(bool required) - { - Required = required; - } - - public bool Required { get; } - } - - /// <summary> - /// Indicates that the value of the marked type (or its derivatives) - /// cannot be compared using '==' or '!=' operators and <c>Equals()</c> - /// should be used instead. However, using '==' or '!=' for comparison - /// with <c>null</c> is always permitted. - /// </summary> - /// <example> - /// <code> - /// [CannotApplyEqualityOperator] - /// class NoEquality { } - /// - /// class UsesNoEquality { - /// void Test() { - /// var ca1 = new NoEquality(); - /// var ca2 = new NoEquality(); - /// if (ca1 != null) { // OK - /// bool condition = ca1 == ca2; // Warning - /// } - /// } - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] - public sealed class CannotApplyEqualityOperatorAttribute : Attribute - { - } - - /// <summary> - /// When applied to a target attribute, specifies a requirement for any type marked - /// with the target attribute to implement or inherit specific type or types. - /// </summary> - /// <example> - /// <code> - /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement - /// class ComponentAttribute : Attribute { } - /// - /// [Component] // ComponentAttribute requires implementing IComponent interface - /// class MyComponent : IComponent { } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - [BaseTypeRequired(typeof(Attribute))] - public sealed class BaseTypeRequiredAttribute : Attribute - { - public BaseTypeRequiredAttribute([NotNull] Type baseType) - { - BaseType = baseType; - } - - [NotNull] - public Type BaseType { get; } - } - - /// <summary> - /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), - /// so this symbol will not be marked as unused (as well as by other usage inspections). - /// </summary> - [AttributeUsage(AttributeTargets.All)] - public sealed class UsedImplicitlyAttribute : Attribute - { - public UsedImplicitlyAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) - { - } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) - { - } - - public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) - { - } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - public ImplicitUseKindFlags UseKindFlags { get; } - - public ImplicitUseTargetFlags TargetFlags { get; } - } - - /// <summary> - /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes - /// as unused (as well as by other usage inspections) - /// </summary> - [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] - public sealed class MeansImplicitUseAttribute : Attribute - { - public MeansImplicitUseAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) - { - } - - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) - { - } - - public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) - { - } - - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - [UsedImplicitly] - public ImplicitUseKindFlags UseKindFlags { get; private set; } - - [UsedImplicitly] - public ImplicitUseTargetFlags TargetFlags { get; private set; } - } - - [Flags] - public enum ImplicitUseKindFlags - { - Default = Access | Assign | InstantiatedWithFixedConstructorSignature, - - /// <summary>Only entity marked with attribute considered used.</summary> - Access = 1, - - /// <summary>Indicates implicit assignment to a member.</summary> - Assign = 2, - - /// <summary> - /// Indicates implicit instantiation of a type with fixed constructor signature. - /// That means any unused constructor parameters won't be reported as such. - /// </summary> - InstantiatedWithFixedConstructorSignature = 4, - - /// <summary>Indicates implicit instantiation of a type.</summary> - InstantiatedNoFixedConstructorSignature = 8 - } - - /// <summary> - /// Specify what is considered used implicitly when marked - /// with <see cref="MeansImplicitUseAttribute" /> or <see cref="UsedImplicitlyAttribute" />. - /// </summary> - [Flags] - public enum ImplicitUseTargetFlags - { - Default = Itself, - Itself = 1, - - /// <summary>Members of entity marked with attribute are considered used.</summary> - Members = 2, - - /// <summary>Entity marked with attribute and all its members considered used.</summary> - WithMembers = Itself | Members - } - - /// <summary> - /// This attribute is intended to mark publicly available API - /// which should not be removed and so is treated as used. - /// </summary> - [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] - public sealed class PublicAPIAttribute : Attribute - { - public PublicAPIAttribute() - { - } - - public PublicAPIAttribute([NotNull] string comment) - { - Comment = comment; - } - - [CanBeNull] - public string Comment { get; } - } - - /// <summary> - /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. - /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. - /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class InstantHandleAttribute : Attribute - { - } - - /// <summary> - /// Indicates that a method does not make any observable state changes. - /// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>. - /// </summary> - /// <example> - /// <code> - /// [Pure] int Multiply(int x, int y) => x * y; - /// - /// void M() { - /// Multiply(123, 42); // Waring: Return value of pure method is not used - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Method)] - public sealed class PureAttribute : Attribute - { - } - - /// <summary> - /// Indicates that the return value of method invocation must be used. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - public sealed class MustUseReturnValueAttribute : Attribute - { - public MustUseReturnValueAttribute() - { - } - - public MustUseReturnValueAttribute([NotNull] string justification) - { - Justification = justification; - } - - [CanBeNull] - public string Justification { get; } - } - - /// <summary> - /// Indicates the type member or parameter of some type, that should be used instead of all other ways - /// to get the value that type. This annotation is useful when you have some "context" value evaluated - /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. - /// </summary> - /// <example> - /// <code> - /// class Foo { - /// [ProvidesContext] IBarService _barService = ...; - /// - /// void ProcessNode(INode node) { - /// DoSomething(node, node.GetGlobalServices().Bar); - /// // ^ Warning: use value of '_barService' field - /// } - /// } - /// </code> - /// </example> - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | - AttributeTargets.GenericParameter)] - public sealed class ProvidesContextAttribute : Attribute - { - } - - /// <summary> - /// Indicates that a parameter is a path to a file or a folder within a web project. - /// Path can be relative or absolute, starting from web root (~). - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class PathReferenceAttribute : Attribute - { - public PathReferenceAttribute() - { - } - - public PathReferenceAttribute([NotNull] [PathReference] string basePath) - { - BasePath = basePath; - } - - [CanBeNull] - public string BasePath { get; } - } - - /// <summary> - /// An extension method marked with this attribute is processed by ReSharper code completion - /// as a 'Source Template'. When extension method is completed over some expression, it's source code - /// is automatically expanded like a template at call site. - /// </summary> - /// <remarks> - /// Template method body can contain valid source code and/or special comments starting with '$'. - /// Text inside these comments is added as source code when the template is applied. Template parameters - /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. - /// Use the <see cref="MacroAttribute" /> attribute to specify macros for parameters. - /// </remarks> - /// <example> - /// In this example, the 'forEach' method is a source template available over all values - /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: - /// <code> - /// [SourceTemplate] - /// public static void forEach<T>(this IEnumerable<T> xs) { - /// foreach (var x in xs) { - /// //$ $END$ - /// } - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Method)] - public sealed class SourceTemplateAttribute : Attribute - { - } - - /// <summary> - /// Allows specifying a macro for a parameter of a <see cref="SourceTemplateAttribute">source template</see>. - /// </summary> - /// <remarks> - /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression - /// is defined in the <see cref="MacroAttribute.Expression" /> property. When applied on a method, the target - /// template parameter is defined in the <see cref="MacroAttribute.Target" /> property. To apply the macro silently - /// for the parameter, set the <see cref="MacroAttribute.Editable" /> property value = -1. - /// </remarks> - /// <example> - /// Applying the attribute on a source template method: - /// <code> - /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] - /// public static void forEach<T>(this IEnumerable<T> collection) { - /// foreach (var item in collection) { - /// //$ $END$ - /// } - /// } - /// </code> - /// Applying the attribute on a template method parameter: - /// <code> - /// [SourceTemplate] - /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { - /// /*$ var $x$Id = "$newguid$" + x.ToString(); - /// x.DoSomething($x$Id); */ - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] - public sealed class MacroAttribute : Attribute - { - /// <summary> - /// Allows specifying a macro that will be executed for a <see cref="SourceTemplateAttribute">source template</see> - /// parameter when the template is expanded. - /// </summary> - [CanBeNull] - public string Expression { get; set; } - - /// <summary> - /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. - /// </summary> - /// <remarks> - /// If the target parameter is used several times in the template, only one occurrence becomes editable; - /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, - /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. - /// </remarks> - /// > - public int Editable { get; set; } - - /// <summary> - /// Identifies the target parameter of a <see cref="SourceTemplateAttribute">source template</see> if the - /// <see cref="MacroAttribute" /> is applied on a template method. - /// </summary> - [CanBeNull] - public string Target { get; set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute - { - public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute - { - public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute - { - public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcMasterLocationFormatAttribute : Attribute - { - public AspMvcMasterLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute - { - public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcViewLocationFormatAttribute : Attribute - { - public AspMvcViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC action. If applied to a method, the MVC action name is calculated - /// implicitly from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcActionAttribute : Attribute - { - public AspMvcActionAttribute() - { - } - - public AspMvcActionAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] - public string AnonymousProperty { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcAreaAttribute : Attribute - { - public AspMvcAreaAttribute() - { - } - - public AspMvcAreaAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] - public string AnonymousProperty { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is - /// an MVC controller. If applied to a method, the MVC controller name is calculated - /// implicitly from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcControllerAttribute : Attribute - { - public AspMvcControllerAttribute() - { - } - - public AspMvcControllerAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] - public string AnonymousProperty { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute - /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcMasterAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute - /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, Object)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcModelTypeAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC - /// partial view. If applied to a method, the MVC partial view name is calculated implicitly - /// from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcPartialViewAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. - /// </summary> - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public sealed class AspMvcSuppressViewErrorAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcDisplayTemplateAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcEditorTemplateAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. - /// Use this attribute for custom wrappers similar to - /// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcTemplateAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly - /// from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Controller.View(Object)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcViewAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component name. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcViewComponentAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component view. If applied to a method, the MVC view component view name is default. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcViewComponentViewAttribute : Attribute - { - } - - /// <summary> - /// ASP.NET MVC attribute. When applied to a parameter of an attribute, - /// indicates that this parameter is an MVC action name. - /// </summary> - /// <example> - /// <code> - /// [ActionName("Foo")] - /// public ActionResult Login(string returnUrl) { - /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK - /// return RedirectToAction("Bar"); // Error: Cannot resolve action - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] - public sealed class AspMvcActionSelectorAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] - public sealed class HtmlElementAttributesAttribute : Attribute - { - public HtmlElementAttributesAttribute() - { - } - - public HtmlElementAttributesAttribute([NotNull] string name) - { - Name = name; - } - - [CanBeNull] - public string Name { get; } - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] - public sealed class HtmlAttributeValueAttribute : Attribute - { - public HtmlAttributeValueAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] - public string Name { get; } - } - - /// <summary> - /// Razor attribute. Indicates that a parameter or a method is a Razor section. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class RazorSectionAttribute : Attribute - { - } - - /// <summary> - /// Indicates how method, constructor invocation or property access - /// over collection type affects content of the collection. - /// </summary> - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] - public sealed class CollectionAccessAttribute : Attribute - { - public CollectionAccessAttribute(CollectionAccessType collectionAccessType) - { - CollectionAccessType = collectionAccessType; - } - - public CollectionAccessType CollectionAccessType { get; } - } - - [Flags] - public enum CollectionAccessType - { - /// <summary>Method does not use or modify content of the collection.</summary> - None = 0, - - /// <summary>Method only reads content of the collection but does not modify it.</summary> - Read = 1, - - /// <summary>Method can change content of the collection but does not add new elements.</summary> - ModifyExistingContent = 2, - - /// <summary>Method can add new elements to the collection.</summary> - UpdatedContent = ModifyExistingContent | 4 - } - - /// <summary> - /// Indicates that the marked method is assertion method, i.e. it halts control flow if - /// one of the conditions is satisfied. To set the condition, mark one of the parameters with - /// <see cref="AssertionConditionAttribute" /> attribute. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - public sealed class AssertionMethodAttribute : Attribute - { - } - - /// <summary> - /// Indicates the condition parameter of the assertion method. The method itself should be - /// marked by <see cref="AssertionMethodAttribute" /> attribute. The mandatory argument of - /// the attribute is the assertion type. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AssertionConditionAttribute : Attribute - { - public AssertionConditionAttribute(AssertionConditionType conditionType) - { - ConditionType = conditionType; - } - - public AssertionConditionType ConditionType { get; } - } - - /// <summary> - /// Specifies assertion type. If the assertion method argument satisfies the condition, - /// then the execution continues. Otherwise, execution is assumed to be halted. - /// </summary> - public enum AssertionConditionType - { - /// <summary>Marked parameter should be evaluated to true.</summary> - IS_TRUE = 0, - - /// <summary>Marked parameter should be evaluated to false.</summary> - IS_FALSE = 1, - - /// <summary>Marked parameter should be evaluated to null value.</summary> - IS_NULL = 2, - - /// <summary>Marked parameter should be evaluated to not null value.</summary> - IS_NOT_NULL = 3 - } - - /// <summary> - /// Indicates that the marked method unconditionally terminates control flow execution. - /// For example, it could unconditionally throw exception. - /// </summary> - [Obsolete("Use [ContractAnnotation('=> halt')] instead")] - [AttributeUsage(AttributeTargets.Method)] - public sealed class TerminatesProgramAttribute : Attribute - { - } - - /// <summary> - /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, - /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters - /// of delegate type by analyzing LINQ method chains. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - public sealed class LinqTunnelAttribute : Attribute - { - } - - /// <summary> - /// Indicates that IEnumerable, passed as parameter, is not enumerated. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class NoEnumerationAttribute : Attribute - { - } - - /// <summary> - /// Indicates that parameter is regular expression pattern. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class RegexPatternAttribute : Attribute - { - } - - /// <summary> - /// Prevents the Member Reordering feature from tossing members of the marked class. - /// </summary> - /// <remarks> - /// The attribute must be mentioned in your member reordering patterns - /// </remarks> - [AttributeUsage( - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] - public sealed class NoReorderAttribute : Attribute - { - } - - /// <summary> - /// XAML attribute. Indicates the type that has <c>ItemsSource</c> property and should be treated - /// as <c>ItemsControl</c>-derived type, to enable inner items <c>DataContext</c> type resolve. - /// </summary> - [AttributeUsage(AttributeTargets.Class)] - public sealed class XamlItemsControlAttribute : Attribute - { - } - - /// <summary> - /// XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that - /// is used to bind some item of <c>ItemsControl</c>-derived type. This annotation will - /// enable the <c>DataContext</c> type resolve for XAML bindings for such properties. - /// </summary> - /// <remarks> - /// Property should have the tree ancestor of the <c>ItemsControl</c> type or - /// marked with the <see cref="XamlItemsControlAttribute" /> attribute. - /// </remarks> - [AttributeUsage(AttributeTargets.Property)] - public sealed class XamlItemBindingOfItemsControlAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class AspChildControlTypeAttribute : Attribute - { - public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) - { - TagName = tagName; - ControlType = controlType; - } - - [NotNull] - public string TagName { get; } - - [NotNull] - public Type ControlType { get; } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] - public sealed class AspDataFieldAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] - public sealed class AspDataFieldsAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public sealed class AspMethodPropertyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class AspRequiredAttributeAttribute : Attribute - { - public AspRequiredAttributeAttribute([NotNull] string attribute) - { - Attribute = attribute; - } - - [NotNull] - public string Attribute { get; } - } - - [AttributeUsage(AttributeTargets.Property)] - public sealed class AspTypePropertyAttribute : Attribute - { - public AspTypePropertyAttribute(bool createConstructorReferences) - { - CreateConstructorReferences = createConstructorReferences; - } - - public bool CreateConstructorReferences { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class RazorImportNamespaceAttribute : Attribute - { - public RazorImportNamespaceAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] - public string Name { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class RazorInjectionAttribute : Attribute - { - public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) - { - Type = type; - FieldName = fieldName; - } - - [NotNull] - public string Type { get; } - - [NotNull] - public string FieldName { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class RazorDirectiveAttribute : Attribute - { - public RazorDirectiveAttribute([NotNull] string directive) - { - Directive = directive; - } - - [NotNull] - public string Directive { get; } - } - - [AttributeUsage(AttributeTargets.Method)] - public sealed class RazorHelperCommonAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public sealed class RazorLayoutAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method)] - public sealed class RazorWriteLiteralMethodAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method)] - public sealed class RazorWriteMethodAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class RazorWriteMethodParameterAttribute : Attribute - { - } -} \ No newline at end of file diff --git a/Core/Camera/CamerasManager.cs b/Core/Camera/CamerasManager.cs new file mode 100644 index 0000000..90e9b44 --- /dev/null +++ b/Core/Camera/CamerasManager.cs @@ -0,0 +1,257 @@ +namespace CameraApi +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Rssdp; + using Rssdp.Infrastructure; + using GMaster.Core.Network; + using CameraApi.Core; + + public class CamerasManager + { + private const int LiveViewPort = 49152; + + private readonly HashSet<string> foundDevices = new HashSet<string>(); + private readonly string lang; + private readonly INetwork network; + private readonly ConcurrentDictionary<string, ICamera> ipToCamera = new ConcurrentDictionary<string, ICamera>(); + private readonly ConcurrentDictionary<string, ICamera> usnToCamera = new ConcurrentDictionary<string, ICamera>(); + private List<SsdpDeviceLocator> deviceLocators; + private List<IDatagramSocket> liveviewUdpSockets; + + public CamerasManager(string lang, INetwork network) + { + this.lang = lang; + this.network = network; + network.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged; + } + + public event Action<SsdpDeviceInfo, ICamera> DeviceDiscovered2; + + public async Task<bool> ConnectCamera(ICamera camera) + { + try + { + var connectResult = await camera.Connect(LiveViewPort, lang); + if (connectResult) + { + ipToCamera[camera.CameraHost] = camera; + usnToCamera[camera.Device.Usn] = camera; + Debug.WriteLine("Add listener: " + camera.CameraHost, "UDP"); + } + + return connectResult; + } + catch (Exception ex) + { + Log.Error(new Exception("Connection failed", ex)); + return false; + } + } + + public void SearchCameras() + { + try + { + if (deviceLocators != null) + { + foreach (var dev in deviceLocators) + { + if (!dev.IsSearching) + { + var task = Task.Run(async () => + { + try + { + await dev.SearchAsync(); + } + catch (ObjectDisposedException) + { + // Ignore due RSSDP lacks Cancellation + } + catch (Exception ex) + { + Log.Error(ex); + } + }); + } + } + } + } + catch (Exception ex) + { + Log.Error(ex); + } + } + + public async Task StartListening() + { + liveviewUdpSockets = new List<IDatagramSocket>(); + var confirmedHosts = new List<string>(); + foreach (var profile in network.GetHostNames()) + { + var liveviewUdp = network.CreateDatagramSocket(); + try + { + liveviewUdp.MessageReceived += LiveviewUdp_MessageReceived; + + await liveviewUdp.Bind(profile, LiveViewPort); + liveviewUdpSockets.Add(liveviewUdp); + confirmedHosts.Add(profile); + } + catch (Exception) + { + liveviewUdp.Dispose(); + } + } + + lock (foundDevices) + { + foundDevices.Clear(); + } + + deviceLocators = new List<SsdpDeviceLocator>(); + + foreach (var host in confirmedHosts) + { + var deviceLocator = + new SsdpDeviceLocator(new SsdpCommunicationsServer(new SocketFactory(host))) + { + NotificationFilter = "urn:schemas-upnp-org:device:MediaServer:1" + }; + deviceLocator.DeviceAvailable += DeviceLocator_DeviceAvailable; + deviceLocator.StartListeningForNotifications(); + deviceLocators.Add(deviceLocator); + } + } + + public void StopListening() + { + if (liveviewUdpSockets != null) + { + foreach (var liveviewUdpSocket in liveviewUdpSockets) + { + liveviewUdpSocket.Dispose(); + } + } + + liveviewUdpSockets = null; + + if (deviceLocators != null) + { + foreach (var deviceLocator in deviceLocators) + { + deviceLocator.StopListeningForNotifications(); + deviceLocator.Dispose(); + } + } + + deviceLocators = null; + } + + public void ForgetDiscovery(DeviceInfo dev) + { + var usn = dev.Usn; + var host = dev.Host; + lock (foundDevices) + { + foundDevices.Remove(usn + host); + } + } + + public void ForgetCamera(ICamera cam) + { + var usn = cam.Device.Usn; + var host = cam.Device.Host; + ipToCamera.TryRemove(host, out _); + usnToCamera.TryRemove(usn, out _); + } + + private async void DeviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs arg) + { + try + { + var usn = arg.DiscoveredDevice.Usn; + var host = arg.DiscoveredDevice.DescriptionLocation.Host; + lock (foundDevices) + { + if (foundDevices.Contains(usn + host)) + { + Debug.WriteLine("Discovered but already found: " + usn, "Discovery"); + return; + } + + Debug.WriteLine("Discovered new: " + usn, "Discovery"); + + foundDevices.Add(usn + host); + } + + if (!arg.DiscoveredDevice.ResponseHeaders.TryGetValues("SERVER", out var values) || !values.Any(s => s.Contains("Panasonic"))) + { + return; + } + + var info = await arg.DiscoveredDevice.GetDeviceInfo() as SsdpRootDevice; + if (info == null) + { + return; + } + + if (info.ModelName != "LUMIX") + { + return; + } + + var dev = new DeviceInfo(info, usn); + Log.Trace("Discovered " + dev.ModelName, tags: "camera." + dev.ModelName); + + if (usnToCamera.TryGetValue(usn, out var oldcamera)) + { + DeviceDiscovered2?.Invoke(dev, oldcamera); + } + else + { + DeviceDiscovered2?.Invoke(dev, null); + } + } + catch (HttpRequestException e) + { + // var status = WebSocketError.GetStatus(ex.GetBaseException().HResult); + Debug.WriteLine(e); + + // Ignore because GetDeviceInfo has problems + } + catch (Exception e) + { + Log.Error(e); + } + } + + private void LiveviewUdp_MessageReceived(DatagramSocketMessage args) + { + try + { + if (!ipToCamera.TryGetValue(args.RemoteAddress, out ICamera camera)) + { + return; + } + + Task.Run(() => camera.ProcessUdpMessage(args.Data)); + } + catch (Exception e) + { + Log.Error(e); + } + } + + private async void NetworkInformation_NetworkStatusChanged() + { + StopListening(); + await StartListening(); + } + } +} \ No newline at end of file diff --git a/Core/Core.csproj b/Core/Core.csproj index a679f61..33561ef 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -7,12 +7,17 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> + <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Nito.AsyncEx.Coordination" Version="1.0.2" /> - <PackageReference Include="Rssdp" Version="3.0.1" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.0.0" /> + <PackageReference Include="Rssdp" Version="3.5.5" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> + <PackageReference Include="System.Net.Http" Version="4.3.2" /> <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\CameraApi.Core\CameraApi.Core.csproj" /> + </ItemGroup> + </Project> \ No newline at end of file diff --git a/Core/Network/SsdpDeviceInfo.cs b/Core/Network/SsdpDeviceInfo.cs new file mode 100644 index 0000000..5f7567f --- /dev/null +++ b/Core/Network/SsdpDeviceInfo.cs @@ -0,0 +1,56 @@ +namespace CameraApi.Core +{ + using Rssdp; + + public class SsdpDeviceInfo + { + internal SsdpDeviceInfo(SsdpRootDevice dev, string usn) + { + Usn = usn; + Uuid = dev.Uuid; + Host = dev.Location.Host; + FriendlyName = dev.FriendlyName; + ModelName = dev.ModelNumber; + } + + public string FriendlyName { get; } + + public string Host { get; set; } + + public string ModelName { get; } + + public string Usn { get; } + + public string Uuid { get; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((SsdpDeviceInfo)obj); + } + + public override int GetHashCode() + { + return Uuid != null ? Uuid.GetHashCode() : 0; + } + + protected bool Equals(SsdpDeviceInfo other) + { + return string.Equals(Uuid, other.Uuid); + } + } +} \ No newline at end of file diff --git a/Core/Tools/Log.cs b/Core/Tools/Log.cs index f3cc8e4..a7b33d7 100644 --- a/Core/Tools/Log.cs +++ b/Core/Tools/Log.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json.Converters; - -namespace GMaster.Core.Tools +namespace GMaster.Core.Tools { using System; using System.Collections.Concurrent; @@ -10,6 +8,7 @@ namespace GMaster.Core.Tools using System.Threading.Tasks; using Network; using Newtonsoft.Json; + using Newtonsoft.Json.Converters; using Nito.AsyncEx; public static class Log diff --git a/GMaster.sln b/GMaster.sln index edea0f0..bef839f 100644 --- a/GMaster.sln +++ b/GMaster.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +VisualStudioVersion = 15.0.26430.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GMaster", "GMaster\GMaster.csproj", "{F361EA15-EF81-4B2D-9D9B-B32B4D174934}" EndProject @@ -11,10 +11,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools", "Tools\Tools.csproj EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools.Tests", "Tools.Tests\Tools.Tests.csproj", "{D9E969D8-E655-4E21-8A2F-FBA250F30D48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{6FA17036-459D-4967-A31B-8E8C7264E497}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{6FA17036-459D-4967-A31B-8E8C7264E497}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Tests", "Core.Tests\Core.Tests.csproj", "{3DC1817B-E178-47B3-A6A3-45FD2BEB868B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CameraApi.Panasonic", "CameraApi.Panasonic\CameraApi.Panasonic.csproj", "{E051F426-E394-4497-A0EA-B0B3D57B899D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CameraApi.Core", "CameraApi.Core\CameraApi.Core.csproj", "{1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -119,6 +123,38 @@ Global {3DC1817B-E178-47B3-A6A3-45FD2BEB868B}.Release|x64.Build.0 = Release|Any CPU {3DC1817B-E178-47B3-A6A3-45FD2BEB868B}.Release|x86.ActiveCfg = Release|Any CPU {3DC1817B-E178-47B3-A6A3-45FD2BEB868B}.Release|x86.Build.0 = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|ARM.Build.0 = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|x64.ActiveCfg = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|x64.Build.0 = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|x86.ActiveCfg = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Debug|x86.Build.0 = Debug|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|Any CPU.Build.0 = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|ARM.ActiveCfg = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|ARM.Build.0 = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|x64.ActiveCfg = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|x64.Build.0 = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|x86.ActiveCfg = Release|Any CPU + {E051F426-E394-4497-A0EA-B0B3D57B899D}.Release|x86.Build.0 = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|ARM.ActiveCfg = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|ARM.Build.0 = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|x64.Build.0 = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Debug|x86.Build.0 = Debug|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|Any CPU.Build.0 = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|ARM.ActiveCfg = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|ARM.Build.0 = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|x64.ActiveCfg = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|x64.Build.0 = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|x86.ActiveCfg = Release|Any CPU + {1C4D7C88-8F3B-4536-8D34-383D3CB96DF2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GMaster/App.xaml.cs b/GMaster/App.xaml.cs index 393f017..c652f0b 100644 --- a/GMaster/App.xaml.cs +++ b/GMaster/App.xaml.cs @@ -36,30 +36,6 @@ public App() Debug.AddCategory("Discovery", false); } - private async void App_Resuming(object sender, object e) - { - await OnResuming(); - } - - private async Task OnResuming() - { - var ver = Package.Current.Id.Version; - - var eas = new EasClientDeviceInformation(); - var deviceName = string.Concat(eas.SystemProductName.Where(char.IsLetterOrDigit)); - if (deviceName == "SystemProductName") - { - deviceName = "PC"; - } - - Log.Init(new WindowsHttpClient(), "deb4bd35-6ddd-4044-b3e8-ac76330e559b", $"{ver.Major}.{ver.Minor}.{ver.Build}", deviceName, 500); - - if (MainModel != null) - { - await MainModel.ConnectionsManager.StartListening(); - } - } - public MainPageModel MainModel { get; private set; } public static async Task<StorageFolder> GetLutsFolder() @@ -123,12 +99,18 @@ protected override async void OnLaunched(LaunchActivatedEventArgs e) } } + private async void App_Resuming(object sender, object e) + { + await OnResuming(); + } + private void App_UnhandledException(object sender, UnhandledExceptionEventArgs e) { switch (e.Exception) { case ObjectDisposedException ex: Debug.WriteLine("ObjectDisposedException in " + ex.Source, "UnhandledException"); + e.Handled = true; break; default: @@ -150,6 +132,25 @@ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) throw new Exception("Failed to load Page " + e.SourcePageType.FullName); } + private async Task OnResuming() + { + var ver = Package.Current.Id.Version; + + var eas = new EasClientDeviceInformation(); + var deviceName = string.Concat(eas.SystemProductName.Where(char.IsLetterOrDigit)); + if (deviceName == "SystemProductName") + { + deviceName = "PC"; + } + + Log.Init(new WindowsHttpClient(), "deb4bd35-6ddd-4044-b3e8-ac76330e559b", $"{ver.Major}.{ver.Minor}.{ver.Build}", deviceName, 500); + + if (MainModel != null) + { + await MainModel.ConnectionsManager.StartListening(); + } + } + /// <summary> /// Invoked when application execution is being suspended. Application state is saved /// without knowing whether the application will be terminated or resumed with the contents diff --git a/GMaster/Donations.cs b/GMaster/Donations.cs index 04480c4..66f0cb1 100644 --- a/GMaster/Donations.cs +++ b/GMaster/Donations.cs @@ -4,8 +4,8 @@ using System.ComponentModel; using System.Runtime.CompilerServices; using System.Threading.Tasks; - using Annotations; using Core.Tools; + using JetBrains.Annotations; using Windows.Services.Store; public class Donations : INotifyPropertyChanged diff --git a/GMaster/GMaster.csproj b/GMaster/GMaster.csproj index 5197ef4..d46b038 100644 --- a/GMaster/GMaster.csproj +++ b/GMaster/GMaster.csproj @@ -7,8 +7,8 @@ <ProjectGuid>{F361EA15-EF81-4B2D-9D9B-B32B4D174934}</ProjectGuid> <OutputType>AppContainerExe</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>GMaster</RootNamespace> - <AssemblyName>GMaster</AssemblyName> + <RootNamespace>LMaster</RootNamespace> + <AssemblyName>LMaster</AssemblyName> <DefaultLanguage>en-US</DefaultLanguage> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformVersion>10.0.15063.0</TargetPlatformVersion> @@ -107,7 +107,10 @@ <Compile Include="App.xaml.cs"> <DependentUpon>App.xaml</DependentUpon> </Compile> - <Compile Include="CubeLutParser.cs" /> + <Compile Include="Views\CameraContentPage.xaml.cs"> + <DependentUpon>CameraContentPage.xaml</DependentUpon> + </Compile> + <Compile Include="Views\Commands\FocusPosResetCommand.cs" /> <Compile Include="Views\Commands\ManualFocusAfCommand.cs" /> <Compile Include="Views\Commands\MfAssistPinpCommand.cs" /> <Compile Include="Views\Commands\TouchAfReleaseCommand.cs" /> @@ -131,12 +134,7 @@ <Compile Include="Views\Models\DebugCategoryEnable.cs" /> <Compile Include="Donations.cs" /> <Compile Include="GlobalSuppressions.cs" /> - <Compile Include="ILutParser.cs" /> - <Compile Include="Lut.cs" /> <Compile Include="Views\Commands\DeleteLutCommand.cs" /> - <Compile Include="Views\Misc\ILutEffectGenerator.cs" /> - <Compile Include="Views\Misc\Lut1DEffectGenerator.cs" /> - <Compile Include="Views\Misc\Lut3DEffectGenerator.cs" /> <Compile Include="Views\MainMenu.xaml.cs"> <DependentUpon>MainMenu.xaml</DependentUpon> </Compile> @@ -264,6 +262,10 @@ <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </ApplicationDefinition> + <Page Include="Views\CameraContentPage.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> <Page Include="Views\MainMenu.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> @@ -311,6 +313,10 @@ </SDKReference> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\CameraApi.Core\CameraApi.Core.csproj"> + <Project>{1c4d7c88-8f3b-4536-8d34-383d3cb96df2}</Project> + <Name>CameraApi.Core</Name> + </ProjectReference> <ProjectReference Include="..\Core\Core.csproj"> <Project>{6FA17036-459D-4967-A31B-8E8C7264E497}</Project> <Name>Core</Name> @@ -320,7 +326,9 @@ <Name>Tools</Name> </ProjectReference> </ItemGroup> - <ItemGroup /> + <ItemGroup> + <WCFMetadata Include="Connected Services\" /> + </ItemGroup> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' "> <VisualStudioVersion>14.0</VisualStudioVersion> </PropertyGroup> diff --git a/GMaster/Package.StoreAssociation.xml b/GMaster/Package.StoreAssociation.xml index 69e3473..e086f6a 100644 --- a/GMaster/Package.StoreAssociation.xml +++ b/GMaster/Package.StoreAssociation.xml @@ -361,6 +361,7 @@ <MainPackageIdentityName>51338Rambalac.GMaster</MainPackageIdentityName> <ReservedNames> <ReservedName>GMaster</ReservedName> + <ReservedName>LMaster</ReservedName> </ReservedNames> </ProductReservedInfo> <AccountPackageIdentityNames> diff --git a/GMaster/Package.appxmanifest b/GMaster/Package.appxmanifest index 0757bea..c165707 100644 --- a/GMaster/Package.appxmanifest +++ b/GMaster/Package.appxmanifest @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp"> - <Identity Name="51338Rambalac.GMaster" Publisher="CN=92765C0E-C69D-491B-9315-91859F5319B9" Version="1.8.4.0" /> - <mp:PhoneIdentity PhoneProductId="19ca5020-d17a-4012-ad2c-663f57d568c7" PhonePublisherId="00000000-0000-0000-0000-000000000000" /> + <Identity Name="51338Rambalac.GMaster" Publisher="CN=92765C0E-C69D-491B-9315-91859F5319B9" Version="1.8.6.0" /> + <mp:PhoneIdentity PhoneProductId="09757e39-4564-4c90-ac40-4b4846f3fc15" PhonePublisherId="00000000-0000-0000-0000-000000000000" /> <Properties> - <DisplayName>GMaster</DisplayName> + <DisplayName>LMaster</DisplayName> <PublisherDisplayName>Rambalac</PublisherDisplayName> <Logo>Assets\PackageLogo.png</Logo> </Properties> @@ -14,8 +14,8 @@ <Resource Language="x-generate" /> </Resources> <Applications> - <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="GMaster.App"> - <uap:VisualElements DisplayName="GMaster" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Control Panasonic Lumix camera remotly via WiFi" BackgroundColor="transparent"> + <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="LMaster.App"> + <uap:VisualElements DisplayName="LMaster" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Control Panasonic Lumix camera remotly via WiFi" BackgroundColor="transparent"> <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\Square71x71Logo.png" Square310x310Logo="Assets\Square310x310Logo.png"> </uap:DefaultTile> <uap:SplashScreen Image="Assets\SplashScreen.png" /> diff --git a/GMaster/Properties/AssemblyInfo.cs b/GMaster/Properties/AssemblyInfo.cs index fe9aa4d..22cf4e8 100644 --- a/GMaster/Properties/AssemblyInfo.cs +++ b/GMaster/Properties/AssemblyInfo.cs @@ -4,11 +4,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("GMaster")] +[assembly: AssemblyTitle("LMaster")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("GMaster")] +[assembly: AssemblyProduct("LMaster")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/GMaster/Views/Adjuster.xaml.cs b/GMaster/Views/Adjuster.xaml.cs index 21eeaf7..c5bdf63 100644 --- a/GMaster/Views/Adjuster.xaml.cs +++ b/GMaster/Views/Adjuster.xaml.cs @@ -3,7 +3,7 @@ namespace GMaster.Views { using System; - using Core.Camera.LumixData; + using Core.Camera.Panasonic.LumixData; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; diff --git a/GMaster/Views/CameraContentPage.xaml b/GMaster/Views/CameraContentPage.xaml new file mode 100644 index 0000000..39265f0 --- /dev/null +++ b/GMaster/Views/CameraContentPage.xaml @@ -0,0 +1,13 @@ +<Page + x:Class="LMaster.CameraContentPage" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="using:LMaster" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d"> + + <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + + </Grid> +</Page> diff --git a/GMaster/Views/CameraContentPage.xaml.cs b/GMaster/Views/CameraContentPage.xaml.cs new file mode 100644 index 0000000..2ec3857 --- /dev/null +++ b/GMaster/Views/CameraContentPage.xaml.cs @@ -0,0 +1,30 @@ +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace LMaster +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices.WindowsRuntime; + using Windows.Foundation; + using Windows.Foundation.Collections; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + using Windows.UI.Xaml.Controls.Primitives; + using Windows.UI.Xaml.Data; + using Windows.UI.Xaml.Input; + using Windows.UI.Xaml.Media; + using Windows.UI.Xaml.Navigation; + + /// <summary> + /// An empty page that can be used on its own or navigated to within a Frame. + /// </summary> + public sealed partial class CameraContentPage : Page + { + public CameraContentPage() + { + InitializeComponent(); + } + } +} diff --git a/GMaster/Views/CameraViewControl.xaml b/GMaster/Views/CameraViewControl.xaml index bc8e7ed..c30f147 100644 --- a/GMaster/Views/CameraViewControl.xaml +++ b/GMaster/Views/CameraViewControl.xaml @@ -99,9 +99,25 @@ <Image Source="{Binding AutoFocusMode, Converter={StaticResource AutofocusModeToIconPathConverter}, Mode=TwoWay}"/> </StackPanel> <Viewbox Margin="20,0,0,0"> - <TextBlock Width="120"> - <Run FontFamily="Segoe MDL2 Assets" Text="" - tools:StoryboardManager.Trigger="{Binding MemoryCardAccess}"> + <StackPanel Orientation="Horizontal" Width="120"> + <Grid Margin="2"> + <TextBlock FontFamily="Segoe MDL2 Assets" Text="" + tools:StoryboardManager.Trigger="{Binding MemoryCardAccess}"> + <tools:StoryboardManager.Storyboard> + <Storyboard RepeatBehavior="Forever" Duration="0:0:1"> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="{Binding}"> + <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Red"/> + <DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="White" /> + </ObjectAnimationUsingKeyFrames> + </Storyboard> + </tools:StoryboardManager.Storyboard> + </TextBlock> + <TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="21" Margin="-3" + Visibility="{Binding MemoryCardPresent, Mode=TwoWay, Converter={StaticResource FalseToVisibileConverter}}"/> + </Grid> + <TextBlock Margin="2" FontFamily="Segoe MDL2 Assets" Text="" + Visibility="{Binding MemoryCard2Present, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}" + tools:StoryboardManager.Trigger="{Binding MemoryCard2Access}"> <tools:StoryboardManager.Storyboard> <Storyboard RepeatBehavior="Forever" Duration="0:0:1"> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="{Binding}"> @@ -110,8 +126,8 @@ </ObjectAnimationUsingKeyFrames> </Storyboard> </tools:StoryboardManager.Storyboard> - </Run> - <Run Text="{Binding MemoryCardInfo, Mode=TwoWay}" + </TextBlock> + <TextBlock Text="{Binding MemoryCardInfo, Mode=TwoWay}" tools:StoryboardManager.Trigger="{Binding MemoryCardError}"> <tools:StoryboardManager.Storyboard> <Storyboard RepeatBehavior="Forever" Duration="0:0:1"> @@ -121,8 +137,8 @@ </ObjectAnimationUsingKeyFrames> </Storyboard> </tools:StoryboardManager.Storyboard> - </Run> - </TextBlock> + </TextBlock> + </StackPanel> </Viewbox> <Viewbox Margin="20,-15,0,-5"> <Viewbox.RenderTransform> @@ -141,6 +157,23 @@ </tools:StoryboardManager.Storyboard> </TextBlock> </Viewbox> + <Viewbox Margin="20,-15,0,-5" Visibility="{Binding GripBatteryPresent, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}"> + <Viewbox.RenderTransform> + <ScaleTransform ScaleX="-1" CenterX="20"/> + </Viewbox.RenderTransform> + <TextBlock Text="{Binding GripBatteryLevel, Converter={StaticResource BatteryToIconConverter}, Mode=TwoWay}" + FontFamily="Segoe MDL2 Assets" + tools:StoryboardManager.Trigger="{Binding GripBatteryCritical}"> + <tools:StoryboardManager.Storyboard> + <Storyboard RepeatBehavior="Forever" Duration="0:0:1"> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="{Binding}"> + <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Red"/> + <DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="White" /> + </ObjectAnimationUsingKeyFrames> + </Storyboard> + </tools:StoryboardManager.Storyboard> + </TextBlock> + </Viewbox> </StackPanel> </Grid> <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Height="70"> @@ -155,13 +188,21 @@ </Viewbox> </Button> <Button Padding="-15" - Visibility="{Binding CanManualFocusAf, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}" - Command="{StaticResource MfAssistPinpCommand}" - Style="{StaticResource TransparentButtonStyle}"> + Visibility="{Binding CanManualFocusAf, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}" + Command="{StaticResource MfAssistPinpCommand}" + Style="{StaticResource TransparentButtonStyle}"> <Viewbox> <TextBlock Padding="5" FontFamily="Segoe MDL2 Assets" Text="" /> </Viewbox> </Button> + <Button Padding="-5" Width="70" Height="70" + Visibility="{Binding CanResetTouchAf, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}" + Command="{StaticResource FocusPosResetCommand}" + Style="{StaticResource TransparentButtonStyle}"> + <Viewbox Stretch="Fill"> + <TextBlock Padding="5" Text="Reset" /> + </Viewbox> + </Button> <Button Padding="-15" Command="{StaticResource TouchAfReleaseCommand}" Visibility="{Binding CanReleaseTouchAf, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}" diff --git a/GMaster/Views/CameraViewControl.xaml.cs b/GMaster/Views/CameraViewControl.xaml.cs index 70f73a0..90772eb 100644 --- a/GMaster/Views/CameraViewControl.xaml.cs +++ b/GMaster/Views/CameraViewControl.xaml.cs @@ -7,7 +7,6 @@ namespace GMaster.Views using System.Linq; using System.Threading.Tasks; using Core.Camera; - using Core.Camera.LumixData; using Core.Tools; using Microsoft.Graphics.Canvas.UI.Xaml; using Models; @@ -29,7 +28,7 @@ public sealed partial class CameraViewControl : UserControl, IDisposable private readonly TimeSpan skipableInterval = TimeSpan.FromMilliseconds(200); private double aspect = 1; - private Lumix currentLumix; + private ICamera currentCamera; private bool is43; private ConnectedCamera lastSelectedCamera; private DateTime lastSkipable; @@ -66,23 +65,16 @@ public CameraViewControl() public CameraViewModel Model => DataContext as CameraViewModel; - private Lumix Lumix => Model?.SelectedCamera?.Camera; + private ICamera camera => Model?.SelectedCamera?.Camera; public void Dispose() { frame.Dispose(); } - private async Task<IntPoint?> Camera_LiveViewUpdated(ArraySegment<byte> segment) + private async void Camera_LiveViewUpdated(ArraySegment<byte> segment) { - var size = await frame.UpdateBitmap(new MemoryStream(segment.Array, segment.Offset, segment.Count)); - if (size != null) - { - var intaspect = size.Value.X * 10 / size.Value.Y; - is43 = intaspect == 13; - } - - return size; + await frame.UpdateBitmap(new MemoryStream(segment.Array, segment.Offset, segment.Count)); } private async Task CameraSet() @@ -149,7 +141,7 @@ private async void FocusAdjuster_OnRepeatClick(object sender, ChangeDirection ob private async void ImageGestureRecognizer_ManipulationCompleted(GestureRecognizer sender, ManipulationCompletedEventArgs args) { manipulationJustStarted = true; - if (Lumix == null) + if (camera == null) { return; } @@ -160,11 +152,11 @@ private async void ImageGestureRecognizer_ManipulationCompleted(GestureRecognize } else { - if (Lumix.LumixState.FocusMode == FocusMode.MF) + if (camera.State.FocusMode == FocusMode.MF) { if (args.PointerDeviceType != PointerDeviceType.Mouse) { - await Lumix.MfAssistZoom(PinchStage.Stop, new FloatPoint(args.Position.X, args.Position.Y), (200 + args.Cumulative.Expansion) / 1000); + await camera.MfAssistZoom(PinchStage.Stop, new FloatPoint(args.Position.X, args.Position.Y), (200 + args.Cumulative.Expansion) / 1000); } Debug.WriteLine("MF Pinch Zoom Stopped", "Manipulation"); @@ -174,7 +166,7 @@ private async void ImageGestureRecognizer_ManipulationCompleted(GestureRecognize private async void ImageGestureRecognizer_ManipulationUpdated(GestureRecognizer sender, ManipulationUpdatedEventArgs args) { - if (manipulating || Lumix == null) + if (manipulating || camera == null) { return; } @@ -242,7 +234,7 @@ private void LiveView_OnDraw(CanvasControl sender, CanvasDrawEventArgs args) { var drawaspect = aspect; if (Model.SelectedCamera.IsAspectAnamorphingVideoOnly && - !(Model.SelectedCamera.Camera.LumixState.IsVideoMode && is43)) + !(Model.SelectedCamera.Camera.State.IsVideoMode && is43)) { drawaspect = 1; } @@ -270,7 +262,7 @@ private async void Model_PropertyChanged(object sender, System.ComponentModel.Pr frame.Reset(); var newcamera = Model?.SelectedCamera?.Camera; - if (!ReferenceEquals(newcamera, currentLumix)) + if (!ReferenceEquals(newcamera, currentCamera)) { UpdateCamera(newcamera); } @@ -282,15 +274,15 @@ private async void Model_PropertyChanged(object sender, System.ComponentModel.Pr private async Task MoveFocusPoint(double x, double y, PinchStage stage) { var fp = ToFloatPoint(x, y); - if (fp.IsInRange(0f, 1f) && Lumix != null) + if (fp.IsInRange(0f, 1f) && camera != null) { - if (Lumix.LumixState.FocusMode != FocusMode.MF) + if (camera.State.FocusMode != FocusMode.MF) { - await Lumix.FocusPointMove(fp); + await camera.FocusPointMove(fp); } else { - await Lumix.MfAssistMove(stage, fp); + await camera.MfAssistMove(stage, fp); } } } @@ -298,17 +290,17 @@ private async Task MoveFocusPoint(double x, double y, PinchStage stage) private async Task PinchZoom(PinchStage stage, FloatPoint point, float extend) { extend = (200f + extend) / 1000f; - if (Lumix.LumixState.FocusMode != FocusMode.MF) + if (camera.State.FocusMode != FocusMode.MF) { - if (Lumix.LumixState.FocusAreas != null - && Lumix.LumixState.FocusAreas.Boxes.Any(b => b.Props.Type == FocusAreaType.OneAreaSelected)) + if (camera.State.FocusAreas != null + && camera.State.FocusAreas.Boxes.Any(b => b.Props.Type == FocusAreaType.OneAreaSelected || b.Props.Type == FocusAreaType.FaceOther)) { - await Lumix.FocusPointResize(stage, point, extend); + await camera.FocusPointResize(stage, point, extend); } } else { - await Lumix.MfAssistZoom(stage, point, extend); + await camera.MfAssistZoom(stage, point, extend); } } @@ -363,16 +355,16 @@ private FloatPoint ToFloatPoint(double x, double y) private void UpdateCamera(Lumix newcamera) { - if (currentLumix != null) + if (currentCamera != null) { - currentLumix.LiveViewUpdated -= Camera_LiveViewUpdated; + currentCamera.LiveViewUpdated -= Camera_LiveViewUpdated; } - currentLumix = newcamera; + currentCamera = newcamera; - if (currentLumix != null) + if (currentCamera != null) { - currentLumix.LiveViewUpdated += Camera_LiveViewUpdated; + currentCamera.LiveViewUpdated += Camera_LiveViewUpdated; } } diff --git a/GMaster/Views/CameraViewControlResources.xaml b/GMaster/Views/CameraViewControlResources.xaml index 00be154..52a176b 100644 --- a/GMaster/Views/CameraViewControlResources.xaml +++ b/GMaster/Views/CameraViewControlResources.xaml @@ -7,6 +7,7 @@ <commands:TouchAfReleaseCommand x:Key="TouchAfReleaseCommand" Model="{Binding}" /> <commands:ManualFocusAfCommand x:Key="ManualFocusAfCommand" Model="{Binding}" /> <commands:MfAssistPinpCommand x:Key="MfAssistPinpCommand" Model="{Binding}" /> + <commands:FocusPosResetCommand x:Key="FocusPosResetCommand" Model="{Binding}" /> <converters:RecStateToIconConvertor x:Key="RecStateConvertor" /> <converters:BatteryToIconConverter x:Key="BatteryToIconConverter"/> <converters:CameraModeToIconPathConverter x:Key="CameraModeToIconPathConverter"/> diff --git a/GMaster/Views/Commands/FocusPosResetCommand.cs b/GMaster/Views/Commands/FocusPosResetCommand.cs new file mode 100644 index 0000000..387647c --- /dev/null +++ b/GMaster/Views/Commands/FocusPosResetCommand.cs @@ -0,0 +1,42 @@ +namespace GMaster.Views.Commands +{ + using System; + using System.Threading.Tasks; + using Core.Camera; + using Core.Tools; + using Core.Camera.Panasonic; + using Core.Camera.Panasonic.LumixData; + using Models; + using Tools; + + public class FocusPosResetCommand : AbstractModelCommand<CameraViewModel> + { + protected override bool InternalCanExecute() => true; + + protected override async Task InternalExecute() + { + var lumix = Model.SelectedCamera; + if (lumix == null) + { + return; + } + + try + { + var floatPoint = new FloatPoint(0.5f, 0.5f); + if (lumix.Camera.LumixState.FocusMode == FocusMode.MF) + { + await lumix.Camera.MfAssistMove(PinchStage.Single, floatPoint); + } + else + { + await lumix.Camera.FocusPointMove(floatPoint); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } +} \ No newline at end of file diff --git a/GMaster/Views/Commands/ManualFocusAfCommand.cs b/GMaster/Views/Commands/ManualFocusAfCommand.cs index 4c0708a..a3bf063 100644 --- a/GMaster/Views/Commands/ManualFocusAfCommand.cs +++ b/GMaster/Views/Commands/ManualFocusAfCommand.cs @@ -2,8 +2,8 @@ namespace GMaster.Views.Commands { using System; using System.Threading.Tasks; - using Core.Camera.LumixData; using Core.Tools; + using Core.Camera.Panasonic.LumixData; using Models; using Tools; diff --git a/GMaster/Views/Commands/MfAssistPinpCommand.cs b/GMaster/Views/Commands/MfAssistPinpCommand.cs index e03eabc..4bc112e 100644 --- a/GMaster/Views/Commands/MfAssistPinpCommand.cs +++ b/GMaster/Views/Commands/MfAssistPinpCommand.cs @@ -2,8 +2,8 @@ namespace GMaster.Views.Commands { using System; using System.Threading.Tasks; - using Core.Camera.LumixData; using Core.Tools; + using Core.Camera.Panasonic.LumixData; using Models; using Tools; diff --git a/GMaster/Views/Commands/RecCommand.cs b/GMaster/Views/Commands/RecCommand.cs index fe77a40..61d6278 100644 --- a/GMaster/Views/Commands/RecCommand.cs +++ b/GMaster/Views/Commands/RecCommand.cs @@ -2,8 +2,8 @@ { using System; using System.Threading.Tasks; - using Core.Camera.LumixData; using Core.Tools; + using Core.Camera.Panasonic.LumixData; using Models; using Tools; diff --git a/GMaster/Views/Commands/TouchAfReleaseCommand.cs b/GMaster/Views/Commands/TouchAfReleaseCommand.cs index e9bcc31..931a110 100644 --- a/GMaster/Views/Commands/TouchAfReleaseCommand.cs +++ b/GMaster/Views/Commands/TouchAfReleaseCommand.cs @@ -2,8 +2,8 @@ namespace GMaster.Views.Commands { using System; using System.Threading.Tasks; - using Core.Camera.LumixData; using Core.Tools; + using Core.Camera.Panasonic.LumixData; using Models; using Tools; diff --git a/GMaster/Views/Converters/AutofocusModeToIconPathConverter.cs b/GMaster/Views/Converters/AutofocusModeToIconPathConverter.cs index 332b49a..c691105 100644 --- a/GMaster/Views/Converters/AutofocusModeToIconPathConverter.cs +++ b/GMaster/Views/Converters/AutofocusModeToIconPathConverter.cs @@ -2,7 +2,7 @@ namespace GMaster.Views.Converters { using System; using System.Collections.Generic; - using Core.Camera.LumixData; + using Core.Camera.Panasonic.LumixData; using Tools; public class AutofocusModeToIconPathConverter : DelegateConverter<AutoFocusMode, string> diff --git a/GMaster/Views/Converters/CameraModeToIconPathConverter.cs b/GMaster/Views/Converters/CameraModeToIconPathConverter.cs index b4d8153..877d35f 100644 --- a/GMaster/Views/Converters/CameraModeToIconPathConverter.cs +++ b/GMaster/Views/Converters/CameraModeToIconPathConverter.cs @@ -2,7 +2,7 @@ namespace GMaster.Views.Converters { using System; using System.Collections.Generic; - using Core.Camera.LumixData; + using Core.Camera.Panasonic.LumixData; using Tools; public class CameraModeToIconPathConverter : DelegateConverter<CameraMode, string> diff --git a/GMaster/Views/Converters/RecStateToIconConvertor.cs b/GMaster/Views/Converters/RecStateToIconConvertor.cs index 945c2f9..d952db5 100644 --- a/GMaster/Views/Converters/RecStateToIconConvertor.cs +++ b/GMaster/Views/Converters/RecStateToIconConvertor.cs @@ -1,7 +1,7 @@ namespace GMaster.Views.Converters { using System; - using Core.Camera.LumixData; + using Core.Camera.Panasonic.LumixData; using Tools; public class RecStateToIconConvertor : DelegateConverter<RecState, string> diff --git a/GMaster/Views/MainMenu.xaml b/GMaster/Views/MainMenu.xaml index a2cd36d..5cd934f 100644 --- a/GMaster/Views/MainMenu.xaml +++ b/GMaster/Views/MainMenu.xaml @@ -18,8 +18,14 @@ <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> - <Button Grid.Column="0" Click="CameraSettings_Click"> + <Button Grid.Column="0"> <SymbolIcon Symbol="Setting" /> + <Button.Flyout> + <MenuFlyout> + <MenuFlyoutItem Text="Settings" Click="CameraSettings_Click"/> + <MenuFlyoutItem Text="New Window" Click="NewWindowCam_Click"/> + </MenuFlyout> + </Button.Flyout> </Button> <ProgressBar IsIndeterminate="True" Grid.Column="1" VerticalAlignment="Top" Margin="0,10,0,0" Visibility="{Binding IsBusy, Mode=TwoWay, Converter={StaticResource TrueToVisibileConverter}}"/> <TextBlock Grid.Column="1" Text="{x:Bind Name}" VerticalAlignment="Center" TextAlignment="Left" Margin="5,0,5,0"/> @@ -83,7 +89,7 @@ <Image Source="/images/X4.png" Stretch="None"/> </Button> <Button Grid.Column="4" Click="NewWindow_OnClick" Margin="1"> - <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="30" Text="" HorizontalAlignment="Center" /> + <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="30" Text="" HorizontalAlignment="Center"/> </Button> <Button Grid.Column="5" IsEnabled="{Binding ConnectedCameras.Count, Mode=TwoWay, Converter={StaticResource GreaterToTrueConverter}, ConverterParameter=1}"> <Button.Flyout> diff --git a/GMaster/Views/MainMenu.xaml.cs b/GMaster/Views/MainMenu.xaml.cs index 3faded6..cbb9379 100644 --- a/GMaster/Views/MainMenu.xaml.cs +++ b/GMaster/Views/MainMenu.xaml.cs @@ -1,6 +1,7 @@ namespace GMaster.Views { using System; + using System.Threading.Tasks; using Models; using Windows.ApplicationModel.Core; using Windows.UI.Core; @@ -123,12 +124,24 @@ private void ListViewBase_OnDragItemsStarting(object sender, DragItemsStartingEv } private async void NewWindow_OnClick(object sender, RoutedEventArgs e) + { + await OpenNewWindow(null); + } + + private async void NewWindowCam_Click(object sender, RoutedEventArgs e) + { + await OpenNewWindow((ConnectedCamera)((FrameworkElement)sender).DataContext); + } + + private async Task OpenNewWindow(ConnectedCamera cam) { var newView = CoreApplication.CreateNewView(); var newViewId = 0; await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { - Window.Current.Content = new NewWindowsPage(); + var newpage = new NewWindowsPage(); + newpage.SelectCamera(cam); + Window.Current.Content = newpage; Window.Current.Activate(); newViewId = ApplicationView.GetForCurrentView().Id; diff --git a/GMaster/Views/Misc/FrameRenderer.cs b/GMaster/Views/Misc/FrameRenderer.cs index df2b92a..5c944f4 100644 --- a/GMaster/Views/Misc/FrameRenderer.cs +++ b/GMaster/Views/Misc/FrameRenderer.cs @@ -6,12 +6,14 @@ namespace GMaster.Views using System.Threading; using System.Threading.Tasks; using Core.Camera; - using Core.Camera.LumixData; using Core.Tools; + using Core.Camera.Panasonic; + using Core.Camera.Panasonic.LumixData; using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Geometry; using Microsoft.Graphics.Canvas.UI; using Microsoft.Graphics.Canvas.UI.Xaml; + using Tools; using Windows.Foundation; using Windows.UI; diff --git a/GMaster/Views/Misc/LutInfo.cs b/GMaster/Views/Misc/LutInfo.cs index e4c759f..683fe9b 100644 --- a/GMaster/Views/Misc/LutInfo.cs +++ b/GMaster/Views/Misc/LutInfo.cs @@ -6,6 +6,7 @@ namespace GMaster.Views using System.Threading.Tasks; using Core.Tools; using Newtonsoft.Json; + using Tools; using Windows.Storage; using Windows.Storage.Streams; diff --git a/GMaster/Views/Models/CameraViewModel.cs b/GMaster/Views/Models/CameraViewModel.cs index 21eaa81..a4e10c5 100644 --- a/GMaster/Views/Models/CameraViewModel.cs +++ b/GMaster/Views/Models/CameraViewModel.cs @@ -6,16 +6,14 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; - using Annotations; - using Core.Camera; - using Core.Camera.LumixData; using Core.Tools; using Windows.UI.Core; + using CameraApi.Core; public class CameraViewModel : INotifyPropertyChanged { private int lastFocusAreasCount; - private LumixState lumixState; + private ICameraState cameraState; private ConnectedCamera selectedCamera; public event PropertyChangedEventHandler PropertyChanged; @@ -24,101 +22,90 @@ public ICollection<string> Apertures { get { - if (lumixState?.LensInfo == null) - { - return new List<string>(); - } - - var openedAperture = lumixState.OpenedAperture; + var openedAperture = cameraState.OpenedAperture; return new[] { openedAperture } - .Concat(lumixState.MenuSet.Apertures. - Where(a => a.IntValue <= lumixState.LensInfo.ClosedAperture && + .Concat(cameraState.Apertures. + Where(a => a.IntValue <= cameraState.LensInfo.ClosedAperture && a.IntValue > openedAperture.IntValue)) .Select(a => a.Text).ToList(); } } - public AutoFocusMode AutoFocusMode => lumixState?.AutoFocusMode ?? AutoFocusMode.Unknown; + public AutoFocusMode AutoFocusMode => cameraState?.AutoFocusMode ?? AutoFocusMode.Unknown; - public float BatteryLevel - { - get - { - var numbers = lumixState?.State?.Battery?.Split('/'); - if (numbers?.Length != 2) - { - return 0; - } + public bool BatteryCritical => Math.Abs(BatteryLevel) < 0.01; - if (!float.TryParse(numbers[0], out var val1) - || !int.TryParse(numbers[1], out var val2) || val2 == 0) - { - return 0; - } + public bool GripBatteryCritical => Math.Abs(GripBatteryLevel) < 0.01; - return val1 / val2; - } - } + public bool GripBatteryPresent => GripBatteryLevel >= 0; + + public float BatteryLevel => GetBatteryLevel(cameraState?.State?.Battery); + + public float GripBatteryLevel => GetBatteryLevel(cameraState?.State?.GripBattery); - public bool CanCapture => lumixState?.CanCapture ?? false; + public CameraMode CameraMode => cameraState?.CameraMode ?? CameraMode.Unknown; - public bool CanChangeAperture => lumixState?.CanChangeAperture ?? true; + public bool CanCapture => cameraState?.CanCapture ?? false; - public bool CanChangeShutter => lumixState?.CanChangeShutter ?? true; + public bool CanChangeAperture => cameraState?.CanChangeAperture ?? true; - public object CanManualFocus => lumixState?.CanManualFocus ?? false; + public bool CanChangeShutter => cameraState?.CanChangeShutter ?? true; - public bool CanManualFocusAf => (lumixState?.FocusMode ?? FocusMode.Unknown) == FocusMode.MF && (selectedCamera?.Camera.Profile.ManualFocusAF ?? false); + public bool CanManualFocus => cameraState?.CanManualFocus ?? false; - public bool CanPowerZoom => lumixState?.LensInfo?.HasPowerZoom ?? false; + public bool CanManualFocusAf => (cameraState?.FocusMode ?? FocusMode.Unknown) == FocusMode.MF && (selectedCamera?.Camera.Profile.ManualFocusAF ?? false); + + public bool CanPowerZoom => cameraState?.LensInfo?.HasPowerZoom ?? false; public bool CanReleaseTouchAf => (AutoFocusMode.ToValue<AutoFocusModeFlags>().HasFlag(AutoFocusModeFlags.TouchAFRelease) && FocusAreas != null && FocusAreas.Boxes.Count > 0) - || lumixState?.CameraMode == CameraMode.MFAssist - || (FocusAreas?.Boxes.Any(b => b.Props.Type == FocusAreaType.MfAssistPinP - || b.Props.Type == FocusAreaType.MfAssistFullscreen) ?? false); + || cameraState?.CameraMode == CameraMode.MFAssist + || (FocusAreas?.Boxes.Any(b => b.Props.Type == FocusAreaType.MfAssistPinP || b.Props.Type == FocusAreaType.MfAssistFullscreen) ?? false); + + public bool CanResetTouchAf => (cameraState?.FocusMode ?? FocusMode.Unknown) == FocusMode.MF || + (FocusAreas?.Boxes.Any(b => b.Props.Type == FocusAreaType.OneAreaSelected || b.Props.Type == FocusAreaType.FaceOther) ?? false); - public int CurentZoom => lumixState?.Zoom ?? 0; + public int CurentZoom => cameraState?.Zoom ?? 0; public string CurrentAperture { - get => lumixState?.Aperture.Text; + get => cameraState?.Aperture.Text; set { - AsyncMenuItemSetter(lumixState.MenuSet.Apertures.SingleOrDefault(a => a.Text == value) ?? lumixState.OpenedAperture); + AsyncMenuItemSetter(cameraState.MenuSet.Apertures.SingleOrDefault(a => a.Text == value) ?? cameraState.OpenedAperture); } } - public int CurrentFocus => lumixState?.CurrentFocus ?? 0; + public int CurrentFocus => cameraState?.CurrentFocus ?? 0; public string CurrentIso { get { - if (lumixState?.Iso.Text == null) + if (cameraState?.Iso.Text == null) { return null; } - return lumixState.MenuSet.IsoValues.FirstOrDefault(i => i.Text.EndsWith(lumixState.Iso.Text, StringComparison.OrdinalIgnoreCase)).Text; + return cameraState.MenuSet.IsoValues.FirstOrDefault(i => i.Text.EndsWith(cameraState.Iso.Text, StringComparison.OrdinalIgnoreCase)).Text; } set { - AsyncMenuItemSetter(lumixState?.MenuSet?.IsoValues.SingleOrDefault(a => a.Text == value)); + AsyncMenuItemSetter(cameraState?.MenuSet?.IsoValues.SingleOrDefault(a => a.Text == value)); } } public string CurrentShutter { - get => lumixState?.Shutter.Text; + get => cameraState?.Shutter.Text; set { Debug.WriteLine("Shutter set to: " + value, "LumixState"); - AsyncMenuItemSetter(lumixState?.MenuSet?.ShutterSpeeds.SingleOrDefault(a => a.Text == value)); + AsyncMenuItemSetter(cameraState?.MenuSet?.ShutterSpeeds.SingleOrDefault(a => a.Text == value)); } } @@ -128,7 +115,7 @@ public FocusAreas FocusAreas { get { - var newval = lumixState?.FocusAreas; + var newval = cameraState?.FocusAreas; if (lastFocusAreasCount != (newval?.Boxes.Count ?? 0)) { lastFocusAreasCount = newval?.Boxes.Count ?? 0; @@ -139,73 +126,74 @@ public FocusAreas FocusAreas } } + public FocusMode FocusMode => cameraState?.FocusMode ?? FocusMode.Unknown; + public bool IsConnected => selectedCamera != null; - public bool IsConnectionActive => !(lumixState?.IsBusy ?? true); + public bool IsConnectionActive => !(cameraState?.IsBusy ?? true); public ICollection<string> IsoValues { get { - return lumixState?.MenuSet?.IsoValues - .Where(i => lumixState.CurMenu.Enabled.ContainsKey(i.Id)).Select(i => i.Text).ToList() + return cameraState?.MenuSet?.IsoValues + .Where(i => cameraState.CurMenu.Enabled.ContainsKey(i.Id)).Select(i => i.Text).ToList() ?? new List<string>(); } } - public CameraMode CameraMode => lumixState?.CameraMode ?? CameraMode.Unknown; + public int MaximumFocus => cameraState?.MaximumFocus ?? 0; - public int MaximumFocus => lumixState?.MaximumFocus ?? 0; + public int MaxZoom => cameraState?.LensInfo?.MaxZoom ?? 0; - public int MaxZoom => lumixState?.LensInfo?.MaxZoom ?? 0; + public bool MemoryCardAccess => cameraState?.State.SdAccess == OnOff.On; - public int MinZoom => lumixState?.LensInfo?.MinZoom ?? 0; + public bool MemoryCard2Access => cameraState?.State.Sd2Access == OnOff.On; - public RecState RecState => lumixState?.RecState ?? RecState.Stopped; + public bool MemoryCardError => cameraState != null && (cameraState.State.SdMemory == SdMemorySet.Unset + || cameraState.State.SdCardStatus != SdCardStatus.WriteEnable); - public bool MemoryCardAccess => lumixState?.State.SdAccess == OnOff.On; + public bool MemoryCard2Error => cameraState != null && (cameraState.State.Sd2Memory == SdMemorySet.Unset + || cameraState.State.Sd2CardStatus != SdCardStatus.WriteEnable); - public bool MemoryCardError - { - get - { - if (lumixState == null) - { - return false; - } + public bool MemoryCardPresent => (cameraState?.State?.SdMemory ?? SdMemorySet.Unset) == SdMemorySet.Set; - return lumixState.State.SdMemory == SdMemorySet.Unset || lumixState.State.SdCardStatus != SdCardStatus.WriteEnable; - } - } + public bool MemoryCard2Present => (cameraState?.State?.Sd2Memory ?? SdMemorySet.Unset) == SdMemorySet.Set; public string MemoryCardInfo { get { - if (lumixState?.State == null) + if (cameraState?.State == null) { return string.Empty; } - if (lumixState.State.SdMemory == SdMemorySet.Unset) + if (cameraState.State.SdMemory == SdMemorySet.Unset + && cameraState.State.Sd2Memory == SdMemorySet.Unset) { return "Not inserted"; } - if (lumixState.State.SdCardStatus != SdCardStatus.WriteEnable) + if (cameraState.State.SdCardStatus != SdCardStatus.WriteEnable + && cameraState.State.Sd2CardStatus != SdCardStatus.WriteEnable) { return "Read Only"; } - if (lumixState.State.RemainDisplayType == RemainDisplayType.Time) + if (cameraState.State.RemainDisplayType == RemainDisplayType.Time) { - return TimeSpan.FromSeconds(lumixState.State.VideoRemainCapacity).ToString("hh'h:'mm'm:'ss's'").TrimStart('0', 'h', 'm', ':'); + return TimeSpan.FromSeconds(cameraState.State.VideoRemainCapacity).ToString("hh'h:'mm'm:'ss's'").TrimStart('0', 'h', 'm', ':'); } - return lumixState.State.RemainCapacity.ToString(); + return cameraState.State.RemainCapacity.ToString(); } } + public int MinZoom => cameraState?.LensInfo?.MinZoom ?? 0; + + public RecState RecState => cameraState?.RecState ?? RecState.Stopped; + public ConnectedCamera SelectedCamera { get => selectedCamera; @@ -219,7 +207,7 @@ public ConnectedCamera SelectedCamera if (selectedCamera != null) { - lumixState.PropertyChanged -= Camera_PropertyChanged; + cameraState.PropertyChanged -= Camera_PropertyChanged; selectedCamera.Removed -= SelectedCamera_Removed; selectedCamera.Camera.ProfileUpdated -= Camera_ProfileUpdated; } @@ -230,8 +218,8 @@ public ConnectedCamera SelectedCamera { selectedCamera.Removed += SelectedCamera_Removed; selectedCamera.Camera.ProfileUpdated += Camera_ProfileUpdated; - lumixState = selectedCamera.Camera.LumixState; - lumixState.PropertyChanged += Camera_PropertyChanged; + cameraState = selectedCamera.Camera.LumixState; + cameraState.PropertyChanged += Camera_PropertyChanged; SetTime = DateTime.UtcNow; } else @@ -254,14 +242,10 @@ public ICollection<string> ShutterSpeeds { get { - return lumixState?.MenuSet?.ShutterSpeeds.Select(s => s.Text).ToList() ?? new List<string>(); + return cameraState?.MenuSet?.ShutterSpeeds.Select(s => s.Text).ToList() ?? new List<string>(); } } - public FocusMode FocusMode => lumixState?.FocusMode ?? FocusMode.Unknown; - - public bool BatteryCritical => BatteryLevel == 0; - [NotifyPropertyChangedInvocator] protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { @@ -303,6 +287,8 @@ await RunAsync(() => { case nameof(LumixState.FocusMode): OnPropertyChanged(nameof(CanManualFocusAf)); + OnPropertyChanged(nameof(CanResetTouchAf)); + OnPropertyChanged(nameof(CanReleaseTouchAf)); OnPropertyChanged(nameof(FocusMode)); break; @@ -374,11 +360,13 @@ await RunAsync(() => case nameof(LumixState.FocusAreas): OnPropertyChanged(nameof(FocusAreas)); OnPropertyChanged(nameof(CanReleaseTouchAf)); + OnPropertyChanged(nameof(CanResetTouchAf)); break; case nameof(LumixState.AutoFocusMode): OnPropertyChanged(nameof(AutoFocusMode)); OnPropertyChanged(nameof(CanReleaseTouchAf)); + OnPropertyChanged(nameof(CanResetTouchAf)); break; case nameof(LumixState.IsBusy): @@ -386,11 +374,19 @@ await RunAsync(() => break; case nameof(LumixState.State): - OnPropertyChanged(nameof(BatteryLevel)); OnPropertyChanged(nameof(BatteryCritical)); + OnPropertyChanged(nameof(BatteryLevel)); + OnPropertyChanged(nameof(GripBatteryLevel)); + OnPropertyChanged(nameof(GripBatteryCritical)); + OnPropertyChanged(nameof(GripBatteryPresent)); OnPropertyChanged(nameof(MemoryCardInfo)); OnPropertyChanged(nameof(MemoryCardAccess)); OnPropertyChanged(nameof(MemoryCardError)); + OnPropertyChanged(nameof(MemoryCardInfo)); + OnPropertyChanged(nameof(MemoryCard2Access)); + OnPropertyChanged(nameof(MemoryCard2Error)); + OnPropertyChanged(nameof(MemoryCardPresent)); + OnPropertyChanged(nameof(MemoryCard2Present)); break; } } @@ -401,6 +397,23 @@ await RunAsync(() => }); } + private float GetBatteryLevel(string value) + { + var numbers = value?.Split('/'); + if (numbers?.Length != 2) + { + return -1; + } + + if (!float.TryParse(numbers[0], out var val1) + || !int.TryParse(numbers[1], out var val2) || val2 == 0) + { + return -1; + } + + return val1 / val2; + } + private void RefreshAll() { try @@ -408,6 +421,7 @@ private void RefreshAll() OnPropertyChanged(nameof(IsConnected)); OnPropertyChanged(nameof(IsConnectionActive)); + OnPropertyChanged(nameof(CanResetTouchAf)); OnPropertyChanged(nameof(CanReleaseTouchAf)); OnPropertyChanged(nameof(CanManualFocusAf)); OnPropertyChanged(nameof(CanChangeAperture)); @@ -438,7 +452,14 @@ private void RefreshAll() OnPropertyChanged(nameof(FocusMode)); OnPropertyChanged(nameof(AutoFocusMode)); OnPropertyChanged(nameof(BatteryCritical)); - + OnPropertyChanged(nameof(BatteryLevel)); + OnPropertyChanged(nameof(GripBatteryLevel)); + OnPropertyChanged(nameof(GripBatteryCritical)); + OnPropertyChanged(nameof(GripBatteryPresent)); + OnPropertyChanged(nameof(MemoryCard2Access)); + OnPropertyChanged(nameof(MemoryCard2Error)); + OnPropertyChanged(nameof(MemoryCardPresent)); + OnPropertyChanged(nameof(MemoryCard2Present)); } catch (Exception ex) { diff --git a/GMaster/Views/Models/ConnectedCamera.cs b/GMaster/Views/Models/ConnectedCamera.cs index 4772131..aac1066 100644 --- a/GMaster/Views/Models/ConnectedCamera.cs +++ b/GMaster/Views/Models/ConnectedCamera.cs @@ -7,10 +7,11 @@ using System.Runtime.CompilerServices; using Annotations; using Core.Camera; + using Core.Camera.Panasonic; public class ConnectedCamera : INotifyPropertyChanged { - private Lumix camera; + private ICamera camera; private bool isAspectAnamorphingVideoOnly; private string selectedAspect; private LutInfo selectedLut; @@ -21,7 +22,7 @@ public class ConnectedCamera : INotifyPropertyChanged public string[] Aspects => new[] { "1", "1.33", "1.5", "1.75", "2" }; - public Lumix Camera + public ICamera Camera { get => camera; set @@ -32,7 +33,7 @@ public Lumix Camera } camera = value; - camera.LumixState.PropertyChanged += LumixState_PropertyChanged2; + camera.State.PropertyChanged += LumixState_PropertyChanged2; OnPropertyChanged(nameof(Camera)); } @@ -53,7 +54,7 @@ public bool IsAspectAnamorphingVideoOnly } } - public bool IsBusy => Camera.LumixState.IsBusy; + public bool IsBusy => Camera.State.IsBusy; public bool IsRemembered => Settings.GeneralSettings.Cameras.Contains(Settings); diff --git a/GMaster/Views/Models/ConnectionsManager.cs b/GMaster/Views/Models/ConnectionsManager.cs index 5932a22..1dba6f0 100644 --- a/GMaster/Views/Models/ConnectionsManager.cs +++ b/GMaster/Views/Models/ConnectionsManager.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Core.Camera; using Core.Tools; + using Core.Camera.Panasonic; using Windows.UI.Xaml; public class ConnectionsManager @@ -41,7 +42,7 @@ public async Task ManuallyDisconnect(ConnectedCamera connected) var camera = connected.Camera; camera.StateUpdateFailed -= Lumix_StateUpdateFailed; camera.ActionCalled -= model.LumixActionCalled; - await camera.StopStream(); + await camera.StopLiveview(); camera.Disconnect(); camera.Dispose(); } diff --git a/GMaster/Views/Models/FocusArea.cs b/GMaster/Views/Models/FocusArea.cs index 10b566e..2247d72 100644 --- a/GMaster/Views/Models/FocusArea.cs +++ b/GMaster/Views/Models/FocusArea.cs @@ -4,6 +4,7 @@ namespace GMaster.Views.Models using System.Runtime.CompilerServices; using Annotations; using Core.Camera; + using Core.Camera.Panasonic; using Windows.Foundation; public class FocusArea : INotifyPropertyChanged diff --git a/GMaster/Views/Models/MainPageModel.cs b/GMaster/Views/Models/MainPageModel.cs index bd2de26..3c5addb 100644 --- a/GMaster/Views/Models/MainPageModel.cs +++ b/GMaster/Views/Models/MainPageModel.cs @@ -12,6 +12,7 @@ using Annotations; using Core.Camera; using Core.Tools; + using Core.Camera.Panasonic; using Windows.ApplicationModel; using Windows.Devices.WiFi; using Windows.UI.Core; diff --git a/GMaster/Views/NewWindowsPage.xaml b/GMaster/Views/NewWindowsPage.xaml index 05fee7e..c3c63b4 100644 --- a/GMaster/Views/NewWindowsPage.xaml +++ b/GMaster/Views/NewWindowsPage.xaml @@ -8,7 +8,7 @@ xmlns:models="using:GMaster.Views.Models" mc:Ignorable="d" RequestedTheme="Dark" > <Grid> - <local:CameraViewControl> + <local:CameraViewControl x:Name="CameraView"> <local:CameraViewControl.DataContext> <models:CameraViewModel/> </local:CameraViewControl.DataContext> diff --git a/GMaster/Views/NewWindowsPage.xaml.cs b/GMaster/Views/NewWindowsPage.xaml.cs index 193cd25..8c71c00 100644 --- a/GMaster/Views/NewWindowsPage.xaml.cs +++ b/GMaster/Views/NewWindowsPage.xaml.cs @@ -2,6 +2,7 @@ namespace GMaster.Views { + using Models; using Windows.UI.Xaml.Controls; /// <summary> @@ -13,5 +14,10 @@ public NewWindowsPage() { InitializeComponent(); } + + public void SelectCamera(ConnectedCamera cam) + { + CameraView.Model.SelectedCamera = cam; + } } } diff --git a/GMaster/WiFiHelper.cs b/GMaster/WiFiHelper.cs index 9439f10..ab91020 100644 --- a/GMaster/WiFiHelper.cs +++ b/GMaster/WiFiHelper.cs @@ -8,8 +8,8 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Annotations; using Core.Tools; + using JetBrains.Annotations; using Windows.Devices.WiFi; using Windows.Security.Credentials; using Windows.UI.Xaml; diff --git a/GMaster/_pkginfo.txt b/GMaster/_pkginfo.txt index fa4a8d3..319af35 100644 --- a/GMaster/_pkginfo.txt +++ b/GMaster/_pkginfo.txt @@ -1 +1 @@ -E:\Projects\GMaster\GMaster\bin\ARM\Release\Upload\GMaster_1.8.4.0\GMaster_1.8.4.0_x86_x64_arm.appxbundle +E:\Projects\GMaster\GMaster\bin\ARM\Release\Upload\GMaster_1.8.6.0\GMaster_1.8.6.0_x86_x64_arm.appxbundle diff --git a/GMaster/project.json b/GMaster/project.json index e0db0a9..1ed7a6c 100644 --- a/GMaster/project.json +++ b/GMaster/project.json @@ -1,16 +1,16 @@ { "dependencies": { - "Microsoft.NETCore.UniversalWindowsPlatform": "5.3.3", - "Microsoft.Services.Store.SDK": "10.1703.19001", - "Newtonsoft.Json": "10.0.2", + "JetBrains.Annotations": "11.0.0", + "Microsoft.NETCore.UniversalWindowsPlatform": "5.4.0", + "Microsoft.Services.Store.SDK": "10.1705.16001", + "Newtonsoft.Json": "10.0.3", "Nito.AsyncEx.Coordination": "1.0.2", - "Rssdp": "3.0.1", - "StyleCop.Analyzers": "1.0.0", - "System.ValueTuple": "4.3.0", - "Win2D.uwp": "1.20.0" + "StyleCop.Analyzers": "1.0.2", + "System.ValueTuple": "4.4.0", + "Win2D.uwp": "1.21.0" }, "frameworks": { - "uap10.0": {} + "uap10.0.10240": {} }, "runtimes": { "win10-arm": {}, diff --git a/GMaster/CubeLutParser.cs b/Tools/CubeLutParser.cs similarity index 98% rename from GMaster/CubeLutParser.cs rename to Tools/CubeLutParser.cs index cdaf662..653e1b5 100644 --- a/GMaster/CubeLutParser.cs +++ b/Tools/CubeLutParser.cs @@ -1,4 +1,4 @@ -namespace GMaster +namespace GMaster.Tools { using System; using System.IO; diff --git a/GMaster/Views/Misc/ILutEffectGenerator.cs b/Tools/ILutEffectGenerator.cs similarity index 92% rename from GMaster/Views/Misc/ILutEffectGenerator.cs rename to Tools/ILutEffectGenerator.cs index 3af40b1..0ceac0d 100644 --- a/GMaster/Views/Misc/ILutEffectGenerator.cs +++ b/Tools/ILutEffectGenerator.cs @@ -1,6 +1,6 @@ // The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236 -namespace GMaster.Views +namespace GMaster.Tools { using Microsoft.Graphics.Canvas.Effects; using Windows.Graphics.Effects; diff --git a/GMaster/ILutParser.cs b/Tools/ILutParser.cs similarity index 84% rename from GMaster/ILutParser.cs rename to Tools/ILutParser.cs index dd2ba20..73176e0 100644 --- a/GMaster/ILutParser.cs +++ b/Tools/ILutParser.cs @@ -1,4 +1,4 @@ -namespace GMaster +namespace GMaster.Tools { using System.IO; using System.Threading.Tasks; diff --git a/GMaster/Lut.cs b/Tools/Lut.cs similarity index 93% rename from GMaster/Lut.cs rename to Tools/Lut.cs index ce86ba7..06ca66e 100644 --- a/GMaster/Lut.cs +++ b/Tools/Lut.cs @@ -1,7 +1,6 @@ -namespace GMaster +namespace GMaster.Tools { using Microsoft.Graphics.Canvas; - using Views; using Windows.UI; public class Lut diff --git a/GMaster/Views/Misc/Lut1DEffectGenerator.cs b/Tools/Lut1DEffectGenerator.cs similarity index 96% rename from GMaster/Views/Misc/Lut1DEffectGenerator.cs rename to Tools/Lut1DEffectGenerator.cs index 957ea27..32dbfbc 100644 --- a/GMaster/Views/Misc/Lut1DEffectGenerator.cs +++ b/Tools/Lut1DEffectGenerator.cs @@ -1,4 +1,4 @@ -namespace GMaster.Views +namespace GMaster.Tools { using System.Linq; using Microsoft.Graphics.Canvas.Effects; diff --git a/GMaster/Views/Misc/Lut3DEffectGenerator.cs b/Tools/Lut3DEffectGenerator.cs similarity index 96% rename from GMaster/Views/Misc/Lut3DEffectGenerator.cs rename to Tools/Lut3DEffectGenerator.cs index f871f4e..06dd03f 100644 --- a/GMaster/Views/Misc/Lut3DEffectGenerator.cs +++ b/Tools/Lut3DEffectGenerator.cs @@ -1,6 +1,6 @@ // The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236 -namespace GMaster.Views +namespace GMaster.Tools { using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Effects; diff --git a/Tools/Properties/Annotations.cs b/Tools/Properties/Annotations.cs deleted file mode 100644 index ccb5110..0000000 --- a/Tools/Properties/Annotations.cs +++ /dev/null @@ -1,1079 +0,0 @@ -/* MIT License - -Copyright (c) 2016 JetBrains http://www.jetbrains.com - -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. */ - -#pragma warning disable - -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable IntroduceOptionalParameters.Global -// ReSharper disable MemberCanBeProtected.Global -// ReSharper disable InconsistentNaming -namespace Tools.Annotations -{ - using System; - - /// <summary> - /// Indicates that the value of the marked element could be <c>null</c> sometimes, - /// so the check for <c>null</c> is necessary before its usage. - /// </summary> - /// <example><code> - /// [CanBeNull] object Test() => null; - /// - /// void UseTest() { - /// var p = Test(); - /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' - /// } - /// </code></example> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] - public sealed class CanBeNullAttribute : Attribute { } - - /// <summary> - /// Indicates that the value of the marked element could never be <c>null</c>. - /// </summary> - /// <example><code> - /// [NotNull] object Foo() { - /// return null; // Warning: Possible 'null' assignment - /// } - /// </code></example> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] - public sealed class NotNullAttribute : Attribute { } - - /// <summary> - /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can never be null. - /// </summary> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - public sealed class ItemNotNullAttribute : Attribute { } - - /// <summary> - /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can be null. - /// </summary> - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - public sealed class ItemCanBeNullAttribute : Attribute { } - - /// <summary> - /// Indicates that the marked method builds string by format pattern and (optional) arguments. - /// Parameter, which contains format string, should be given in constructor. The format string - /// should be in <see cref="string.Format(IFormatProvider,string,object[])"/>-like form. - /// </summary> - /// <example><code> - /// [StringFormatMethod("message")] - /// void ShowError(string message, params object[] args) { /* do something */ } - /// - /// void Foo() { - /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string - /// } - /// </code></example> - [AttributeUsage( - AttributeTargets.Constructor | AttributeTargets.Method | - AttributeTargets.Property | AttributeTargets.Delegate)] - public sealed class StringFormatMethodAttribute : Attribute - { - /// <param name="formatParameterName"> - /// Specifies which parameter of an annotated method should be treated as format-string - /// </param> - public StringFormatMethodAttribute([NotNull] string formatParameterName) - { - FormatParameterName = formatParameterName; - } - - [NotNull] public string FormatParameterName { get; } - } - - /// <summary> - /// For a parameter that is expected to be one of the limited set of values. - /// Specify fields of which type should be used as values for this parameter. - /// </summary> - [AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, - AllowMultiple = true)] - public sealed class ValueProviderAttribute : Attribute - { - public ValueProviderAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] public string Name { get; } - } - - /// <summary> - /// Indicates that the function argument should be string literal and match one - /// of the parameters of the caller function. For example, ReSharper annotates - /// the parameter of <see cref="System.ArgumentNullException"/>. - /// </summary> - /// <example><code> - /// void Foo(string param) { - /// if (param == null) - /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol - /// } - /// </code></example> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class InvokerParameterNameAttribute : Attribute { } - - /// <summary> - /// Indicates that the method is contained in a type that implements - /// <c>System.ComponentModel.INotifyPropertyChanged</c> interface and this method - /// is used to notify that some property value changed. - /// </summary> - /// <remarks> - /// The method should be non-static and conform to one of the supported signatures: - /// <list> - /// <item><c>NotifyChanged(string)</c></item> - /// <item><c>NotifyChanged(params string[])</c></item> - /// <item><c>NotifyChanged{T}(Expression{Func{T}})</c></item> - /// <item><c>NotifyChanged{T,U}(Expression{Func{T,U}})</c></item> - /// <item><c>SetProperty{T}(ref T, T, string)</c></item> - /// </list> - /// </remarks> - /// <example><code> - /// public class Foo : INotifyPropertyChanged { - /// public event PropertyChangedEventHandler PropertyChanged; - /// - /// [NotifyPropertyChangedInvocator] - /// protected virtual void NotifyChanged(string propertyName) { ... } - /// - /// string _name; - /// - /// public string Name { - /// get { return _name; } - /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } - /// } - /// } - /// </code> - /// Examples of generated notifications: - /// <list> - /// <item><c>NotifyChanged("Property")</c></item> - /// <item><c>NotifyChanged(() => Property)</c></item> - /// <item><c>NotifyChanged((VM x) => x.Property)</c></item> - /// <item><c>SetProperty(ref myField, value, "Property")</c></item> - /// </list> - /// </example> - [AttributeUsage(AttributeTargets.Method)] - public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute - { - public NotifyPropertyChangedInvocatorAttribute() - { - } - - public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) - { - ParameterName = parameterName; - } - - [CanBeNull] public string ParameterName { get; } - } - - /// <summary> - /// Describes dependency between method input and output. - /// </summary> - /// <syntax> - /// <p>Function Definition Table syntax:</p> - /// <list> - /// <item>FDT ::= FDTRow [;FDTRow]*</item> - /// <item>FDTRow ::= Input => Output | Output <= Input</item> - /// <item>Input ::= ParameterName: Value [, Input]*</item> - /// <item>Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item> - /// <item>Value ::= true | false | null | notnull | canbenull</item> - /// </list> - /// If method has single input parameter, it's name could be omitted.<br/> - /// Using <c>halt</c> (or <c>void</c>/<c>nothing</c>, which is the same) for method output - /// means that the methos doesn't return normally (throws or terminates the process).<br/> - /// Value <c>canbenull</c> is only applicable for output parameters.<br/> - /// You can use multiple <c>[ContractAnnotation]</c> for each FDT row, or use single attribute - /// with rows separated by semicolon. There is no notion of order rows, all rows are checked - /// for applicability and applied per each program state tracked by R# analysis.<br/> - /// </syntax> - /// <examples><list> - /// <item><code> - /// [ContractAnnotation("=> halt")] - /// public void TerminationMethod() - /// </code></item> - /// <item><code> - /// [ContractAnnotation("halt <= condition: false")] - /// public void Assert(bool condition, string text) // regular assertion method - /// </code></item> - /// <item><code> - /// [ContractAnnotation("s:null => true")] - /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() - /// </code></item> - /// <item><code> - /// // A method that returns null if the parameter is null, - /// // and not null if the parameter is not null - /// [ContractAnnotation("null => null; notnull => notnull")] - /// public object Transform(object data) - /// </code></item> - /// <item><code> - /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] - /// public bool TryParse(string s, out Person result) - /// </code></item> - /// </list></examples> - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class ContractAnnotationAttribute : Attribute - { - public ContractAnnotationAttribute([NotNull] string contract) - : this(contract, false) { } - - public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) - { - Contract = contract; - ForceFullStates = forceFullStates; - } - - [NotNull] public string Contract { get; } - - public bool ForceFullStates { get; } - } - - /// <summary> - /// Indicates that marked element should be localized or not. - /// </summary> - /// <example><code> - /// [LocalizationRequiredAttribute(true)] - /// class Foo { - /// string str = "my string"; // Warning: Localizable string - /// } - /// </code></example> - [AttributeUsage(AttributeTargets.All)] - public sealed class LocalizationRequiredAttribute : Attribute - { - public LocalizationRequiredAttribute() : this(true) - { - } - - public LocalizationRequiredAttribute(bool required) - { - Required = required; - } - - public bool Required { get; } - } - - /// <summary> - /// Indicates that the value of the marked type (or its derivatives) - /// cannot be compared using '==' or '!=' operators and <c>Equals()</c> - /// should be used instead. However, using '==' or '!=' for comparison - /// with <c>null</c> is always permitted. - /// </summary> - /// <example><code> - /// [CannotApplyEqualityOperator] - /// class NoEquality { } - /// - /// class UsesNoEquality { - /// void Test() { - /// var ca1 = new NoEquality(); - /// var ca2 = new NoEquality(); - /// if (ca1 != null) { // OK - /// bool condition = ca1 == ca2; // Warning - /// } - /// } - /// } - /// </code></example> - [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] - public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } - - /// <summary> - /// When applied to a target attribute, specifies a requirement for any type marked - /// with the target attribute to implement or inherit specific type or types. - /// </summary> - /// <example><code> - /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement - /// class ComponentAttribute : Attribute { } - /// - /// [Component] // ComponentAttribute requires implementing IComponent interface - /// class MyComponent : IComponent { } - /// </code></example> - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - [BaseTypeRequired(typeof(Attribute))] - public sealed class BaseTypeRequiredAttribute : Attribute - { - public BaseTypeRequiredAttribute([NotNull] Type baseType) - { - BaseType = baseType; - } - - [NotNull] public Type BaseType { get; } - } - - /// <summary> - /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), - /// so this symbol will not be marked as unused (as well as by other usage inspections). - /// </summary> - [AttributeUsage(AttributeTargets.All)] - public sealed class UsedImplicitlyAttribute : Attribute - { - public UsedImplicitlyAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) { } - - public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) { } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - public ImplicitUseKindFlags UseKindFlags { get; } - - public ImplicitUseTargetFlags TargetFlags { get; } - } - - /// <summary> - /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes - /// as unused (as well as by other usage inspections) - /// </summary> - [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] - public sealed class MeansImplicitUseAttribute : Attribute - { - public MeansImplicitUseAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } - - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) { } - - public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) { } - - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } - - [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } - } - - [Flags] - public enum ImplicitUseKindFlags - { - Default = Access | Assign | InstantiatedWithFixedConstructorSignature, - - /// <summary>Only entity marked with attribute considered used.</summary> - Access = 1, - - /// <summary>Indicates implicit assignment to a member.</summary> - Assign = 2, - - /// <summary> - /// Indicates implicit instantiation of a type with fixed constructor signature. - /// That means any unused constructor parameters won't be reported as such. - /// </summary> - InstantiatedWithFixedConstructorSignature = 4, - - /// <summary>Indicates implicit instantiation of a type.</summary> - InstantiatedNoFixedConstructorSignature = 8, - } - - /// <summary> - /// Specify what is considered used implicitly when marked - /// with <see cref="MeansImplicitUseAttribute"/> or <see cref="UsedImplicitlyAttribute"/>. - /// </summary> - [Flags] - public enum ImplicitUseTargetFlags - { - Default = Itself, - Itself = 1, - - /// <summary>Members of entity marked with attribute are considered used.</summary> - Members = 2, - - /// <summary>Entity marked with attribute and all its members considered used.</summary> - WithMembers = Itself | Members - } - - /// <summary> - /// This attribute is intended to mark publicly available API - /// which should not be removed and so is treated as used. - /// </summary> - [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] - public sealed class PublicAPIAttribute : Attribute - { - public PublicAPIAttribute() - { - } - - public PublicAPIAttribute([NotNull] string comment) - { - Comment = comment; - } - - [CanBeNull] public string Comment { get; } - } - - /// <summary> - /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. - /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. - /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class InstantHandleAttribute : Attribute { } - - /// <summary> - /// Indicates that a method does not make any observable state changes. - /// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>. - /// </summary> - /// <example><code> - /// [Pure] int Multiply(int x, int y) => x * y; - /// - /// void M() { - /// Multiply(123, 42); // Waring: Return value of pure method is not used - /// } - /// </code></example> - [AttributeUsage(AttributeTargets.Method)] - public sealed class PureAttribute : Attribute { } - - /// <summary> - /// Indicates that the return value of method invocation must be used. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - public sealed class MustUseReturnValueAttribute : Attribute - { - public MustUseReturnValueAttribute() - { - } - - public MustUseReturnValueAttribute([NotNull] string justification) - { - Justification = justification; - } - - [CanBeNull] public string Justification { get; } - } - - /// <summary> - /// Indicates the type member or parameter of some type, that should be used instead of all other ways - /// to get the value that type. This annotation is useful when you have some "context" value evaluated - /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. - /// </summary> - /// <example><code> - /// class Foo { - /// [ProvidesContext] IBarService _barService = ...; - /// - /// void ProcessNode(INode node) { - /// DoSomething(node, node.GetGlobalServices().Bar); - /// // ^ Warning: use value of '_barService' field - /// } - /// } - /// </code></example> - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] - public sealed class ProvidesContextAttribute : Attribute { } - - /// <summary> - /// Indicates that a parameter is a path to a file or a folder within a web project. - /// Path can be relative or absolute, starting from web root (~). - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class PathReferenceAttribute : Attribute - { - public PathReferenceAttribute() - { - } - - public PathReferenceAttribute([NotNull, PathReference] string basePath) - { - BasePath = basePath; - } - - [CanBeNull] public string BasePath { get; } - } - - /// <summary> - /// An extension method marked with this attribute is processed by ReSharper code completion - /// as a 'Source Template'. When extension method is completed over some expression, it's source code - /// is automatically expanded like a template at call site. - /// </summary> - /// <remarks> - /// Template method body can contain valid source code and/or special comments starting with '$'. - /// Text inside these comments is added as source code when the template is applied. Template parameters - /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. - /// Use the <see cref="MacroAttribute"/> attribute to specify macros for parameters. - /// </remarks> - /// <example> - /// In this example, the 'forEach' method is a source template available over all values - /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: - /// <code> - /// [SourceTemplate] - /// public static void forEach<T>(this IEnumerable<T> xs) { - /// foreach (var x in xs) { - /// //$ $END$ - /// } - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Method)] - public sealed class SourceTemplateAttribute : Attribute { } - - /// <summary> - /// Allows specifying a macro for a parameter of a <see cref="SourceTemplateAttribute">source template</see>. - /// </summary> - /// <remarks> - /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression - /// is defined in the <see cref="MacroAttribute.Expression"/> property. When applied on a method, the target - /// template parameter is defined in the <see cref="MacroAttribute.Target"/> property. To apply the macro silently - /// for the parameter, set the <see cref="MacroAttribute.Editable"/> property value = -1. - /// </remarks> - /// <example> - /// Applying the attribute on a source template method: - /// <code> - /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] - /// public static void forEach<T>(this IEnumerable<T> collection) { - /// foreach (var item in collection) { - /// //$ $END$ - /// } - /// } - /// </code> - /// Applying the attribute on a template method parameter: - /// <code> - /// [SourceTemplate] - /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { - /// /*$ var $x$Id = "$newguid$" + x.ToString(); - /// x.DoSomething($x$Id); */ - /// } - /// </code> - /// </example> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] - public sealed class MacroAttribute : Attribute - { - /// <summary> - /// Allows specifying a macro that will be executed for a <see cref="SourceTemplateAttribute">source template</see> - /// parameter when the template is expanded. - /// </summary> - [CanBeNull] public string Expression { get; set; } - - /// <summary> - /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. - /// </summary> - /// <remarks> - /// If the target parameter is used several times in the template, only one occurrence becomes editable; - /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, - /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. - /// </remarks>> - public int Editable { get; set; } - - /// <summary> - /// Identifies the target parameter of a <see cref="SourceTemplateAttribute">source template</see> if the - /// <see cref="MacroAttribute"/> is applied on a template method. - /// </summary> - [CanBeNull] public string Target { get; set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute - { - public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute - { - public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute - { - public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcMasterLocationFormatAttribute : Attribute - { - public AspMvcMasterLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute - { - public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] public string Format { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class AspMvcViewLocationFormatAttribute : Attribute - { - public AspMvcViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] public string Format { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC action. If applied to a method, the MVC action name is calculated - /// implicitly from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcActionAttribute : Attribute - { - public AspMvcActionAttribute() - { - } - - public AspMvcActionAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] public string AnonymousProperty { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcAreaAttribute : Attribute - { - public AspMvcAreaAttribute() - { - } - - public AspMvcAreaAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] public string AnonymousProperty { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is - /// an MVC controller. If applied to a method, the MVC controller name is calculated - /// implicitly from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcControllerAttribute : Attribute - { - public AspMvcControllerAttribute() - { - } - - public AspMvcControllerAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] public string AnonymousProperty { get; } - } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute - /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcMasterAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute - /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, Object)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcModelTypeAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC - /// partial view. If applied to a method, the MVC partial view name is calculated implicitly - /// from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcPartialViewAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. - /// </summary> - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcDisplayTemplateAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcEditorTemplateAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. - /// Use this attribute for custom wrappers similar to - /// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcTemplateAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly - /// from the context. Use this attribute for custom wrappers similar to - /// <c>System.Web.Mvc.Controller.View(Object)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcViewAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component name. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AspMvcViewComponentAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component view. If applied to a method, the MVC view component view name is default. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class AspMvcViewComponentViewAttribute : Attribute { } - - /// <summary> - /// ASP.NET MVC attribute. When applied to a parameter of an attribute, - /// indicates that this parameter is an MVC action name. - /// </summary> - /// <example><code> - /// [ActionName("Foo")] - /// public ActionResult Login(string returnUrl) { - /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK - /// return RedirectToAction("Bar"); // Error: Cannot resolve action - /// } - /// </code></example> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] - public sealed class AspMvcActionSelectorAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] - public sealed class HtmlElementAttributesAttribute : Attribute - { - public HtmlElementAttributesAttribute() - { - } - - public HtmlElementAttributesAttribute([NotNull] string name) - { - Name = name; - } - - [CanBeNull] public string Name { get; } - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] - public sealed class HtmlAttributeValueAttribute : Attribute - { - public HtmlAttributeValueAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] public string Name { get; } - } - - /// <summary> - /// Razor attribute. Indicates that a parameter or a method is a Razor section. - /// Use this attribute for custom wrappers similar to - /// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - public sealed class RazorSectionAttribute : Attribute { } - - /// <summary> - /// Indicates how method, constructor invocation or property access - /// over collection type affects content of the collection. - /// </summary> - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] - public sealed class CollectionAccessAttribute : Attribute - { - public CollectionAccessAttribute(CollectionAccessType collectionAccessType) - { - CollectionAccessType = collectionAccessType; - } - - public CollectionAccessType CollectionAccessType { get; } - } - - [Flags] - public enum CollectionAccessType - { - /// <summary>Method does not use or modify content of the collection.</summary> - None = 0, - - /// <summary>Method only reads content of the collection but does not modify it.</summary> - Read = 1, - - /// <summary>Method can change content of the collection but does not add new elements.</summary> - ModifyExistingContent = 2, - - /// <summary>Method can add new elements to the collection.</summary> - UpdatedContent = ModifyExistingContent | 4 - } - - /// <summary> - /// Indicates that the marked method is assertion method, i.e. it halts control flow if - /// one of the conditions is satisfied. To set the condition, mark one of the parameters with - /// <see cref="AssertionConditionAttribute"/> attribute. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - public sealed class AssertionMethodAttribute : Attribute { } - - /// <summary> - /// Indicates the condition parameter of the assertion method. The method itself should be - /// marked by <see cref="AssertionMethodAttribute"/> attribute. The mandatory argument of - /// the attribute is the assertion type. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AssertionConditionAttribute : Attribute - { - public AssertionConditionAttribute(AssertionConditionType conditionType) - { - ConditionType = conditionType; - } - - public AssertionConditionType ConditionType { get; } - } - - /// <summary> - /// Specifies assertion type. If the assertion method argument satisfies the condition, - /// then the execution continues. Otherwise, execution is assumed to be halted. - /// </summary> - public enum AssertionConditionType - { - /// <summary>Marked parameter should be evaluated to true.</summary> - IS_TRUE = 0, - - /// <summary>Marked parameter should be evaluated to false.</summary> - IS_FALSE = 1, - - /// <summary>Marked parameter should be evaluated to null value.</summary> - IS_NULL = 2, - - /// <summary>Marked parameter should be evaluated to not null value.</summary> - IS_NOT_NULL = 3, - } - - /// <summary> - /// Indicates that the marked method unconditionally terminates control flow execution. - /// For example, it could unconditionally throw exception. - /// </summary> - [Obsolete("Use [ContractAnnotation('=> halt')] instead")] - [AttributeUsage(AttributeTargets.Method)] - public sealed class TerminatesProgramAttribute : Attribute { } - - /// <summary> - /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, - /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters - /// of delegate type by analyzing LINQ method chains. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - public sealed class LinqTunnelAttribute : Attribute { } - - /// <summary> - /// Indicates that IEnumerable, passed as parameter, is not enumerated. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class NoEnumerationAttribute : Attribute { } - - /// <summary> - /// Indicates that parameter is regular expression pattern. - /// </summary> - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class RegexPatternAttribute : Attribute { } - - /// <summary> - /// Prevents the Member Reordering feature from tossing members of the marked class. - /// </summary> - /// <remarks> - /// The attribute must be mentioned in your member reordering patterns - /// </remarks> - [AttributeUsage( - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] - public sealed class NoReorderAttribute : Attribute { } - - /// <summary> - /// XAML attribute. Indicates the type that has <c>ItemsSource</c> property and should be treated - /// as <c>ItemsControl</c>-derived type, to enable inner items <c>DataContext</c> type resolve. - /// </summary> - [AttributeUsage(AttributeTargets.Class)] - public sealed class XamlItemsControlAttribute : Attribute { } - - /// <summary> - /// XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that - /// is used to bind some item of <c>ItemsControl</c>-derived type. This annotation will - /// enable the <c>DataContext</c> type resolve for XAML bindings for such properties. - /// </summary> - /// <remarks> - /// Property should have the tree ancestor of the <c>ItemsControl</c> type or - /// marked with the <see cref="XamlItemsControlAttribute"/> attribute. - /// </remarks> - [AttributeUsage(AttributeTargets.Property)] - public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class AspChildControlTypeAttribute : Attribute - { - public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) - { - TagName = tagName; - ControlType = controlType; - } - - [NotNull] public string TagName { get; } - - [NotNull] public Type ControlType { get; } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] - public sealed class AspDataFieldAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] - public sealed class AspDataFieldsAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Property)] - public sealed class AspMethodPropertyAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class AspRequiredAttributeAttribute : Attribute - { - public AspRequiredAttributeAttribute([NotNull] string attribute) - { - Attribute = attribute; - } - - [NotNull] public string Attribute { get; } - } - - [AttributeUsage(AttributeTargets.Property)] - public sealed class AspTypePropertyAttribute : Attribute - { - public bool CreateConstructorReferences { get; } - - public AspTypePropertyAttribute(bool createConstructorReferences) - { - CreateConstructorReferences = createConstructorReferences; - } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class RazorImportNamespaceAttribute : Attribute - { - public RazorImportNamespaceAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] public string Name { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class RazorInjectionAttribute : Attribute - { - public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) - { - Type = type; - FieldName = fieldName; - } - - [NotNull] public string Type { get; } - - [NotNull] public string FieldName { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class RazorDirectiveAttribute : Attribute - { - public RazorDirectiveAttribute([NotNull] string directive) - { - Directive = directive; - } - - [NotNull] public string Directive { get; } - } - - [AttributeUsage(AttributeTargets.Method)] - public sealed class RazorHelperCommonAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Property)] - public sealed class RazorLayoutAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Method)] - public sealed class RazorWriteLiteralMethodAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Method)] - public sealed class RazorWriteMethodAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class RazorWriteMethodParameterAttribute : Attribute { } -} \ No newline at end of file diff --git a/Tools/StoryboardManager.cs b/Tools/StoryboardManager.cs index 536d758..affd308 100644 --- a/Tools/StoryboardManager.cs +++ b/Tools/StoryboardManager.cs @@ -1,7 +1,6 @@ -using GMaster.Core.Tools; - -namespace GMaster.Tools +namespace GMaster.Tools { + using Core.Tools; using Windows.UI.Xaml; using Windows.UI.Xaml.Media.Animation; diff --git a/Tools/Tools.csproj b/Tools/Tools.csproj index ae5216e..0c7c0fe 100644 --- a/Tools/Tools.csproj +++ b/Tools/Tools.csproj @@ -114,14 +114,19 @@ <Compile Include="AbstractParameterModelCommand.cs" /> <Compile Include="AbstractSimpleCommand.cs" /> <Compile Include="AbstractSimpleParameterCommand.cs" /> + <Compile Include="CubeLutParser.cs" /> <Compile Include="DelegateConverter.cs" /> <Compile Include="DelegateParameterConverter.cs" /> <Compile Include="GlobalSuppressions.cs" /> + <Compile Include="ILutEffectGenerator.cs" /> + <Compile Include="ILutParser.cs" /> <Compile Include="IObservableHashCollection.cs" /> + <Compile Include="Lut.cs" /> + <Compile Include="Lut1DEffectGenerator.cs" /> + <Compile Include="Lut3DEffectGenerator.cs" /> <Compile Include="NotifyProperty.cs" /> <Compile Include="ObservableHashCollection.cs" /> <Compile Include="PressButton.cs" /> - <Compile Include="Properties\Annotations.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="SettingsContainer.cs" /> <Compile Include="StoryboardManager.cs" /> diff --git a/Tools/project.json b/Tools/project.json index 0e676f0..9d77998 100644 --- a/Tools/project.json +++ b/Tools/project.json @@ -1,8 +1,9 @@ { "dependencies": { "Microsoft.NETCore.UniversalWindowsPlatform": "5.3.3", - "Newtonsoft.Json": "10.0.2", - "StyleCop.Analyzers": "1.0.0" + "Newtonsoft.Json": "10.0.3", + "StyleCop.Analyzers": "1.0.2", + "Win2D.uwp": "1.21.0" }, "frameworks": { "uap10.0": {}