using Microsoft.Win32; using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Reflection; namespace Optimizer { public sealed class ColorOverrider { public static event Action SystemColorsChanging; public static event Action SystemColorsChanged; private int[] OriginalColors { get; } private IReadOnlyDictionary KnownOriginalColors; private int[] _colorTable; private readonly FieldInfo _colorTableField; private readonly PropertyInfo _threadDataProperty; public ColorOverrider() { // force init color table byte unused = SystemColors.Window.R; var systemDrawingAssembly = typeof(Color).Assembly; string colorTableField = "colorTable"; _colorTableField = systemDrawingAssembly.GetType("System.Drawing.KnownColorTable") .GetField(colorTableField, BindingFlags.Static | BindingFlags.NonPublic); _colorTable = readColorTable(); SystemEvents.UserPreferenceChanging += userPreferenceChanging; OriginalColors = _colorTable.ToArray(); KnownOriginalColors = KnownColors.Cast() .ToDictionary(i => i, i => OriginalColors[i]); _threadDataProperty = systemDrawingAssembly.GetType("System.Drawing.SafeNativeMethods") .GetNestedType("Gdip", BindingFlags.NonPublic) .GetProperty("ThreadData", BindingFlags.Static | BindingFlags.NonPublic); string systemBrushesKeyField = "SystemBrushesKey"; SystemBrushesKey = typeof(SystemBrushes) .GetField(systemBrushesKeyField, BindingFlags.Static | BindingFlags.NonPublic) ?.GetValue(null); SystemPensKey = typeof(SystemPens) .GetField("SystemPensKey", BindingFlags.Static | BindingFlags.NonPublic) ?.GetValue(null); } private void userPreferenceChanging(object sender, UserPreferenceChangingEventArgs e) { if (e.Category != UserPreferenceCategory.Color) return; _colorTable = readColorTable(); fireColorsChangedEvents(); } private static void fireColorsChangedEvents() { SystemColorsChanging?.Invoke(); SystemColorsChanged?.Invoke(); } private int[] readColorTable() => (int[])_colorTableField.GetValue(null); public void SetColor(KnownColor knownColor, int argb) { setColor(knownColor, argb); if (SystemBrushesKey != null) ThreadData[SystemBrushesKey] = null; if (SystemPensKey != null) ThreadData[SystemPensKey] = null; fireColorsChangedEvents(); } private void setColor(KnownColor knownColor, int argb) => _colorTable[(int)knownColor] = argb; public int GetOriginalColor(KnownColor knownColor) => OriginalColors[(int)knownColor]; public int GetColor(KnownColor knownColor) { if (!KnownColors.Contains(knownColor)) throw new ArgumentException(); return _colorTable[(int)knownColor]; } public IReadOnlyDictionary Save() => KnownColors.Cast() .ToDictionary(i => i, i => _colorTable[i]); public void Load(IReadOnlyDictionary saved) { foreach (var color in KnownColors) { int v; var value = saved.TryGetValue((int)color, out v); setColor(color, v); } if (SystemBrushesKey != null) ThreadData[SystemBrushesKey] = null; if (SystemPensKey != null) ThreadData[SystemPensKey] = null; fireColorsChangedEvents(); } public void Reset(KnownColor color) => SetColor(color, OriginalColors[(int)color]); public void ResetAll() => Load(KnownOriginalColors); private IDictionary ThreadData => (IDictionary)_threadDataProperty.GetValue(null, null); private object SystemBrushesKey { get; } private object SystemPensKey { get; } public readonly HashSet KnownColors = new HashSet( new[] { SystemColors.Control, SystemColors.ControlText, SystemColors.ButtonFace, // menu gradient SystemColors.ButtonShadow, // menu border SystemColors.Window, SystemColors.WindowText, SystemColors.GrayText, SystemColors.HotTrack, SystemColors.Highlight, SystemColors.HighlightText, SystemColors.ActiveCaption, SystemColors.GradientActiveCaption, SystemColors.InactiveCaption, SystemColors.GradientInactiveCaption, SystemColors.ActiveBorder }.Select(_ => _.ToKnownColor()) ); } }