mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
Refactor: Simplify codebase and remove unused methods
Removed unused methods across multiple files, including `GetContrastAsync`, `GetVolumeAsync`, and `SaveCurrentSettingsAsync` in `DdcCiController.cs` and `WmiController.cs`. Simplified the `IMonitorController` interface by removing redundant methods. Replaced `ProcessThemeChangeAsync` with a synchronous `ProcessThemeChange` in `LightSwitchListener.cs`. Changed `ParseVcpCodesToIntegers` visibility to private in `MonitorFeatureHelper.cs` and removed related helper methods. Eliminated retry logic by removing `ExecuteWithRetryAsync` in `RetryHelper.cs`. Simplified `SetBrightnessAsync` in `MonitorManager.cs` using a generic helper. Streamlined `DisplayName` in `MonitorViewModel.cs` and removed unnecessary property change notifications. These changes reduce complexity, improve maintainability, and streamline the codebase by removing unused or redundant functionality.
This commit is contained in:
@@ -143,24 +143,12 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get monitor contrast
|
||||
/// </summary>
|
||||
public Task<BrightnessInfo> GetContrastAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
=> GetVcpFeatureAsync(monitor, NativeConstants.VcpCodeContrast, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Set monitor contrast
|
||||
/// </summary>
|
||||
public Task<MonitorOperationResult> SetContrastAsync(Monitor monitor, int contrast, CancellationToken cancellationToken = default)
|
||||
=> SetVcpFeatureAsync(monitor, NativeConstants.VcpCodeContrast, contrast, 0, 100, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get monitor volume
|
||||
/// </summary>
|
||||
public Task<BrightnessInfo> GetVolumeAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
=> GetVcpFeatureAsync(monitor, NativeConstants.VcpCodeVolume, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Set monitor volume
|
||||
/// </summary>
|
||||
@@ -441,37 +429,6 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save current settings
|
||||
/// </summary>
|
||||
public async Task<MonitorOperationResult> SaveCurrentSettingsAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
if (monitor.Handle == IntPtr.Zero)
|
||||
{
|
||||
return MonitorOperationResult.Failure("Invalid monitor handle");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (SaveCurrentSettings(monitor.Handle))
|
||||
{
|
||||
return MonitorOperationResult.Success();
|
||||
}
|
||||
|
||||
var lastError = GetLastError();
|
||||
return MonitorOperationResult.Failure($"Failed to save settings", (int)lastError);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return MonitorOperationResult.Failure($"Exception saving settings: {ex.Message}");
|
||||
}
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discover supported monitors
|
||||
/// </summary>
|
||||
@@ -640,17 +597,6 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate monitor connection status.
|
||||
/// Uses quick VCP read instead of full capabilities retrieval.
|
||||
/// </summary>
|
||||
public async Task<bool> ValidateConnectionAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Task.Run(
|
||||
() => monitor.Handle != IntPtr.Zero && DdcCiNative.QuickConnectionCheck(monitor.Handle),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get physical monitors with retry logic to handle Windows API occasionally returning NULL handles
|
||||
/// </summary>
|
||||
|
||||
@@ -264,31 +264,6 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate monitor connection status
|
||||
/// </summary>
|
||||
public async Task<bool> ValidateConnectionAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to read current brightness to validate connection
|
||||
using var connection = new WmiConnection(WmiNamespace);
|
||||
var query = $"SELECT CurrentBrightness FROM {BrightnessQueryClass} WHERE InstanceName='{monitor.InstanceName}'";
|
||||
var results = connection.CreateQuery(query).ToList();
|
||||
return results.Count > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"WMI ValidateConnection failed for {monitor.InstanceName}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get user-friendly name from WMI object
|
||||
/// </summary>
|
||||
@@ -348,69 +323,12 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if administrator privileges are required
|
||||
/// </summary>
|
||||
public static bool RequiresElevation()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var connection = new WmiConnection(WmiNamespace);
|
||||
var query = $"SELECT * FROM {BrightnessMethodClass}";
|
||||
var results = connection.CreateQuery(query).ToList();
|
||||
|
||||
foreach (var obj in results)
|
||||
{
|
||||
// Try to call method to check permissions
|
||||
try
|
||||
{
|
||||
using (WmiMethod method = obj.GetMethod("WmiSetBrightness"))
|
||||
using (WmiMethodParameters inParams = method.CreateInParameters())
|
||||
{
|
||||
inParams.SetPropertyValue("Timeout", "0");
|
||||
inParams.SetPropertyValue("Brightness", "50");
|
||||
obj.ExecuteMethod<uint>(method, inParams, out WmiMethodParameters outParams);
|
||||
return false; // If successful, no elevation required
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return true; // Administrator privileges required
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Other errors may not be permission issues
|
||||
Logger.LogDebug($"WMI RequiresElevation check error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Cannot determine, assume privileges required
|
||||
Logger.LogWarning($"WMI RequiresElevation check failed: {ex.Message}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extended features not supported by WMI
|
||||
public Task<BrightnessInfo> GetContrastAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(BrightnessInfo.Invalid);
|
||||
}
|
||||
|
||||
public Task<MonitorOperationResult> SetContrastAsync(Monitor monitor, int contrast, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(MonitorOperationResult.Failure("Contrast control not supported via WMI"));
|
||||
}
|
||||
|
||||
public Task<BrightnessInfo> GetVolumeAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(BrightnessInfo.Invalid);
|
||||
}
|
||||
|
||||
public Task<MonitorOperationResult> SetVolumeAsync(Monitor monitor, int volume, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(MonitorOperationResult.Failure("Volume control not supported via WMI"));
|
||||
@@ -443,11 +361,6 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
return Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
public Task<MonitorOperationResult> SaveCurrentSettingsAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(MonitorOperationResult.Failure("Save settings not supported via WMI"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
@@ -53,22 +53,6 @@ namespace PowerDisplay.Common.Interfaces
|
||||
/// <returns>List of monitors</returns>
|
||||
Task<IEnumerable<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Validates monitor connection status
|
||||
/// </summary>
|
||||
/// <param name="monitor">Monitor object</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Whether the monitor is connected</returns>
|
||||
Task<bool> ValidateConnectionAsync(Monitor monitor, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets monitor contrast
|
||||
/// </summary>
|
||||
/// <param name="monitor">Monitor object</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Contrast information</returns>
|
||||
Task<BrightnessInfo> GetContrastAsync(Monitor monitor, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sets monitor contrast
|
||||
/// </summary>
|
||||
@@ -78,14 +62,6 @@ namespace PowerDisplay.Common.Interfaces
|
||||
/// <returns>Operation result</returns>
|
||||
Task<MonitorOperationResult> SetContrastAsync(Monitor monitor, int contrast, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets monitor volume
|
||||
/// </summary>
|
||||
/// <param name="monitor">Monitor object</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Volume information</returns>
|
||||
Task<BrightnessInfo> GetVolumeAsync(Monitor monitor, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sets monitor volume
|
||||
/// </summary>
|
||||
@@ -137,14 +113,6 @@ namespace PowerDisplay.Common.Interfaces
|
||||
/// <returns>Capabilities string</returns>
|
||||
Task<string> GetCapabilitiesStringAsync(Monitor monitor, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Saves current settings to monitor
|
||||
/// </summary>
|
||||
/// <param name="monitor">Monitor object</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Operation result</returns>
|
||||
Task<MonitorOperationResult> SaveCurrentSettingsAsync(Monitor monitor, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Releases resources
|
||||
/// </summary>
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace PowerDisplay.Common.Services
|
||||
Logger.LogInfo($"{LogPrefix} Theme change event received");
|
||||
|
||||
// Process the theme change
|
||||
_ = Task.Run(() => ProcessThemeChangeAsync(), CancellationToken.None);
|
||||
_ = Task.Run(() => ProcessThemeChange(), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace PowerDisplay.Common.Services
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessThemeChangeAsync()
|
||||
private void ProcessThemeChange()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -149,8 +149,6 @@ namespace PowerDisplay.Common.Services
|
||||
{
|
||||
Logger.LogError($"{LogPrefix} Failed to process theme change: {ex.Message}");
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using ManagedCommon;
|
||||
using PowerDisplay.Common.Drivers;
|
||||
|
||||
namespace PowerDisplay.Common.Utils
|
||||
@@ -74,7 +73,7 @@ namespace PowerDisplay.Common.Utils
|
||||
/// Parse VCP codes from string list to integer set
|
||||
/// Handles both hex formats: "0x10" and "10"
|
||||
/// </summary>
|
||||
public static HashSet<int> ParseVcpCodesToIntegers(IReadOnlyList<string>? vcpCodes)
|
||||
private static HashSet<int> ParseVcpCodesToIntegers(IReadOnlyList<string>? vcpCodes)
|
||||
{
|
||||
var result = new HashSet<int>();
|
||||
|
||||
@@ -105,22 +104,5 @@ namespace PowerDisplay.Common.Utils
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a specific VCP code is supported
|
||||
/// </summary>
|
||||
public static bool SupportsVcpCode(IReadOnlyList<string>? vcpCodes, int vcpCode)
|
||||
{
|
||||
var parsed = ParseVcpCodesToIntegers(vcpCodes);
|
||||
return parsed.Contains(vcpCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format VCP code for display (e.g., 0x10 -> "0x10")
|
||||
/// </summary>
|
||||
public static string FormatVcpCode(int vcpCode)
|
||||
{
|
||||
return $"0x{vcpCode:X2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace PowerDisplay.Common.Utils
|
||||
@@ -25,86 +24,6 @@ namespace PowerDisplay.Common.Utils
|
||||
/// </summary>
|
||||
public const int DefaultMaxRetries = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an async operation with retry logic.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The return type of the operation.</typeparam>
|
||||
/// <param name="operation">The async operation to execute.</param>
|
||||
/// <param name="isValid">Predicate to determine if the result is valid.</param>
|
||||
/// <param name="maxRetries">Maximum number of retry attempts (default: 3).</param>
|
||||
/// <param name="delayMs">Delay between retries in milliseconds (default: 100).</param>
|
||||
/// <param name="operationName">Optional name for logging purposes.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result of the operation, or default if all retries failed.</returns>
|
||||
public static async Task<T?> ExecuteWithRetryAsync<T>(
|
||||
Func<Task<T?>> operation,
|
||||
Func<T?, bool> isValid,
|
||||
int maxRetries = DefaultMaxRetries,
|
||||
int delayMs = DefaultRetryDelayMs,
|
||||
string? operationName = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await operation();
|
||||
|
||||
if (isValid(result))
|
||||
{
|
||||
if (attempt > 0 && !string.IsNullOrEmpty(operationName))
|
||||
{
|
||||
Logger.LogDebug($"[Retry] {operationName} succeeded on attempt {attempt + 1}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries - 1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(operationName))
|
||||
{
|
||||
Logger.LogWarning($"[Retry] {operationName} returned invalid result on attempt {attempt + 1}, retrying...");
|
||||
}
|
||||
|
||||
await Task.Delay(delayMs, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (attempt < maxRetries - 1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(operationName))
|
||||
{
|
||||
Logger.LogWarning($"[Retry] {operationName} failed on attempt {attempt + 1}: {ex.Message}, retrying...");
|
||||
}
|
||||
|
||||
await Task.Delay(delayMs, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(operationName))
|
||||
{
|
||||
Logger.LogWarning($"[Retry] {operationName} failed after {maxRetries} attempts: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(operationName))
|
||||
{
|
||||
Logger.LogWarning($"[Retry] {operationName} failed after {maxRetries} attempts");
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a synchronous operation with retry logic.
|
||||
/// </summary>
|
||||
|
||||
@@ -225,15 +225,5 @@ namespace PowerDisplay.Common.Utils
|
||||
{
|
||||
return CodeNames.TryGetValue(code, out var name) ? name : $"Unknown (0x{code:X2})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a VCP code has a known name
|
||||
/// </summary>
|
||||
public static bool HasName(byte code) => CodeNames.ContainsKey(code);
|
||||
|
||||
/// <summary>
|
||||
/// Get all known VCP codes
|
||||
/// </summary>
|
||||
public static IEnumerable<byte> GetAllKnownCodes() => CodeNames.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,12 +188,5 @@ namespace PowerDisplay.Common.Utils
|
||||
|
||||
return $"0x{value:X2}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a VCP code has value name mappings
|
||||
/// </summary>
|
||||
/// <param name="vcpCode">VCP code to check</param>
|
||||
/// <returns>True if value names are available</returns>
|
||||
public static bool HasValueNames(byte vcpCode) => ValueNames.ContainsKey(vcpCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,6 @@ namespace PowerDisplay.Commands
|
||||
Logger.LogError($"Command execution failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +101,5 @@ namespace PowerDisplay.Commands
|
||||
Logger.LogError($"Command<T> execution failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,45 +356,13 @@ namespace PowerDisplay.Core
|
||||
/// <summary>
|
||||
/// Set brightness of the specified monitor
|
||||
/// </summary>
|
||||
public async Task<MonitorOperationResult> SetBrightnessAsync(string monitorId, int brightness, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var monitor = GetMonitor(monitorId);
|
||||
if (monitor == null)
|
||||
{
|
||||
Logger.LogError($"Monitor not found: {monitorId}");
|
||||
return MonitorOperationResult.Failure("Monitor not found");
|
||||
}
|
||||
|
||||
var controller = await GetControllerForMonitorAsync(monitor, cancellationToken);
|
||||
if (controller == null)
|
||||
{
|
||||
Logger.LogError($"No controller available for monitor {monitorId}");
|
||||
return MonitorOperationResult.Failure("No controller available for this monitor");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await controller.SetBrightnessAsync(monitor, brightness, cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
// Update monitor status
|
||||
monitor.UpdateStatus(brightness, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If setting fails, monitor may be unavailable
|
||||
monitor.IsAvailable = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
monitor.IsAvailable = false;
|
||||
return MonitorOperationResult.Failure($"Exception setting brightness: {ex.Message}");
|
||||
}
|
||||
}
|
||||
public Task<MonitorOperationResult> SetBrightnessAsync(string monitorId, int brightness, CancellationToken cancellationToken = default)
|
||||
=> ExecuteMonitorOperationAsync(
|
||||
monitorId,
|
||||
brightness,
|
||||
(ctrl, mon, val, ct) => ctrl.SetBrightnessAsync(mon, val, ct),
|
||||
(mon, val) => mon.UpdateStatus(val, true),
|
||||
cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Set brightness of all monitors
|
||||
|
||||
@@ -275,22 +275,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
public int MonitorNumber => _monitor.MonitorNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name - includes monitor number when multiple monitors are visible
|
||||
/// Gets the display name
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
// Check if there's more than one visible monitor and MonitorNumber is valid
|
||||
// Set the limit to zero for debugging.
|
||||
/* if (_mainViewModel != null && _mainViewModel.Monitors.Count > 0 && MonitorNumber > 0)
|
||||
{
|
||||
return $"{Name} {MonitorNumber}";
|
||||
} */
|
||||
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
public string DisplayName => Name;
|
||||
|
||||
public string Manufacturer => _monitor.Manufacturer;
|
||||
|
||||
@@ -573,12 +560,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
// Notify percentage properties when actual values change
|
||||
if (propertyName == nameof(Contrast))
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ContrastPercent)));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user