[Run] Fix dark mode detection code, plus refactor (#37324)

* Fix risky int cast in dark mode detection.

* Refactored Helper and Manager classes. New unit tests and changes to support Registry access mocking.

* Spelling update.

* Improve documentation for the registry-related classes.

* Fix issue with UpdateTheme raised in review. Enhance documentation. Rewrite tests to use parameterised unit tests, and expand to cover more cases.
This commit is contained in:
Dave Rayment
2025-02-20 10:47:30 +00:00
committed by GitHub
parent c6f9701818
commit 727de3e1fc
7 changed files with 426 additions and 104 deletions

View File

@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Win32;
namespace PowerLauncher.Services;
#nullable enable
/// <summary>
/// Provides methods for interacting with the Windows Registry or an equivalent key-value data
/// store.
/// </summary>
public interface IRegistryService
{
/// <summary>
/// Retrieves the value associated with the specified name, in the specified registry key.
/// If the name is not found in the specified key, returns the specified default value, or
/// <c>null</c> if the specified key does not exist.
/// </summary>
/// <param name="keyName">The full registry path of the key, beginning with a valid registry
/// root, such as "HKEY_CURRENT_USER".</param>
/// <param name="valueName">The name of the name/value pair.</param>
/// <param name="defaultValue">The value to return if <see cref="valueName"/> does not exist.
/// </param>
/// <returns><c>null</c> if the subkey specified by <paramref name="keyName"/> does not exist;
/// otherwise, the value associated with <paramref name="valueName"/>, or
/// <paramref name="defaultValue"/> if <paramref name="valueName"/> is not found.</returns>
/// <exception cref="ArgumentException"><paramref name="keyName"/> does not begin with a valid
/// registry root.</exception>
/// <exception cref="UnauthorizedAccessException">Thrown if access to the registry or
/// equivalent store is denied.</exception>
/// <remarks>Implementations may throw additional exceptions depending on their internal
/// storage mechanism.</remarks>
object? GetValue(string keyName, string? valueName, object? defaultValue);
/// <summary>
/// Sets the specified name/value pair on the specified registry key. If the specified key does
/// not exist, it is created.
/// </summary>
/// <param name="keyName">The full registry path of the key, beginning with a valid registry
/// root, such as "HKEY_CURRENT_USER".</param>
/// <param name="valueName">The name of the name/value pair.</param>
/// <param name="value">The value to be stored.</param>
/// <exception cref="ArgumentException">
/// <paramref name="keyName"/> does not begin with a valid registry root.
///
/// -or-
///
/// <paramref name="keyName"> is longer than the maximum length allowed (255 characters).
/// </exception>
/// <exception cref="UnauthorizedAccessException">Access to the key is denied; for example,
/// it is a root-level node, or the key has not been opened with write access.</exception>
void SetValue(string keyName, string? valueName, object value);
/// <summary>
/// Sets the specified name/value pair on the specified registry key. If the specified key does
/// not exist, it is created.
/// </summary>
/// <param name="keyName">The full registry path of the key, beginning with a valid registry
/// root, such as "HKEY_CURRENT_USER".</param>
/// <param name="valueName">The name of the name/value pair.</param>
/// <param name="value">The value to be stored.</param>
/// <param name="valueKind">The registry data type to use when storing the data.</param>
/// <exception cref="ArgumentException">
/// <paramref name="keyName"/> does not begin with a valid registry root.
///
/// -or-
///
/// <paramref name="keyName"> is longer than the maximum length allowed (255 characters).
///
/// -or-
///
/// The type of <paramref name="value"/> did not match the registry data type specified by
/// <paramref name="valueKind"/>, therefore the data could not be converted properly.
/// </exception>
/// <exception cref="UnauthorizedAccessException">Access to the key is denied; for example,
/// it is a root-level node, or the key has not been opened with write access.</exception>
void SetValue(string keyName, string? valueName, object value, RegistryValueKind valueKind);
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO;
using System.Security;
using Microsoft.Win32;
namespace PowerLauncher.Services;
#nullable enable
public class RegistryService : IRegistryService
{
/// <inheritdoc/>
/// <exception cref="SecurityException">The user does not have the permissions required to read
/// from the registry key.</exception>
/// <exception cref="IOException">The <see cref="RegistryKey"/> that contains the specified
/// value has been marked for deletion.</exception>
public object? GetValue(string keyName, string? valueName, object? defaultValue) =>
Registry.GetValue(keyName, valueName, defaultValue);
/// <inheritdoc/>
/// <exception cref="SecurityException">The user does not have the permissions required to
/// create or modify registry keys.</exception>"
public void SetValue(string keyName, string? valueName, object value) =>
Registry.SetValue(keyName, valueName, value);
/// <inheritdoc/>
/// <exception cref="SecurityException">The user does not have the permissions required to
/// create or modify registry keys.</exception>
public void SetValue(string keyName, string? valueName, object value, RegistryValueKind valueKind) =>
Registry.SetValue(keyName, valueName, value, valueKind);
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace PowerLauncher.Services;
/// <summary>
/// Factory for creating instances of <see cref="IRegistryService"/>.
/// </summary>
public static class RegistryServiceFactory
{
/// <summary>
/// Creates the default implementation of <see cref="IRegistryService"/>.
/// </summary>
/// <returns>An instance of the default <see cref="IRegistryService"/> implementation.</returns>
public static IRegistryService Create() => new RegistryService();
}