Skip to content

Commit

Permalink
feat: Add automatic switching between dark and light color themes
Browse files Browse the repository at this point in the history
add source

xx
  • Loading branch information
ZGGSONG committed Feb 5, 2024
1 parent 91c7e95 commit 3c91d13
Show file tree
Hide file tree
Showing 12 changed files with 458 additions and 81 deletions.
7 changes: 1 addition & 6 deletions STranslate.Model/ConfigModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class ConfigModel
/// <summary>
/// 是否亮色模式
/// </summary>
public bool IsBright { get; set; }
public ThemeType ThemeType { get; set; }

/// <summary>
/// 是否跟随鼠标
Expand Down Expand Up @@ -99,11 +99,6 @@ public class ConfigModel
/// </summary>
public bool IsShowPreference { get; set; } = true;

/// <summary>
/// 是否显示切换主题图标
/// </summary>
public bool IsShowSwitchTheme { get; set; } = true;

/// <summary>
/// 是否显示打开鼠标划词图标
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions STranslate.Model/ConstStr.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

using Microsoft.Win32;

namespace STranslate.Model
{
public static class ConstStr
Expand Down Expand Up @@ -40,5 +42,9 @@ public static class ConstStr

public const string LOADING = "加载中...";
public const string UNLOADING = "加载结束...";

public const RegistryHive REGISTRYHIVE = RegistryHive.CurrentUser;
public const string REGISTRY = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
public const string REGISTRYKEY = "SystemUsesLightTheme";
}
}
10 changes: 10 additions & 0 deletions STranslate.Model/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,16 @@ public enum PerferenceType
About
}

/// <summary>
/// 主题类型
/// </summary>
public enum ThemeType
{
Light,
Dark,
Auto,
}

/// <summary>
/// 获取Description
/// </summary>
Expand Down
341 changes: 341 additions & 0 deletions STranslate.Util/RegistryMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace STranslate.Util
{
/// <summary>
/// https://www.codeproject.com/Articles/4502/RegistryMonitor-a-NET-wrapper-class-for-RegNotifyC
/// </summary>
public class RegistryMonitor : IDisposable
{
#region P/Invoke

[DllImport("advapi32.dll", SetLastError = true)]
private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern int RegCloseKey(IntPtr hKey);

private const int KEY_QUERY_VALUE = 0x0001;
private const int KEY_NOTIFY = 0x0010;
private const int STANDARD_RIGHTS_READ = 0x00020000;

private static readonly IntPtr HKEY_CLASSES_ROOT = new(unchecked((int)0x80000000));
private static readonly IntPtr HKEY_CURRENT_USER = new(unchecked((int)0x80000001));
private static readonly IntPtr HKEY_LOCAL_MACHINE = new(unchecked((int)0x80000002));
private static readonly IntPtr HKEY_USERS = new(unchecked((int)0x80000003));
private static readonly IntPtr HKEY_PERFORMANCE_DATA = new(unchecked((int)0x80000004));
private static readonly IntPtr HKEY_CURRENT_CONFIG = new(unchecked((int)0x80000005));

#endregion

#region Event handling

/// <summary>
/// Occurs when the specified registry key has changed.
/// </summary>
public event Action<string>? RegChanged;

/// <summary>
/// Occurs when the access to the registry fails.
/// </summary>
public event ErrorEventHandler? Error;

/// <summary>
/// Raises the <see cref="RegChanged"/> event.
/// </summary>
/// <remarks>
/// <p>
/// <b>OnRegChanged</b> is called when the specified registry key has changed.
/// </p>
/// <note type="inheritinfo">
/// When overriding <see cref="OnRegChanged"/> in a derived class, be sure to call
/// the base class's <see cref="OnRegChanged"/> method.
/// </note>
/// </remarks>
protected virtual void OnRegChanged(string arg)
{
RegChanged?.Invoke(arg);
}

/// <summary>
/// Raises the <see cref="Error"/> event.
/// </summary>
/// <param name="e">The <see cref="Exception"/> which occured while watching the registry.</param>
/// <remarks>
/// <p>
/// <b>OnError</b> is called when an exception occurs while watching the registry.
/// </p>
/// <note type="inheritinfo">
/// When overriding <see cref="OnError"/> in a derived class, be sure to call
/// the base class's <see cref="OnError"/> method.
/// </note>
/// </remarks>
protected virtual void OnError(Exception e)
{
Error?.Invoke(this, new ErrorEventArgs(e));
}

#endregion

#region Private member variables

private IntPtr _registryHive;
private string _registrySubName = "";
private string _monitorKey = "";
private readonly object _threadLock = new();
private Thread? _thread;
private bool _disposed = false;
private readonly ManualResetEvent _eventTerminate = new(false);

private RegChangeNotifyFilter _regFilter = RegChangeNotifyFilter.Key | RegChangeNotifyFilter.Attribute |
RegChangeNotifyFilter.Value | RegChangeNotifyFilter.Security;

#endregion

/// <summary>
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="monitorKey">The monitor key.</param>
public RegistryMonitor(string name, string monitorKey = "")
{
if (name == null || name.Length == 0)
throw new ArgumentNullException("name");

InitRegistryKey(name, monitorKey);
}

/// <summary>
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
/// </summary>
/// <param name="registryHive">The registry hive.</param>
/// <param name="subKey">The sub key.</param>
/// <param name="monitorKey">Should Monitor key.</param>
public RegistryMonitor(RegistryHive registryHive, string subKey, string monitorKey)
{
InitRegistryKey(registryHive, subKey, monitorKey);
}

/// <summary>
/// Disposes this object.
/// </summary>
public void Dispose()
{
Stop();
_disposed = true;
GC.SuppressFinalize(this);
}

/// <summary>
/// Gets or sets the <see cref="RegChangeNotifyFilter">RegChangeNotifyFilter</see>.
/// </summary>
public RegChangeNotifyFilter RegChangeNotifyFilter
{
get { return _regFilter; }
set
{
lock (_threadLock)
{
if (IsMonitoring)
throw new InvalidOperationException("Monitoring thread is already running");

_regFilter = value;
}
}
}

#region Initialization

private void InitRegistryKey(RegistryHive hive, string name, string key = "")
{
_registryHive = hive switch
{
RegistryHive.ClassesRoot => HKEY_CLASSES_ROOT,
RegistryHive.CurrentConfig => HKEY_CURRENT_CONFIG,
RegistryHive.CurrentUser => HKEY_CURRENT_USER,
RegistryHive.LocalMachine => HKEY_LOCAL_MACHINE,
RegistryHive.PerformanceData => HKEY_PERFORMANCE_DATA,
RegistryHive.Users => HKEY_USERS,
_ => throw new InvalidEnumArgumentException("hive", (int)hive, typeof(RegistryHive)),
};
_registrySubName = name;
_monitorKey = key;
}

private void InitRegistryKey(string name, string key = "")
{
var nameParts = name.Split('\\');

switch (nameParts[0])
{
case "HKEY_CLASSES_ROOT":
case "HKCR":
_registryHive = HKEY_CLASSES_ROOT;
break;

case "HKEY_CURRENT_USER":
case "HKCU":
_registryHive = HKEY_CURRENT_USER;
break;

case "HKEY_LOCAL_MACHINE":
case "HKLM":
_registryHive = HKEY_LOCAL_MACHINE;
break;

case "HKEY_USERS":
_registryHive = HKEY_USERS;
break;

case "HKEY_CURRENT_CONFIG":
_registryHive = HKEY_CURRENT_CONFIG;
break;

default:
_registryHive = IntPtr.Zero;
throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", "value");
}

_registrySubName = string.Join("\\", nameParts, 1, nameParts.Length - 1);
_monitorKey = key;
}

#endregion

/// <summary>
/// <b>true</b> if this <see cref="RegistryMonitor"/> object is currently monitoring;
/// otherwise, <b>false</b>.
/// </summary>
public bool IsMonitoring
{
get { return _thread != null; }
}

/// <summary>
/// Start monitoring.
/// </summary>
public void Start()
{
if (_disposed)
throw new ObjectDisposedException(null, "This instance is already disposed");

lock (_threadLock)
{
if (!IsMonitoring)
{
_eventTerminate.Reset();
_thread = new Thread(new ThreadStart(MonitorThread))
{
IsBackground = true
};
_thread.Start();
}
}
}

/// <summary>
/// Stops the monitoring thread.
/// </summary>
public void Stop()
{
if (_disposed)
throw new ObjectDisposedException(null, "This instance is already disposed");

lock (_threadLock)
{
var thread = _thread;
if (thread != null)
{
_eventTerminate.Set();
thread.Join();
}
}
}

private void MonitorThread()
{
try
{
ThreadLoop();
}
catch (Exception e)
{
OnError(e);
}
_thread = null;
}

private void ThreadLoop()
{
int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out IntPtr registryKey);
if (result != 0)
throw new Win32Exception(result);

try
{
while (!_eventTerminate.WaitOne(0, true))
{
using AutoResetEvent _eventNotify = new(false);
result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.SafeWaitHandle.DangerousGetHandle(), true);
if (result != 0)
throw new Win32Exception(result);

WaitHandle[] waitHandles = [_eventNotify, _eventTerminate];
if (WaitHandle.WaitAny(waitHandles) == 0)
{
// 获取变化的参数
string changedValue = GetRegistryValue(_registrySubName, _monitorKey);
OnRegChanged(changedValue);
}
}
}
finally
{
if (registryKey != IntPtr.Zero)
{
_ = RegCloseKey(registryKey);
}
}
}

/// <summary>
/// 获取注册表键值的方法
/// </summary>
/// <param name="registryKey"></param>
/// <param name="valueName"></param>
/// <returns></returns>
public static string GetRegistryValue(string registryKey, string? valueName)
{
if (string.IsNullOrEmpty(valueName)) return "";
using var key = Registry.CurrentUser.OpenSubKey(registryKey);
return key?.GetValue(valueName)?.ToString() ?? "";
}
}

/// <summary>
/// Filter for notifications reported by <see cref="RegistryMonitor"/>.
/// </summary>
[Flags]
public enum RegChangeNotifyFilter
{
/// <summary>Notify the caller if a subkey is added or deleted.</summary>
Key = 1,
/// <summary>Notify the caller of changes to the attributes of the key,
/// such as the security descriptor information.</summary>
Attribute = 2,
/// <summary>Notify the caller of changes to a value of the key. This can
/// include adding or deleting a value, or changing an existing value.</summary>
Value = 4,
/// <summary>Notify the caller of changes to the security descriptor
/// of the key.</summary>
Security = 8,
}
}
Loading

0 comments on commit 3c91d13

Please sign in to comment.