Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Xamarin.MacDev/NullableAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,12 @@ internal sealed class NotNullIfNotNullAttribute : Attribute {
}
}
#endif // !NET

#if NETSTANDARD2_0
namespace System.Runtime.CompilerServices {
// Required polyfill for C# 9 records on netstandard2.0 targets.
// Enables init-only setters (C# 9 'init' keyword) in netstandard2.0 projects.
// See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records
internal static class IsExternalInit { }
}
#endif // NETSTANDARD2_0
25 changes: 25 additions & 0 deletions Xamarin.MacDev/PrivacyPermission.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

namespace Xamarin.MacDev;

/// <summary>
/// Privacy service categories for <c>xcrun simctl privacy</c>.
/// </summary>
public enum PrivacyPermission {
All,
Calendar,
ContactsLimited,
Contacts,
Location,
LocationAlways,
PhotosAdd,
Photos,
MediaLibrary,
Microphone,
Motion,
Reminders,
Siri,
}
2 changes: 1 addition & 1 deletion Xamarin.MacDev/SimCtl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Xamarin.MacDev;
/// </summary>
public class SimCtl {

static readonly string XcrunPath = "/usr/bin/xcrun";
internal static readonly string XcrunPath = "/usr/bin/xcrun";

readonly ICustomLogger log;

Expand Down
15 changes: 15 additions & 0 deletions Xamarin.MacDev/SimulatorAppearance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

namespace Xamarin.MacDev;

/// <summary>
/// The UI appearance (theme) of a simulator.
/// Used with <c>xcrun simctl ui &lt;udid&gt; appearance</c>.
/// </summary>
public enum SimulatorAppearance {
Light,
Dark,
}
76 changes: 76 additions & 0 deletions Xamarin.MacDev/SimulatorLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Globalization;

#nullable enable

namespace Xamarin.MacDev;

/// <summary>
/// Wraps <c>xcrun simctl location</c> operations for setting, clearing,
/// and simulating GPS routes on a simulator.
/// </summary>
public class SimulatorLocation {

readonly ICustomLogger log;
readonly SimCtl simctl;

internal SimulatorLocation (ICustomLogger log, SimCtl simctl)
{
this.log = log;
this.simctl = simctl;
}

/// <summary>
/// Sets the simulated GPS location on the simulator.
/// Wraps <c>xcrun simctl location &lt;udid&gt; set &lt;lat&gt;,&lt;lng&gt;</c>.
/// </summary>
public bool Set (string udidOrName, double latitude, double longitude)
{
if (string.IsNullOrWhiteSpace (udidOrName))
throw new ArgumentException ("Simulator UDID or name must not be null or empty.", nameof (udidOrName));

var coords = string.Format (CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude);
var result = simctl.Run ("location", udidOrName, "set", coords);
var success = result is not null;
if (success)
log.LogInfo ("simctl location set '{0}' to {1} succeeded.", udidOrName, coords);
return success;
}

/// <summary>
/// Clears the simulated GPS location on the simulator.
/// Wraps <c>xcrun simctl location &lt;udid&gt; clear</c>.
/// </summary>
public bool Clear (string udidOrName)
{
if (string.IsNullOrWhiteSpace (udidOrName))
throw new ArgumentException ("Simulator UDID or name must not be null or empty.", nameof (udidOrName));

var result = simctl.Run ("location", udidOrName, "clear");
var success = result is not null;
if (success)
log.LogInfo ("simctl location clear '{0}' succeeded.", udidOrName);
return success;
}

/// <summary>
/// Runs a GPX route simulation on the simulator.
/// Wraps <c>xcrun simctl location &lt;udid&gt; run &lt;gpxPath&gt;</c>.
/// </summary>
public bool Run (string udidOrName, string gpxPath)
{
if (string.IsNullOrWhiteSpace (udidOrName))
throw new ArgumentException ("Simulator UDID or name must not be null or empty.", nameof (udidOrName));
if (string.IsNullOrWhiteSpace (gpxPath))
throw new ArgumentException ("GPX path must not be null or empty.", nameof (gpxPath));

var result = simctl.Run ("location", udidOrName, "run", gpxPath);
var success = result is not null;
if (success)
log.LogInfo ("simctl location run '{0}' with '{1}' succeeded.", udidOrName, gpxPath);
return success;
}
}
90 changes: 90 additions & 0 deletions Xamarin.MacDev/SimulatorPrivacy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

#nullable enable

namespace Xamarin.MacDev;

/// <summary>
/// Wraps <c>xcrun simctl privacy</c> operations for granting, revoking,
/// and resetting simulator privacy permissions.
/// </summary>
public class SimulatorPrivacy {

readonly ICustomLogger log;
readonly SimCtl simctl;

internal SimulatorPrivacy (ICustomLogger log, SimCtl simctl)
{
this.log = log;
this.simctl = simctl;
}

/// <summary>
/// Grants a privacy permission for all apps or a specific bundle on the simulator.
/// Wraps <c>xcrun simctl privacy &lt;udid&gt; grant &lt;service&gt; [bundleId]</c>.
/// </summary>
public bool Grant (string udidOrName, PrivacyPermission permission, string? bundleIdentifier = null)
{
return RunPrivacy ("grant", udidOrName, permission, bundleIdentifier);
}

/// <summary>
/// Revokes a privacy permission for all apps or a specific bundle on the simulator.
/// Wraps <c>xcrun simctl privacy &lt;udid&gt; revoke &lt;service&gt; [bundleId]</c>.
/// </summary>
public bool Revoke (string udidOrName, PrivacyPermission permission, string? bundleIdentifier = null)
{
return RunPrivacy ("revoke", udidOrName, permission, bundleIdentifier);
}

/// <summary>
/// Resets a privacy permission for all apps or a specific bundle on the simulator.
/// Wraps <c>xcrun simctl privacy &lt;udid&gt; reset &lt;service&gt; [bundleId]</c>.
/// </summary>
public bool Reset (string udidOrName, PrivacyPermission permission, string? bundleIdentifier = null)
{
return RunPrivacy ("reset", udidOrName, permission, bundleIdentifier);
}

bool RunPrivacy (string action, string udidOrName, PrivacyPermission permission, string? bundleIdentifier)
{
if (string.IsNullOrWhiteSpace (udidOrName))
throw new ArgumentException ("Simulator UDID or name must not be null or empty.", nameof (udidOrName));

var service = ToSimctlServiceName (permission);

string? result;
if (!string.IsNullOrWhiteSpace (bundleIdentifier))
result = simctl.Run ("privacy", udidOrName, action, service, bundleIdentifier!);
else
result = simctl.Run ("privacy", udidOrName, action, service);

var success = result is not null;
if (success)
log.LogInfo ("simctl privacy {0} {1} {2} succeeded.", udidOrName, action, service);
return success;
}

public static string ToSimctlServiceName (PrivacyPermission permission)
{
return permission switch {
PrivacyPermission.All => "all",
PrivacyPermission.Calendar => "calendar",
PrivacyPermission.ContactsLimited => "contacts-limited",
PrivacyPermission.Contacts => "contacts",
PrivacyPermission.Location => "location",
PrivacyPermission.LocationAlways => "location-always",
PrivacyPermission.PhotosAdd => "photos-add",
PrivacyPermission.Photos => "photos",
PrivacyPermission.MediaLibrary => "media-library",
PrivacyPermission.Microphone => "microphone",
PrivacyPermission.Motion => "motion",
PrivacyPermission.Reminders => "reminders",
PrivacyPermission.Siri => "siri",
_ => throw new ArgumentOutOfRangeException (nameof (permission), permission, null),
};
}
}
Loading
Loading