From a8163d43e7bc3857839054187e343f5c9c9f0ab1 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 28 May 2023 14:38:47 +0800 Subject: [PATCH 1/6] Add DumpScreenWinRT to return winrt XmlDocument --- .../Dummys/DummyAdbClient.cs | 4 ++ AdvancedSharpAdbClient/AdbClient.Async.cs | 29 ++++++++++++++- AdvancedSharpAdbClient/AdbClient.cs | 37 ++++++++++++++++++- .../AdvancedSharpAdbClient.csproj | 2 +- .../Interfaces/IAdbClient.Async.cs | 21 ++++++++++- .../Interfaces/IAdbClient.cs | 19 +++++++++- 6 files changed, 105 insertions(+), 7 deletions(-) diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs index d111de57..8a6ae5f7 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs @@ -119,6 +119,10 @@ public Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellO public Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); + public string DumpScreenString(DeviceData device) => throw new NotImplementedException(); + + public Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = default) => throw new NotImplementedException(); public Task FindElementAsync(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index 428ee5b5..6265efe9 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -678,9 +678,9 @@ public async Task> GetFeatureSetAsync(DeviceData device, Can } /// - public async Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken = default) + public async Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken = default) { - XmlDocument doc = new(); + EnsureDevice(device); using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); await socket.SendAdbRequestAsync("shell:uiautomator dump /dev/tty", cancellationToken); @@ -696,6 +696,30 @@ public async Task DumpScreenAsync(DeviceData device, CancellationTo string xmlString = await Utilities.Run(reader.ReadToEnd, cancellationToken).ConfigureAwait(false); #endif xmlString = xmlString.Replace("Events injected: 1\r\n", "").Replace("UI hierchary dumped to: /dev/tty", "").Trim(); + return xmlString; + } + + /// + public async Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken = default) + { + XmlDocument doc = new(); + string xmlString = await DumpScreenStringAsync(device, cancellationToken); + if (!string.IsNullOrEmpty(xmlString) + && !xmlString.StartsWith("ERROR") + && !xmlString.StartsWith("java.lang.Exception")) + { + doc.LoadXml(xmlString); + return doc; + } + return null; + } + +#if WINDOWS_UWP + /// + public async Task DumpScreenWinRTAsync(DeviceData device, CancellationToken cancellationToken = default) + { + Windows.Data.Xml.Dom.XmlDocument doc = new(); + string xmlString = await DumpScreenStringAsync(device, cancellationToken); if (!string.IsNullOrEmpty(xmlString) && !xmlString.StartsWith("ERROR") && !xmlString.StartsWith("java.lang.Exception")) @@ -705,6 +729,7 @@ public async Task DumpScreenAsync(DeviceData device, CancellationTo } return null; } +#endif /// public async Task ClickAsync(DeviceData device, Cords cords, CancellationToken cancellationToken = default) diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 40661b0c..3570eefe 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -620,6 +620,8 @@ public void InstallWrite(DeviceData device, Stream apk, string apkName, string s /// public void InstallCommit(DeviceData device, string session) { + EnsureDevice(device); + using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); @@ -637,6 +639,8 @@ public void InstallCommit(DeviceData device, string session) /// public IEnumerable GetFeatureSet(DeviceData device) { + EnsureDevice(device); + using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SendAdbRequest($"host-serial:{device.Serial}:features"); @@ -648,15 +652,39 @@ public IEnumerable GetFeatureSet(DeviceData device) } /// - public XmlDocument DumpScreen(DeviceData device) + public string DumpScreenString(DeviceData device) { - XmlDocument doc = new(); + EnsureDevice(device); using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); socket.SendAdbRequest("shell:uiautomator dump /dev/tty"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); string xmlString = reader.ReadToEnd().Replace("Events injected: 1\r\n", "").Replace("UI hierchary dumped to: /dev/tty", "").Trim(); + return xmlString; + } + + /// + public XmlDocument DumpScreen(DeviceData device) + { + XmlDocument doc = new(); + string xmlString = DumpScreenString(device); + if (!string.IsNullOrEmpty(xmlString) + && !xmlString.StartsWith("ERROR") + && !xmlString.StartsWith("java.lang.Exception")) + { + doc.LoadXml(xmlString); + return doc; + } + return null; + } + +#if WINDOWS_UWP + /// + public Windows.Data.Xml.Dom.XmlDocument DumpScreenWinRT(DeviceData device) + { + Windows.Data.Xml.Dom.XmlDocument doc = new(); + string xmlString = DumpScreenString(device); if (!string.IsNullOrEmpty(xmlString) && !xmlString.StartsWith("ERROR") && !xmlString.StartsWith("java.lang.Exception")) @@ -666,6 +694,7 @@ public XmlDocument DumpScreen(DeviceData device) } return null; } +#endif /// public void Click(DeviceData device, Cords cords) @@ -753,6 +782,8 @@ public bool IsAppRunning(DeviceData device, string packageName) /// public AppStatus GetAppStatus(DeviceData device, string packageName) { + EnsureDevice(device); + // Check if the app is in foreground bool currentApp = IsCurrentApp(device, packageName); if (currentApp) @@ -773,6 +804,7 @@ public AppStatus GetAppStatus(DeviceData device, string packageName) /// public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = default) { + EnsureDevice(device); Stopwatch stopwatch = new(); stopwatch.Start(); while (timeout == TimeSpan.Zero || stopwatch.Elapsed < timeout) @@ -808,6 +840,7 @@ public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = d /// public Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout = default) { + EnsureDevice(device); Stopwatch stopwatch = new(); stopwatch.Start(); while (timeout == TimeSpan.Zero || stopwatch.Elapsed < timeout) diff --git a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj index 02a7d906..8e3a9041 100644 --- a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj +++ b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj @@ -1,7 +1,7 @@  - False + True CA2254 diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs index 3afe3f14..3e65429f 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs @@ -422,9 +422,28 @@ public partial interface IAdbClient /// /// The device for which to get the screen snapshot. /// A which can be used to cancel the asynchronous operation. - /// An which return the Xml containing current hierarchy. + /// An which return a containing current hierarchy. + /// Failed if start with ERROR or java.lang.Exception. + Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken); + + /// + /// Gets the current device screen snapshot asynchronously. + /// + /// The device for which to get the screen snapshot. + /// A which can be used to cancel the asynchronous operation. + /// An which return a containing current hierarchy. Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken); +#if WINDOWS_UWP + /// + /// Gets the current device screen snapshot asynchronously. + /// + /// The device for which to get the screen snapshot. + /// A which can be used to cancel the asynchronous operation. + /// An which return a containing current hierarchy. + Task DumpScreenWinRTAsync(DeviceData device, CancellationToken cancellationToken); +#endif + /// /// Clicks on the specified coordinates. /// diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs index 0ec50e32..303a0d78 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs @@ -371,9 +371,26 @@ public partial interface IAdbClient /// Gets the current device screen snapshot. /// /// The device for which to get the screen snapshot. - /// Xml containing current hierarchy. + /// A containing current hierarchy. + /// Failed if start with ERROR or java.lang.Exception. + string DumpScreenString(DeviceData device); + + /// + /// Gets the current device screen snapshot. + /// + /// The device for which to get the screen snapshot. + /// A containing current hierarchy. XmlDocument DumpScreen(DeviceData device); +#if WINDOWS_UWP + /// + /// Gets the current device screen snapshot. + /// + /// The device for which to get the screen snapshot. + /// A containing current hierarchy. + Windows.Data.Xml.Dom.XmlDocument DumpScreenWinRT(DeviceData device); +#endif + /// /// Clicks on the specified coordinates. /// From e83549d0478ae35674e87eb2941f423670c94199 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 28 May 2023 15:55:26 +0800 Subject: [PATCH 2/6] Add converts for cords --- AdvancedSharpAdbClient/AdbClient.Async.cs | 8 +- AdvancedSharpAdbClient/Models/Cords.cs | 353 +++++++++++++++++++++- 2 files changed, 349 insertions(+), 12 deletions(-) diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index 6265efe9..c09591d0 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -738,7 +738,7 @@ public async Task ClickAsync(DeviceData device, Cords cords, CancellationToken c using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); - await socket.SendAdbRequestAsync(string.Format("shell:input tap {0} {1}", cords.X, cords.Y), cancellationToken); + await socket.SendAdbRequestAsync($"shell:input tap {cords.X} {cords.Y}", cancellationToken); AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); using StreamReader reader = new(socket.GetShellStream(), Encoding); #if !NET35 @@ -763,7 +763,7 @@ public async Task ClickAsync(DeviceData device, int x, int y, CancellationToken using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); - await socket.SendAdbRequestAsync(string.Format("shell:input tap {0} {1}", x, y), cancellationToken); + await socket.SendAdbRequestAsync($"shell:input tap {x} {y}", cancellationToken); AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); using StreamReader reader = new(socket.GetShellStream(), Encoding); #if !NET35 @@ -788,7 +788,7 @@ public async Task SwipeAsync(DeviceData device, Element first, Element second, l using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); - await socket.SendAdbRequestAsync(string.Format("shell:input swipe {0} {1} {2} {3} {4}", first.Cords.X, first.Cords.Y, second.Cords.X, second.Cords.Y, speed), cancellationToken); + await socket.SendAdbRequestAsync($"shell:input swipe {first.Cords.X} {first.Cords.Y} {second.Cords.X} {second.Cords.Y} {speed}", cancellationToken); AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); using StreamReader reader = new(socket.GetShellStream(), Encoding); #if !NET35 @@ -813,7 +813,7 @@ public async Task SwipeAsync(DeviceData device, int x1, int y1, int x2, int y2, using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); - await socket.SendAdbRequestAsync(string.Format("shell:input swipe {0} {1} {2} {3} {4}", x1, y1, x2, y2, speed), cancellationToken); + await socket.SendAdbRequestAsync($"shell:input swipe {x1} {y1} {x2} {y2} {speed}", cancellationToken); AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); using StreamReader reader = new(socket.GetShellStream(), Encoding); #if !NET35 diff --git a/AdvancedSharpAdbClient/Models/Cords.cs b/AdvancedSharpAdbClient/Models/Cords.cs index f99b3bf9..d6b8c6f3 100644 --- a/AdvancedSharpAdbClient/Models/Cords.cs +++ b/AdvancedSharpAdbClient/Models/Cords.cs @@ -2,13 +2,23 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // +using System; + namespace AdvancedSharpAdbClient { /// - /// Contains element coordinates. + /// Represents an ordered pair of integer x- and y-coordinates that defines a point in a two-dimensional plane. /// - public struct Cords + public struct Cords : IEquatable { + /// + /// Creates a new instance of the class with member data left uninitialized. + /// + public static readonly Cords Empty; + + private int x; // Do not rename (binary serialization) + private int y; // Do not rename (binary serialization) + /// /// Initializes a new instance of the class. /// @@ -16,19 +26,342 @@ public struct Cords /// The vertical "Y" coordinate. public Cords(int cx, int cy) { - X = cx; - Y = cy; + x = cx; + y = cy; + } + + /// + /// Initializes a new instance of the class using coordinates specified by an integer value. + /// + public Cords(int dw) + { + x = LowInt16(dw); + y = HighInt16(dw); + } + +#if HAS_DRAWING + /// + /// Initializes a new instance of the class from a . + /// + public Cords(System.Drawing.Point sz) + { + x = sz.X; + y = sz.Y; + } + + /// + /// Initializes a new instance of the class from a . + /// + public Cords(System.Drawing.Size sz) + { + x = sz.Width; + y = sz.Height; } +#endif + +#if WINDOWS_UWP +#pragma warning disable CS0419 // cref 特性中有不明确的引用 + /// + /// Initializes a new instance of the class from a . + /// + public Cords(Windows.Foundation.Point sz) + { + x = unchecked((int)sz.X); + y = unchecked((int)sz.Y); + } + + /// + /// Initializes a new instance of the class from a . + /// + public Cords(Windows.Foundation.Size sz) + { + x = unchecked((int)sz.Width); + y = unchecked((int)sz.Height); + } +#pragma warning restore CS0419 // cref 特性中有不明确的引用 +#endif + + /// + /// Gets a value indicating whether this is empty. + /// + public readonly bool IsEmpty => x == 0 && y == 0; /// /// Gets or sets the horizontal "X" coordinate. /// - public int X { get; set; } + public int X + { + readonly get => x; + set => x = value; + } /// /// Gets or sets the vertical "Y" coordinate. /// - public int Y { get; set; } + public int Y + { + readonly get => y; + set => y = value; + } + +#if HAS_DRAWING + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator System.Drawing.Point(Cords p) => new(p.X, p.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator System.Drawing.PointF(Cords p) => new(p.X, p.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static explicit operator System.Drawing.Size(Cords p) => new(p.X, p.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator Cords(System.Drawing.Point p) => new(p); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static explicit operator Cords(System.Drawing.Size p) => new(p); + + /// + /// Translates a by a given . + /// + /// The to translate. + /// A that specifies the pair of numbers + /// to add to the coordinates of . + /// The translated . + public static Cords operator +(Cords pt, System.Drawing.Size sz) => Add(pt, sz); + + /// + /// Translates a by the negative of a given . + /// + /// The to translate. + /// A that specifies the pair of numbers + /// to subtract from the coordinates of . + /// A structure that is translated by the negative of a given structure. + public static Cords operator -(Cords pt, System.Drawing.Size sz) => Subtract(pt, sz); +#endif + +#if WINDOWS_UWP +#pragma warning disable CS0419 // cref 特性中有不明确的引用 + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator Windows.Foundation.Point(Cords p) => new(p.X, p.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static explicit operator Windows.Foundation.Size(Cords p) => new(p.X, p.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator Cords(Windows.Foundation.Point p) => new(p); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static explicit operator Cords(Windows.Foundation.Size p) => new(p); + + /// + /// Translates a by a given . + /// + /// The to translate. + /// A that specifies the pair of numbers + /// to add to the coordinates of . + /// The translated . + public static Cords operator +(Cords pt, Windows.Foundation.Size sz) => Add(pt, sz); + + /// + /// Translates a by the negative of a given . + /// + /// The to translate. + /// A that specifies the pair of numbers + /// to subtract from the coordinates of . + /// A structure that is translated by the negative of a given structure. + public static Cords operator -(Cords pt, Windows.Foundation.Size sz) => Subtract(pt, sz); +#pragma warning restore CS0419 // cref 特性中有不明确的引用 +#endif + + /// + /// Compares two objects. The result specifies whether the values of the + /// and properties of the two + /// objects are equal. + /// + /// A to compare. + /// A to compare. + /// if the and values + /// of and are equal; otherwise, . + public static bool operator ==(Cords left, Cords right) => left.X == right.X && left.Y == right.Y; + + /// + /// Compares two objects. The result specifies whether the values of the + /// or properties of the two + /// objects are unequal. + /// + /// A to compare. + /// A to compare. + /// if the values of either the or values + /// of and differ; otherwise, . + public static bool operator !=(Cords left, Cords right) => !(left == right); + +#if HAS_DRAWING + /// + /// Translates a by a given . + /// + /// The to add. + /// The to add. + /// The that is the result of the addition operation. + public static Cords Add(Cords pt, System.Drawing.Size sz) => new(unchecked(pt.X + sz.Width), unchecked(pt.Y + sz.Height)); + + /// + /// Translates a by the negative of a given . + /// + /// The to be subtracted from. + /// The to subtract from the Point. + /// The that is the result of the subtraction operation. + public static Cords Subtract(Cords pt, System.Drawing.Size sz) => new(unchecked(pt.X - sz.Width), unchecked(pt.Y - sz.Height)); + + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The to convert. + /// The this method converts to. + public static Cords Ceiling(System.Drawing.PointF value) => new(unchecked((int)Math.Ceiling(value.X)), unchecked((int)Math.Ceiling(value.Y))); + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The to convert. + /// The this method converts to. + public static Cords Truncate(System.Drawing.PointF value) => new(unchecked((int)value.X), unchecked((int)value.Y)); + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The to convert. + /// The this method converts to. + public static Cords Round(System.Drawing.PointF value) => new(unchecked((int)Math.Round(value.X)), unchecked((int)Math.Round(value.Y))); +#endif + +#if WINDOWS_UWP +#pragma warning disable CS0419 // cref 特性中有不明确的引用 + /// + /// Translates a by a given . + /// + /// The to add. + /// The to add. + /// The that is the result of the addition operation. + public static Cords Add(Cords pt, Windows.Foundation.Size sz) => new(unchecked((int)(pt.X + sz.Width)), unchecked((int)(pt.Y + sz.Height))); + + /// + /// Translates a by the negative of a given . + /// + /// The to be subtracted from. + /// The to subtract from the Point. + /// The that is the result of the subtraction operation. + public static Cords Subtract(Cords pt, Windows.Foundation.Size sz) => new(unchecked((int)(pt.X - sz.Width)), unchecked((int)(pt.Y - sz.Height))); + + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The to convert. + /// The this method converts to. + public static Cords Ceiling(Windows.Foundation.Point value) => new(unchecked((int)Math.Ceiling(value.X)), unchecked((int)Math.Ceiling(value.Y))); + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The to convert. + /// The this method converts to. + public static Cords Truncate(Windows.Foundation.Point value) => new(unchecked((int)value.X), unchecked((int)value.Y)); + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The to convert. + /// The this method converts to. + public static Cords Round(Windows.Foundation.Point value) => new(unchecked((int)Math.Round(value.X)), unchecked((int)Math.Round(value.Y))); +#pragma warning restore CS0419 // cref 特性中有不明确的引用 +#endif + + /// + /// Specifies whether this contains the same coordinates as the specified + /// . + /// + /// The to test for equality. + /// if is a and has the same coordinates as this point instance. + public override readonly bool Equals(object obj) => obj is Cords cords && Equals(cords); + + /// + /// Specifies whether this contains the same coordinates as the specified + /// . + /// + /// The point to test for equality. + /// if has the same coordinates as this point instance. + public readonly bool Equals(Cords other) => this == other; + + /// + /// Returns a hash code. + /// + public override readonly int GetHashCode() => +#if NETCOREAPP2_1_OR_GREATER + HashCode.Combine(x, y); +#else + x ^ y; +#endif + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + public void Offset(int dx, int dy) + { + unchecked + { + X += dx; + Y += dy; + } + } + + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + public void Offset(Cords p) => Offset(p.X, p.Y); + + /// + /// Converts this to a human readable string. + /// + /// A string that represents this . + public override readonly string ToString() => $"{{X={X},Y={Y}}}"; /// /// Deconstruct the class. @@ -37,8 +370,12 @@ public Cords(int cx, int cy) /// The vertical "Y" coordinate. public readonly void Deconstruct(out int cx, out int cy) { - cx = X; - cy = Y; + cx = x; + cy = y; } + + private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); + + private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); } } From cfc45e017a80785c179df99901e47cd1d34201b1 Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 28 May 2023 17:13:58 +0800 Subject: [PATCH 3/6] Add Area for Element --- AdvancedSharpAdbClient/AdbClient.Async.cs | 12 +- AdvancedSharpAdbClient/AdbClient.cs | 22 +- AdvancedSharpAdbClient/AdbSocket.cs | 2 +- .../DeviceCommands/LinuxPath.cs | 6 +- AdvancedSharpAdbClient/Models/Area.cs | 589 ++++++++++++++++++ AdvancedSharpAdbClient/Models/Cords.cs | 5 +- AdvancedSharpAdbClient/Models/Element.cs | 25 +- 7 files changed, 636 insertions(+), 25 deletions(-) create mode 100644 AdvancedSharpAdbClient/Models/Area.cs diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index c09591d0..45003bc5 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -892,8 +892,8 @@ public async Task FindElementAsync(DeviceData device, string xpath, Can { attributes.Add(at.Name, at.Value); } - Cords cord = new((cords[0] + cords[2]) / 2, (cords[1] + cords[3]) / 2); // Average x1, y1, x2, y2 - return new Element(this, device, cord, attributes); + Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); + return new Element(this, device, area, attributes); } } } @@ -941,8 +941,8 @@ public async Task FindElementsAsync(DeviceData device, string xpath, { attributes.Add(at.Name, at.Value); } - Cords cord = new((cords[0] + cords[2]) / 2, (cords[1] + cords[3]) / 2); // Average x1, y1, x2, y2 - elements[i] = new Element(this, device, cord, attributes); + Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); + elements[i] = new Element(this, device, area, attributes); } } return elements.Length == 0 ? null : elements; @@ -974,7 +974,7 @@ public async Task SendKeyEventAsync(DeviceData device, string key, CancellationT using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); - await socket.SendAdbRequestAsync(string.Format("shell:input keyevent {0}", key), cancellationToken); + await socket.SendAdbRequestAsync($"shell:input keyevent {key}", cancellationToken); AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); using StreamReader reader = new(socket.GetShellStream(), Encoding); #if !NET35 @@ -999,7 +999,7 @@ public async Task SendTextAsync(DeviceData device, string text, CancellationToke using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken); - await socket.SendAdbRequestAsync(string.Format("shell:input text {0}", text), cancellationToken); + await socket.SendAdbRequestAsync($"shell:input text {text}", cancellationToken); AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); using StreamReader reader = new(socket.GetShellStream(), Encoding); #if !NET35 diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 3570eefe..d06aea5d 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -107,7 +107,7 @@ public AdbClient(EndPoint endPoint, Func adbSocketFactory) public static byte[] FormAdbRequest(string req) { int payloadLength = Encoding.GetByteCount(req); - string resultStr = string.Format("{0}{1}", payloadLength.ToString("X4"), req); + string resultStr = $"{payloadLength.ToString("X4")}{req}"; byte[] result = Encoding.GetBytes(resultStr); return result; } @@ -703,7 +703,7 @@ public void Click(DeviceData device, Cords cords) using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest(string.Format("shell:input tap {0} {1}", cords.X, cords.Y)); + socket.SendAdbRequest($"shell:input tap {cords.X} {cords.Y}"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); if (reader.ReadToEnd().ToUpper().Contains("ERROR")) // error or ERROR @@ -719,7 +719,7 @@ public void Click(DeviceData device, int x, int y) using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest(string.Format("shell:input tap {0} {1}", x, y)); + socket.SendAdbRequest($"shell:input tap {x} {y}"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); if (reader.ReadToEnd().ToUpper().Contains("ERROR")) @@ -735,7 +735,7 @@ public void Swipe(DeviceData device, Element first, Element second, long speed) using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest(string.Format("shell:input swipe {0} {1} {2} {3} {4}", first.Cords.X, first.Cords.Y, second.Cords.X, second.Cords.Y, speed)); + socket.SendAdbRequest($"shell:input swipe {first.Cords.X} {first.Cords.Y} {second.Cords.X} {second.Cords.Y} {speed}"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); if (reader.ReadToEnd().ToUpper().Contains("ERROR")) @@ -751,7 +751,7 @@ public void Swipe(DeviceData device, int x1, int y1, int x2, int y2, long speed) using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest(string.Format("shell:input swipe {0} {1} {2} {3} {4}", x1, y1, x2, y2, speed)); + socket.SendAdbRequest($"shell:input swipe {x1} {y1} {x2} {y2} {speed}"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); if (reader.ReadToEnd().ToUpper().Contains("ERROR")) @@ -824,8 +824,8 @@ public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = d { attributes.Add(at.Name, at.Value); } - Cords cord = new((cords[0] + cords[2]) / 2, (cords[1] + cords[3]) / 2); // Average x1, y1, x2, y2 - return new Element(this, device, cord, attributes); + Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); + return new Element(this, device, area, attributes); } } } @@ -863,8 +863,8 @@ public Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout { attributes.Add(at.Name, at.Value); } - Cords cord = new((cords[0] + cords[2]) / 2, (cords[1] + cords[3]) / 2); // Average x1, y1, x2, y2 - elements[i] = new Element(this, device, cord, attributes); + Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); + elements[i] = new Element(this, device, area, attributes); } } return elements.Length == 0 ? null : elements; @@ -885,7 +885,7 @@ public void SendKeyEvent(DeviceData device, string key) using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest(string.Format("shell:input keyevent {0}", key)); + socket.SendAdbRequest($"shell:input keyevent {key}"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); if (reader.ReadToEnd().ToUpper().Contains("ERROR")) @@ -901,7 +901,7 @@ public void SendText(DeviceData device, string text) using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest(string.Format("shell:input text {0}", text)); + socket.SendAdbRequest($"shell:input text {text}"); AdbResponse response = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); if (reader.ReadToEnd().ToUpper().Contains("ERROR")) diff --git a/AdvancedSharpAdbClient/AdbSocket.cs b/AdvancedSharpAdbClient/AdbSocket.cs index 269b0f23..bdbfaa5e 100644 --- a/AdvancedSharpAdbClient/AdbSocket.cs +++ b/AdvancedSharpAdbClient/AdbSocket.cs @@ -221,7 +221,7 @@ public virtual int Read(byte[] data, int length) } catch (SocketException sex) { - throw new AdbException(string.Format("No Data to read: {0}", sex.Message)); + throw new AdbException($"No Data to read: {sex.Message}"); } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/LinuxPath.cs b/AdvancedSharpAdbClient/DeviceCommands/LinuxPath.cs index 79500862..9f3cf83a 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/LinuxPath.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/LinuxPath.cs @@ -211,7 +211,7 @@ public static bool IsPathRooted(string path) /// /// The path. /// The quoted path. - public static string Quote(string path) => path.Contains(' ') ? string.Format("\"{0}\"", path) : path; + public static string Quote(string path) => path.Contains(' ') ? $"\"{path}\"" : path; /// /// Checks the invalid path chars. @@ -243,7 +243,7 @@ private static string FixupPath(string path) if (sb != "." && !sb.StartsWith(new string(new char[] { DirectorySeparatorChar }))) #endif { - sb = string.Format(".{0}{1}", DirectorySeparatorChar, sb); + sb = $".{DirectorySeparatorChar}{sb}"; } #if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER @@ -252,7 +252,7 @@ private static string FixupPath(string path) if (!sb.EndsWith(new string(new char[] { DirectorySeparatorChar }))) #endif { - sb = string.Format("{0}{1}", sb, DirectorySeparatorChar); + sb = $"{sb}{DirectorySeparatorChar}"; } sb = sb.Replace("//", new string(new char[] { DirectorySeparatorChar })); diff --git a/AdvancedSharpAdbClient/Models/Area.cs b/AdvancedSharpAdbClient/Models/Area.cs new file mode 100644 index 00000000..c241322f --- /dev/null +++ b/AdvancedSharpAdbClient/Models/Area.cs @@ -0,0 +1,589 @@ +using System; + +namespace AdvancedSharpAdbClient +{ + /// + /// Stores the location and size of a rectangular region. + /// + public struct Area : IEquatable + { + /// + /// Represents a structure with its properties left uninitialized. + /// + public static readonly Area Empty; + + private int x; // Do not rename (binary serialization) + private int y; // Do not rename (binary serialization) + private int width; // Do not rename (binary serialization) + private int height; // Do not rename (binary serialization) + + /// + /// Initializes a new instance of the class with the specified location + /// and size. + /// + /// The x-coordinate of the upper-left corner of the area. + /// The y-coordinate of the upper-left corner of the area. + /// The width of the area. + /// The height of the area. + public Area(int x, int y, int width, int height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + +#if HAS_DRAWING + /// + /// Initializes a new instance of the class with the specified rectangle. + /// + /// A that represents the rectangular region. + public Area(System.Drawing.Rectangle rectangle) + { + x = rectangle.X; + y = rectangle.Y; + width = rectangle.Width; + height = rectangle.Height; + } + + /// + /// Initializes a new instance of the class with the specified location and size. + /// + /// A that represents the upper-left corner of the rectangular region. + /// A that represents the width and height of the rectangular region. + public Area(Cords location, System.Drawing.Size size) + { + x = location.X; + y = location.Y; + width = size.Width; + height = size.Height; + } + + /// + /// Initializes a new instance of the class with the specified location and size. + /// + /// A that represents the upper-left corner of the rectangular region. + /// A that represents the width and height of the rectangular region. + public Area(System.Drawing.Point location, System.Drawing.Size size) + { + x = location.X; + y = location.Y; + width = size.Width; + height = size.Height; + } +#endif + +#if WINDOWS_UWP +#pragma warning disable CS0419 // cref 特性中有不明确的引用 + /// + /// Initializes a new instance of the class with the specified rectangle. + /// + /// A that represents the rectangular region. + public Area(Windows.Foundation.Rect rectangle) + { + x = unchecked((int)rectangle.X); + y = unchecked((int)rectangle.Y); + width = unchecked((int)rectangle.Width); + height = unchecked((int)rectangle.Height); + } + + /// + /// Initializes a new instance of the class with the specified location and size. + /// + /// A that represents the upper-left corner of the rectangular region. + /// A that represents the width and height of the rectangular region. + public Area(Cords location, Windows.Foundation.Size size) + { + x = location.X; + y = location.Y; + width = unchecked((int)size.Width); + height = unchecked((int)size.Height); + } + + /// + /// Initializes a new instance of the class with the specified location and size. + /// + /// A that represents the upper-left corner of the rectangular region. + /// A that represents the width and height of the rectangular region. + public Area(Windows.Foundation.Point location, Windows.Foundation.Size size) + { + x = unchecked((int)location.X); + y = unchecked((int)location.Y); + width = unchecked((int)size.Width); + height = unchecked((int)size.Height); + } +#pragma warning restore CS0419 // cref 特性中有不明确的引用 +#endif + + /// + /// Creates a new with the specified location and size. + /// + /// The x-coordinate of the upper-left corner of this structure. + /// The y-coordinate of the upper-left corner of this structure. + /// The x-coordinate of the lower-right corner of this structure. + /// The y-coordinate of the lower-right corner of this structure. + public static Area FromLTRB(int left, int top, int right, int bottom) => + new(left, top, unchecked(right - left), unchecked(bottom - top)); + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this + /// . + /// + public Cords Location + { + readonly get => new(X, Y); + set + { + X = value.X; + Y = value.Y; + } + } + + /// + /// Gets or sets the coordinates of the center of the rectangular region represented by this + /// . + /// + public readonly Cords Center => unchecked(new(X + Width / 2, Y + Height / 2)); + +#if HAS_DRAWING + /// + /// Gets or sets the size of this . + /// + public System.Drawing.Size Size + { + readonly get => new(Width, Height); + set + { + Width = value.Width; + Height = value.Height; + } + } +#endif + +#if !HAS_DRAWING && WINDOWS_UWP + /// + /// Gets or sets the size of this . + /// + public Windows.Foundation.Size Size + { + readonly get => new(Width, Height); + set + { + Width = unchecked((int)value.Width); + Height = unchecked((int)value.Height); + } + } +#endif + + /// + /// Gets or sets the x-coordinate of the upper-left corner of the rectangular region defined by this + /// . + /// + public int X + { + readonly get => x; + set => x = value; + } + + /// + /// Gets or sets the y-coordinate of the upper-left corner of the rectangular region defined by this + /// . + /// + public int Y + { + readonly get => y; + set => y = value; + } + + /// + /// Gets or sets the width of the rectangular region defined by this . + /// + public int Width + { + readonly get => width; + set => width = value; + } + + /// + /// Gets or sets the width of the rectangular region defined by this . + /// + public int Height + { + readonly get => height; + set => height = value; + } + + /// + /// Gets the x-coordinate of the upper-left corner of the rectangular region defined by this + /// . + /// + public readonly int Left => X; + + /// + /// Gets the y-coordinate of the upper-left corner of the rectangular region defined by this + /// . + /// + public readonly int Top => Y; + + /// + /// Gets the x-coordinate of the lower-right corner of the rectangular region defined by this + /// . + /// + public readonly int Right => unchecked(X + Width); + + /// + /// Gets the y-coordinate of the lower-right corner of the rectangular region defined by this + /// . + /// + public readonly int Bottom => unchecked(Y + Height); + + /// + /// Tests whether this has a + /// or a of 0. + /// + public readonly bool IsEmpty => height == 0 && width == 0 && x == 0 && y == 0; + + /// + /// Tests whether is a with the same location + /// and size of this Area. + /// + /// The to test. + /// This method returns if is a structure + /// and its , , , and properties are equal to + /// the corresponding properties of this structure; otherwise, . + public override readonly bool Equals(object obj) => obj is Area area && Equals(area); + + /// + public readonly bool Equals(Area other) => this == other; + +#if HAS_DRAWING + /// + /// Creates a with the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator System.Drawing.Rectangle(Area rect) => new(rect.X, rect.Y, rect.Width, rect.Height); + + /// + /// Creates a with the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator Area(System.Drawing.Rectangle rect) => new(rect); +#endif + +#if WINDOWS_UWP +#pragma warning disable CS0419 // cref 特性中有不明确的引用 + /// + /// Creates a with the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator Windows.Foundation.Rect(Area rect) => new(rect.X, rect.Y, rect.Width, rect.Height); + + /// + /// Creates a with the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator Area(Windows.Foundation.Rect rect) => new(rect); +#pragma warning restore CS0419 // cref 特性中有不明确的引用 +#endif + + /// + /// Tests whether two objects have equal location and size. + /// + /// The Rectangle structure that is to the left of the equality operator. + /// The Rectangle structure that is to the right of the equality operator. + /// This operator returns if the two structures have equal + /// , , , and properties. + public static bool operator ==(Area left, Area right) => + left.X == right.X && left.Y == right.Y && left.Width == right.Width && left.Height == right.Height; + + /// + /// Tests whether two objects differ in location or size. + /// + /// The Rectangle structure that is to the left of the inequality operator. + /// The Rectangle structure that is to the right of the inequality operator. + /// This operator returns if any of the , , + /// properties of the two structures are unequal; otherwise . + public static bool operator !=(Area left, Area right) => !(left == right); + +#if HAS_DRAWING + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The structure to be converted. + public static Area Ceiling(System.Drawing.RectangleF value) + { + unchecked + { + return new Area( + (int)Math.Ceiling(value.X), + (int)Math.Ceiling(value.Y), + (int)Math.Ceiling(value.Width), + (int)Math.Ceiling(value.Height)); + } + } + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The structure to be converted. + public static Area Truncate(System.Drawing.RectangleF value) + { + unchecked + { + return new Area( + (int)value.X, + (int)value.Y, + (int)value.Width, + (int)value.Height); + } + } + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The structure to be converted. + public static Area Round(System.Drawing.RectangleF value) + { + unchecked + { + return new Area( + (int)Math.Round(value.X), + (int)Math.Round(value.Y), + (int)Math.Round(value.Width), + (int)Math.Round(value.Height)); + } + } +#endif + +#if WINDOWS_UWP +#pragma warning disable CS0419 // cref 特性中有不明确的引用 + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The structure to be converted. + public static Area Ceiling(Windows.Foundation.Rect value) + { + unchecked + { + return new Area( + (int)Math.Ceiling(value.X), + (int)Math.Ceiling(value.Y), + (int)Math.Ceiling(value.Width), + (int)Math.Ceiling(value.Height)); + } + } + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The structure to be converted. + public static Area Truncate(Windows.Foundation.Rect value) + { + unchecked + { + return new Area( + (int)value.X, + (int)value.Y, + (int)value.Width, + (int)value.Height); + } + } + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The structure to be converted. + public static Area Round(Windows.Foundation.Rect value) + { + unchecked + { + return new Area( + (int)Math.Round(value.X), + (int)Math.Round(value.Y), + (int)Math.Round(value.Width), + (int)Math.Round(value.Height)); + } + } +#pragma warning restore CS0419 // cref 特性中有不明确的引用 +#endif + + /// + /// Determines if the specified point is contained within the rectangular region defined by this + /// . + /// + /// The x-coordinate of the point to test. + /// The y-coordinate of the point to test. + /// This method returns if the point defined by and + /// is contained within this structure; otherwise . + public readonly bool Contains(int x, int y) => X <= x && x < X + Width && Y <= y && y < Y + Height; + + /// + /// Determines if the specified point is contained within the rectangular region defined by this + /// . + /// + /// The to test. + /// This method returns if the point represented by + /// is contained within this structure; otherwise . + public readonly bool Contains(Cords pt) => Contains(pt.X, pt.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained within the + /// rectangular region represented by this . + /// + /// The to test. + /// This method returns if the rectangular region represented by + /// is entirely contained within this structure; otherwise . + public readonly bool Contains(Area rect) => + (X <= rect.X) && (rect.X + rect.Width <= X + Width) && + (Y <= rect.Y) && (rect.Y + rect.Height <= Y + Height); + + /// + /// Returns the hash code for this structure. + /// + /// An integer that represents the hash code for this rectangle. + public override readonly int GetHashCode() => +#if NETCOREAPP2_1_OR_GREATER + HashCode.Combine(X, Y, Width, Height); +#else + X ^ Y ^ Width ^ Height; +#endif + + /// + /// Inflates this by the specified amount. + /// + /// The amount to inflate this horizontally. + /// The amount to inflate this vertically. + public void Inflate(int width, int height) + { + unchecked + { + X -= width; + Y -= height; + + Width += 2 * width; + Height += 2 * height; + } + } + +#if HAS_DRAWING + /// + /// Inflates this by the specified amount. + /// + /// The amount to inflate this rectangle. + public void Inflate(System.Drawing.Size size) => Inflate(size.Width, size.Height); +#endif + +#if WINDOWS_UWP + /// + /// Inflates this by the specified amount. + /// + /// The amount to inflate this rectangle. + public void Inflate(Windows.Foundation.Size size) => Inflate(unchecked((int)size.Width), unchecked((int)size.Height)); +#endif + + /// + /// Creates a that is inflated by the specified amount. + /// + /// The with which to start. This rectangle is not modified. + /// The amount to inflate this horizontally. + /// The amount to inflate this vertically. + public static Area Inflate(Area rect, int x, int y) + { + Area r = rect; + r.Inflate(x, y); + return r; + } + + /// + /// Creates a Area that represents the intersection between this Area and rect. + /// + /// The with which to intersect. + public void Intersect(Area rect) + { + Area result = Intersect(rect, this); + + X = result.X; + Y = result.Y; + Width = result.Width; + Height = result.Height; + } + + /// + /// Creates a rectangle that represents the intersection between a and b. If there is no intersection, an + /// empty rectangle is returned. + /// + /// A rectangle to intersect. + /// A rectangle to intersect. + /// A that represents the intersection of and . + public static Area Intersect(Area a, Area b) + { + int x1 = Math.Max(a.X, b.X); + int x2 = Math.Min(a.X + a.Width, b.X + b.Width); + int y1 = Math.Max(a.Y, b.Y); + int y2 = Math.Min(a.Y + a.Height, b.Y + b.Height); + + if (x2 >= x1 && y2 >= y1) + { + return new Area(x1, y1, x2 - x1, y2 - y1); + } + + return Empty; + } + + /// + /// Determines if this rectangle intersects with rect. + /// + /// The rectangle to test. + /// This method returns if there is any intersection, otherwise . + public readonly bool IntersectsWith(Area rect) => + (rect.X < X + Width) && (X < rect.X + rect.Width) && + (rect.Y < Y + Height) && (Y < rect.Y + rect.Height); + + /// + /// Creates a rectangle that represents the union between a and b. + /// + /// A rectangle to union. + /// A rectangle to union. + /// A structure that bounds the union of the two structures. + public static Area Union(Area a, Area b) + { + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.X + a.Width, b.X + b.Width); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + return new Area(x1, y1, x2 - x1, y2 - y1); + } + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// Amount to offset the location. + public void Offset(Cords pos) => Offset(pos.X, pos.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The horizontal offset. + /// The vertical offset. + public void Offset(int x, int y) + { + unchecked + { + X += x; + Y += y; + } + } + + /// + /// Converts the attributes of this to a human readable string. + /// + /// A string that contains the position, width, and height of this structure ¾ + /// for example, {X=20, Y=20, Width=100, Height=50}. + public override readonly string ToString() => $"{{X={X},Y={Y},Width={Width},Height={Height}}}"; + } +} diff --git a/AdvancedSharpAdbClient/Models/Cords.cs b/AdvancedSharpAdbClient/Models/Cords.cs index d6b8c6f3..9c5de2f2 100644 --- a/AdvancedSharpAdbClient/Models/Cords.cs +++ b/AdvancedSharpAdbClient/Models/Cords.cs @@ -180,7 +180,7 @@ public int Y /// /// The to convert. /// The that results from the conversion. - public static implicit operator Cords(Windows.Foundation.Point p) => new(p); + public static explicit operator Cords(Windows.Foundation.Point p) => new(p); /// /// Creates a with the coordinates of the specified . @@ -328,8 +328,9 @@ public int Y public readonly bool Equals(Cords other) => this == other; /// - /// Returns a hash code. + /// Returns a hash code for this . /// + /// An integer value that specifies a hash value for this . public override readonly int GetHashCode() => #if NETCOREAPP2_1_OR_GREATER HashCode.Combine(x, y); diff --git a/AdvancedSharpAdbClient/Models/Element.cs b/AdvancedSharpAdbClient/Models/Element.cs index e7502cf8..57a7fb89 100644 --- a/AdvancedSharpAdbClient/Models/Element.cs +++ b/AdvancedSharpAdbClient/Models/Element.cs @@ -24,7 +24,12 @@ public class Element private DeviceData Device { get; set; } /// - /// Contains element coordinates. + /// The coordinates and size of the element. + /// + public Area Area { get; set; } + + /// + /// The coordinates of the element to click. Default is the center of area. /// public Cords Cords { get; set; } @@ -38,7 +43,7 @@ public class Element /// /// The current ADB client that manages the connection. /// The current device containing the element. - /// Contains element coordinates . + /// The coordinates of the element to click. /// Gets or sets element attributes. public Element(IAdbClient client, DeviceData device, Cords cords, Dictionary attributes) { @@ -48,6 +53,22 @@ public Element(IAdbClient client, DeviceData device, Cords cords, Dictionary + /// Initializes a new instance of the class. + /// + /// The current ADB client that manages the connection. + /// The current device containing the element. + /// The coordinates and size of the element. + /// Gets or sets element attributes. + public Element(IAdbClient client, DeviceData device, Area area, Dictionary attributes) + { + Client = client; + Device = device; + Area = area; + Attributes = attributes; + Cords = area.Center; // Average x1, y1, x2, y2 + } + /// /// Clicks on this coordinates. /// From d66d367e97a7dcc9a94588717faee323fe63bd7c Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 28 May 2023 17:35:23 +0800 Subject: [PATCH 4/6] Add tests of Cords and Area --- .../Models/AreaTests.cs | 304 ++++++++++++++++++ .../Models/CordsTests.cs | 224 +++++++++++++ AdvancedSharpAdbClient/Models/Area.cs | 7 + 3 files changed, 535 insertions(+) create mode 100644 AdvancedSharpAdbClient.Tests/Models/AreaTests.cs create mode 100644 AdvancedSharpAdbClient.Tests/Models/CordsTests.cs diff --git a/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs b/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs new file mode 100644 index 00000000..dc65b62e --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs @@ -0,0 +1,304 @@ +using System; +using System.Drawing; +using System.Globalization; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + /// + /// Tests the class. + /// + public class AreaTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(Area.Empty, new Area()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonDefaultConstructorTest(int x, int y, int width, int height) + { + Area rect1 = new(x, y, width, height); + Area rect2 = new(new Cords(x, y), new Size(width, height)); + + Assert.Equal(rect1, rect2); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void FromLTRBTest(int left, int top, int right, int bottom) + { + Area rect1 = new(left, top, unchecked(right - left), unchecked(bottom - top)); + Area rect2 = Area.FromLTRB(left, top, right, bottom); + + Assert.Equal(rect1, rect2); + } + + [Fact] + public void EmptyTest() + { + Assert.True(Area.Empty.IsEmpty); + Assert.True(new Area(0, 0, 0, 0).IsEmpty); + Assert.True(new Area().IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonEmptyTest(int x, int y, int width, int height) + { + Assert.False(new Area(x, y, width, height).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void DimensionsTest(int x, int y, int width, int height) + { + Area rect = new(x, y, width, height); + Assert.Equal(new Cords(x, y), rect.Location); + Assert.Equal(new Size(width, height), rect.Size); + + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + + Cords p = new(width, height); + Size s = new(x, y); + rect.Location = p; + rect.Size = s; + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + + Assert.Equal(width, rect.X); + Assert.Equal(height, rect.Y); + Assert.Equal(x, rect.Width); + Assert.Equal(y, rect.Height); + Assert.Equal(width, rect.Left); + Assert.Equal(height, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public static void LocationSetTest(int x, int y) + { + Cords Cords = new(x, y); + Area rect = new(10, 10, 10, 10) + { + Location = Cords + }; + Assert.Equal(Cords, rect.Location); + Assert.Equal(Cords.X, rect.X); + Assert.Equal(Cords.Y, rect.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public static void SizeSetTest(int x, int y) + { + Size size = new(x, y); + Area rect = new(10, 10, 10, 10) + { + Size = size + }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void EqualityTest(int x, int y, int width, int height) + { + Area rect1 = new(x, y, width, height); + Area rect2 = new(width / 2, height / 2, x, y); + + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); + } + + [Fact] + public static void EqualityTest_NotArea() + { + Area Area = new(0, 0, 0, 0); + Assert.False(Area.Equals(null)); + Assert.False(Area.Equals(0)); + Assert.False(Area.Equals(new RectangleF(0, 0, 0, 0))); + } + + [Fact] + public static void GetHashCodeTest() + { + Area rect1 = new(10, 10, 10, 10); + Area rect2 = new(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Area(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Area(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Area(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Area(10, 10, 10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void AreaFConversionTest(float x, float y, float width, float height) + { + RectangleF rect = new(x, y, width, height); + Area rCeiling, rTruncate, rRound; + + unchecked + { + rCeiling = new Area((int)Math.Ceiling(x), (int)Math.Ceiling(y), + (int)Math.Ceiling(width), (int)Math.Ceiling(height)); + rTruncate = new Area((int)x, (int)y, (int)width, (int)height); + rRound = new Area((int)Math.Round(x), (int)Math.Round(y), + (int)Math.Round(width), (int)Math.Round(height)); + } + + Assert.Equal(rCeiling, Area.Ceiling(rect)); + Assert.Equal(rTruncate, Area.Truncate(rect)); + Assert.Equal(rRound, Area.Round(rect)); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void ContainsTest(int x, int y, int width, int height) + { + Area rect = new(unchecked(2 * x - width), unchecked(2 * y - height), width, height); + Cords p = new(x, y); + Area r = new(x, y, width / 2, height / 2); + + Assert.False(rect.Contains(x, y)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void InflateTest(int x, int y, int width, int height) + { + Area inflatedRect, rect = new(x, y, width, height); + unchecked + { + inflatedRect = new Area(x - width, y - height, width + 2 * width, height + 2 * height); + } + + Assert.Equal(inflatedRect, Area.Inflate(rect, width, height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + Size s = new(x, y); + unchecked + { + inflatedRect = new Area(rect.X - x, rect.Y - y, rect.Width + 2 * x, rect.Height + 2 * y); + } + + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void IntersectTest(int x, int y, int width, int height) + { + Area rect = new(x, y, width, height); + Area expectedRect = Area.Intersect(rect, rect); + rect.Intersect(rect); + Assert.Equal(expectedRect, rect); + Assert.False(rect.IntersectsWith(expectedRect)); + } + + [Fact] + public static void Intersect_IntersectingAreas_Test() + { + Area rect1 = new(0, 0, 5, 5); + Area rect2 = new(1, 1, 3, 3); + Area expected = new(1, 1, 3, 3); + + Assert.Equal(expected, Area.Intersect(rect1, rect2)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void UnionTest(int x, int y, int width, int height) + { + Area a = new(x, y, width, height); + Area b = new(width, height, x, y); + + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.X + a.Width, b.X + b.Width); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + Area expectedArea = new(x1, y1, x2 - x1, y2 - y1); + + Assert.Equal(expectedArea, Area.Union(a, b)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void OffsetTest(int x, int y, int width, int height) + { + Area r1 = new(x, y, width, height); + Area expectedRect = new(x + width, y + height, width, height); + Cords p = new(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(5, -5, 0, 1)] + public void ToStringTest(int x, int y, int width, int height) + { + Area r = new(x, y, width, height); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "{{X={0},Y={1},Width={2},Height={3}}}", r.X, r.Y, r.Width, r.Height), r.ToString()); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs b/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs new file mode 100644 index 00000000..542a2501 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs @@ -0,0 +1,224 @@ +using System; +using System.Drawing; +using System.Globalization; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + /// + /// Tests the class. + /// + public class CordsTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(Cords.Empty, new Cords()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int x, int y) + { + Cords p1 = new(x, y); + Cords p2 = new(new Size(x, y)); + + Assert.Equal(p1, p2); + } + + [Theory] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(0)] + public void SingleIntConstructorTest(int x) + { + Cords p1 = new(x); + Cords p2 = new(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); + + Assert.Equal(p1, p2); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(Cords.Empty.IsEmpty); + Assert.True(new Cords().IsEmpty); + Assert.True(new Cords(0, 0).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int x, int y) + { + Assert.False(new Cords(x, y).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(int x, int y) + { + Cords p = new(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void CordsFConversionTest(int x, int y) + { + PointF p = new Cords(x, y); + Assert.Equal(new PointF(x, y), p); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int x, int y) + { + Size sz = (Size)new Cords(x, y); + Assert.Equal(new Size(x, y), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int x, int y) + { + Cords addExpected, subExpected, p = new(x, y); + Size s = new(y, x); + + unchecked + { + addExpected = new Cords(x + y, y + x); + subExpected = new Cords(x - y, y - x); + } + + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, Cords.Add(p, s)); + Assert.Equal(subExpected, Cords.Subtract(p, s)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void CordsFMathematicalTest(float x, float y) + { + PointF pf = new(x, y); + Cords pCeiling, pTruncate, pRound; + + unchecked + { + pCeiling = new Cords((int)Math.Ceiling(x), (int)Math.Ceiling(y)); + pTruncate = new Cords((int)x, (int)y); + pRound = new Cords((int)Math.Round(x), (int)Math.Round(y)); + } + + Assert.Equal(pCeiling, Cords.Ceiling(pf)); + Assert.Equal(pRound, Cords.Round(pf)); + Assert.Equal(pTruncate, Cords.Truncate(pf)); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void OffsetTest(int x, int y) + { + Cords p1 = new(x, y); + Cords p2 = new(y, x); + + p1.Offset(p2); + + Assert.Equal(unchecked(p2.X + p2.Y), p1.X); + Assert.Equal(p1.X, p1.Y); + + p2.Offset(x, y); + Assert.Equal(p1, p2); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int x, int y) + { + Cords p1 = new(x, y); + Cords p2 = new((x / 2) - 1, y / 2 - 1); + Cords p3 = new(x, y); + + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); + + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); + + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); + + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); + } + + [Fact] + public static void EqualityTest_NotCords() + { + Cords Cords = new(0, 0); + Assert.False(Cords.Equals(null)); + Assert.False(Cords.Equals(0)); + Assert.False(Cords.Equals(new PointF(0, 0))); + } + + [Fact] + public static void GetHashCodeTest() + { + Cords Cords = new(10, 10); + Assert.Equal(Cords.GetHashCode(), new Cords(10, 10).GetHashCode()); + Assert.NotEqual(Cords.GetHashCode(), new Cords(20, 10).GetHashCode()); + Assert.NotEqual(Cords.GetHashCode(), new Cords(10, 20).GetHashCode()); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(1, -2, 3, -4)] + public void ConversionTest(int x, int y, int width, int height) + { + Area rect = new(x, y, width, height); + RectangleF rectF = rect; + Assert.Equal(x, rectF.X); + Assert.Equal(y, rectF.Y); + Assert.Equal(width, rectF.Width); + Assert.Equal(height, rectF.Height); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(5, -5)] + public void ToStringTest(int x, int y) + { + Cords p = new(x, y); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "{{X={0},Y={1}}}", p.X, p.Y), p.ToString()); + } + } +} diff --git a/AdvancedSharpAdbClient/Models/Area.cs b/AdvancedSharpAdbClient/Models/Area.cs index c241322f..69daf2f8 100644 --- a/AdvancedSharpAdbClient/Models/Area.cs +++ b/AdvancedSharpAdbClient/Models/Area.cs @@ -264,6 +264,13 @@ public int Height /// The that results from the conversion. public static implicit operator System.Drawing.Rectangle(Area rect) => new(rect.X, rect.Y, rect.Width, rect.Height); + /// + /// Creates a with the specified . + /// + /// The to convert. + /// The that results from the conversion. + public static implicit operator System.Drawing.RectangleF(Area rect) => new(rect.X, rect.Y, rect.Width, rect.Height); + /// /// Creates a with the specified . /// From 4657943eb48cbf41a1601c206ef9c9efa53bce4f Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 28 May 2023 21:09:09 +0800 Subject: [PATCH 5/6] Add create WriteableBitmap in FramebufferHeader --- AdvancedSharpAdbClient/Models/Framebuffer.cs | 43 +++++- .../Models/FramebufferHeader.cs | 135 ++++++++++++++++++ 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/AdvancedSharpAdbClient/Models/Framebuffer.cs b/AdvancedSharpAdbClient/Models/Framebuffer.cs index 394f19a1..149114f0 100644 --- a/AdvancedSharpAdbClient/Models/Framebuffer.cs +++ b/AdvancedSharpAdbClient/Models/Framebuffer.cs @@ -2,12 +2,12 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // +using AdvancedSharpAdbClient.Exceptions; using System; using System.Buffers; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using AdvancedSharpAdbClient.Exceptions; #if NET using System.Runtime.Versioning; @@ -17,6 +17,12 @@ using System.Drawing; #endif +#if WINDOWS_UWP +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml.Media.Imaging; +#endif + namespace AdvancedSharpAdbClient { /// @@ -115,7 +121,6 @@ public async Task RefreshAsync(CancellationToken cancellationToken = default) public Image ToImage() { EnsureNotDisposed(); - return Data == null ? throw new InvalidOperationException("Call RefreshAsync first") : Header.ToImage(Data); } @@ -126,6 +131,40 @@ public Image ToImage() public static explicit operator Image(Framebuffer value) => value.ToImage(); #endif +#if WINDOWS_UWP + /// + /// Converts the framebuffer data to a . + /// + /// An which represents the framebuffer data. + public Task ToBitmap() + { + EnsureNotDisposed(); + return Data == null ? throw new InvalidOperationException("Call RefreshAsync first") : Header.ToBitmap(Data); + } + + /// + /// Converts the framebuffer data to a . + /// + /// The target to invoke the code on. + /// An which represents the framebuffer data. + public Task ToBitmap(CoreDispatcher dispatcher) + { + EnsureNotDisposed(); + return Data == null ? throw new InvalidOperationException("Call RefreshAsync first") : Header.ToBitmap(Data, dispatcher); + } + + /// + /// Converts the framebuffer data to a . + /// + /// The target to invoke the code on. + /// An which represents the framebuffer data. + public Task ToBitmap(DispatcherQueue dispatcher) + { + EnsureNotDisposed(); + return Data == null ? throw new InvalidOperationException("Call RefreshAsync first") : Header.ToBitmap(Data, dispatcher); + } +#endif + /// protected virtual void Dispose(bool disposing) { diff --git a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs index e1b3bf56..e582ff31 100644 --- a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs +++ b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Text; +using System.Threading.Tasks; #if NET using System.Runtime.Versioning; @@ -16,6 +17,16 @@ using System.Runtime.InteropServices; #endif +#if WINDOWS_UWP +using Windows.Foundation; +using Windows.Foundation.Metadata; +using Windows.Graphics.Imaging; +using Windows.Storage.Streams; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml.Media.Imaging; +#endif + namespace AdvancedSharpAdbClient { /// @@ -291,5 +302,129 @@ private readonly PixelFormat StandardizePixelFormat(byte[] buffer) throw new NotSupportedException($"Pixel depths of {Bpp} are not supported"); } #endif + +#if WINDOWS_UWP + /// + /// Converts a array containing the raw frame buffer data to a . + /// + /// The buffer containing the image data. + /// The target to invoke the code on. + /// + /// A that represents the image contained in the frame buffer, or + /// if the framebuffer does not contain any data. This can happen when DRM is enabled on the device. + /// + public readonly Task ToBitmap(byte[] buffer, CoreDispatcher dispatcher) + { + FramebufferHeader self = this; + + if (dispatcher.HasThreadAccess) + { + return ToBitmap(buffer); + } + else + { + TaskCompletionSource taskCompletionSource = new(); + + _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + try + { + taskCompletionSource.SetResult(await self.ToBitmap(buffer)); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + } + + /// + /// Converts a array containing the raw frame buffer data to a . + /// + /// The buffer containing the image data. + /// The target to invoke the code on. + /// + /// A that represents the image contained in the frame buffer, or + /// if the framebuffer does not contain any data. This can happen when DRM is enabled on the device. + /// + [ContractVersion(typeof(UniversalApiContract), 327680u)] + public readonly Task ToBitmap(byte[] buffer, DispatcherQueue dispatcher) + { + FramebufferHeader self = this; + + if (ApiInformation.IsMethodPresent("Windows.System.DispatcherQueue", "HasThreadAccess") && dispatcher.HasThreadAccess) + { + return ToBitmap(buffer); + } + else + { + TaskCompletionSource taskCompletionSource = new(); + + if (!dispatcher.TryEnqueue(async () => + { + try + { + taskCompletionSource.SetResult(await self.ToBitmap(buffer)); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + })) + { + taskCompletionSource.SetException(new InvalidOperationException("Failed to enqueue the operation")); + } + + return taskCompletionSource.Task; + } + } + + /// + /// Converts a array containing the raw frame buffer data to a . + /// + /// The buffer containing the image data. + /// + /// A that represents the image contained in the frame buffer, or + /// if the framebuffer does not contain any data. This can happen when DRM is enabled on the device. + /// + public readonly async Task ToBitmap(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + // This happens, for example, when DRM is enabled. In that scenario, no screenshot is taken on the device and an empty + // framebuffer is returned; we'll just return null. + if (Width == 0 || Height == 0 || Bpp == 0) + { + return null; + } + + using MemoryStream stream = new(buffer); + using IRandomAccessStream randomAccessStream = stream.AsRandomAccessStream(); + BitmapDecoder decoder = await BitmapDecoder.CreateAsync(randomAccessStream); + SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync(); + try + { + WriteableBitmap WriteableImage = new((int)decoder.PixelWidth, (int)decoder.PixelHeight); + await WriteableImage.SetSourceAsync(randomAccessStream); + return WriteableImage; + } + catch (Exception) + { + using InMemoryRandomAccessStream random = new(); + BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, random); + encoder.SetSoftwareBitmap(softwareBitmap); + await encoder.FlushAsync(); + WriteableBitmap WriteableImage = new((int)decoder.PixelWidth, (int)decoder.PixelHeight); + await WriteableImage.SetSourceAsync(random); + return WriteableImage; + } + } +#endif } } From 3829261c717adbf3f9c07b6adc3219347a3ced6b Mon Sep 17 00:00:00 2001 From: wherewhere Date: Sun, 28 May 2023 21:20:38 +0800 Subject: [PATCH 6/6] Change uap10.0 to uap10.0 uap10.0.15138.0 --- .../AdvancedSharpAdbClient.csproj | 28 +++++++++++++------ Directory.Build.props | 6 ++-- Directory.Build.targets | 3 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj index 8e3a9041..a6b3b02a 100644 --- a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj +++ b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj @@ -1,13 +1,13 @@  - True + False CA2254 NU1603;$(NoWarn) - net3.5-client;net4.0-client;net4.5.2;net4.6.2;net4.7.2;net4.8.1;net6.0;net8.0;netcore50;netcoreapp2.1;netcoreapp3.1;netstandard1.3;netstandard2.0;netstandard2.1;uap10.0 + net3.5-client;net4.0-client;net4.5.2;net4.6.2;net4.7.2;net4.8.1;net6.0;net8.0;netcore50;netcoreapp2.1;netcoreapp3.1;netstandard1.3;netstandard2.0;netstandard2.1;uap10.0;uap10.0.15138.0 @@ -40,10 +40,19 @@ + + 10.0 + + + + 10.0.15138.0 + + + or '$(TargetFramework)' == 'netstandard1.3' + or '$(TargetFramework)' == 'uap10.0'"> @@ -53,7 +62,8 @@ or '$(TargetFramework)' == 'netcore50' or '$(TargetFramework)' == 'netcoreapp1.0' or '$(TargetFramework)' == 'netcoreapp1.1' - or '$(TargetFramework)' == 'netstandard1.3'"> + or '$(TargetFramework)' == 'netstandard1.3' + or '$(TargetFramework)' == 'uap10.0'"> @@ -66,7 +76,7 @@ or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' - or '$(TargetFramework)' == 'uap10.0'"> + or '$(TargetFramework)' == 'uap10.0.15138.0'"> @@ -76,7 +86,7 @@ or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' - or '$(TargetFramework)' == 'uap10.0'"> + or '$(TargetFramework)' == 'uap10.0.15138.0'"> @@ -89,7 +99,8 @@ or '$(TargetFramework)' == 'netcore50' or '$(TargetFramework)' == 'netcoreapp1.0' or '$(TargetFramework)' == 'netcoreapp1.1' - or '$(TargetFramework)' == 'netstandard1.3'"> + or '$(TargetFramework)' == 'netstandard1.3' + or '$(TargetFramework)' == 'uap10.0'"> $(DefineConstants);HAS_OLDLOGGER @@ -103,7 +114,8 @@ + and '$(TargetFramework)' != 'netstandard1.3' + and '$(TargetFramework)' != 'uap10.0'"> $(DefineConstants);HAS_PROCESS;HAS_DRAWING;HAS_SERIALIZATION diff --git a/Directory.Build.props b/Directory.Build.props index 60648c8d..ddeabc43 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ True latest Icon.png - https://raw.githubusercontent.com/yungd1plomat/AdvancedSharpAdbClient/main/nuget.png + https://raw.githubusercontent.com/yungd1plomat/AdvancedSharpAdbClient/main/logo.png Apache-2.0 https://github.com/yungd1plomat/AdvancedSharpAdbClient True @@ -43,7 +43,8 @@ True - + False en-US $(DefineConstants);WINDOWS_UWP @@ -52,7 +53,6 @@ .NETCore v5.0 UAP - 10.0.15138.0 10.0.22621.0 diff --git a/Directory.Build.targets b/Directory.Build.targets index df1f024f..577876ad 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -15,7 +15,8 @@ - +