using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Runtime.InteropServices; using System.Windows.Forms; using Microsoft.Win32; namespace ThemeToggler { static class Program { [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern IntPtr SendMessageTimeout( IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern bool DestroyIcon(IntPtr handle); [DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll", SetLastError = true)] private static extern bool UnregisterHotKey(IntPtr hWnd, int id); private const int WM_SETTINGCHANGE = 0x001A; private const uint HWND_BROADCAST = 0xFFFF; private const uint SMTO_ABORTIFHUNG = 0x0002; private const int HOTKEY_ID = 9000; private const uint MOD_ALT = 0x0001; private const uint MOD_CONTROL = 0x0002; private const uint MOD_WIN = 0x0008; public static NotifyIcon? TrayIcon { get; private set; } private static ToolStripMenuItem? _startupMenuItem; private static IntPtr _currentIconHandle = IntPtr.Zero; [STAThread] static void Main() { ApplicationConfiguration.Initialize(); // Prevents multiple instances using (var mutex = new System.Threading.Mutex(true, "ThemeTogglerMutex", out bool createdNew)) { if (!createdNew) { MessageBox.Show("ThemeToggler כבר פועל ברקע.", "Theme Toggler", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // Set up tray icon TrayIcon = new NotifyIcon(); TrayIcon.Text = "Theme Toggler"; TrayIcon.Click += TrayIcon_Click; // Context Menu var contextMenu = new ContextMenuStrip(); var toggleItem = new ToolStripMenuItem("החלף מצב תצוגה", null, (s, e) => ToggleTheme()); toggleItem.Font = new Font(toggleItem.Font, FontStyle.Bold); contextMenu.Items.Add(toggleItem); contextMenu.Items.Add(new ToolStripSeparator()); _startupMenuItem = new ToolStripMenuItem("הפעל בעליית המחשב", null, Startup_Click); _startupMenuItem.Checked = IsRunAtStartupEnabled(); contextMenu.Items.Add(_startupMenuItem); contextMenu.Items.Add(new ToolStripSeparator()); contextMenu.Items.Add("יציאה", null, (s, e) => Application.Exit()); TrayIcon.ContextMenuStrip = contextMenu; UpdateIcon(); TrayIcon.Visible = true; // Watch registry for external theme changes SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; // Register Thread-Level Hotkey (hWnd = IntPtr.Zero) // This registers the hotkey directly to the message loop thread, avoiding window handle issues. bool registered = RegisterHotKey(IntPtr.Zero, HOTKEY_ID, MOD_WIN | MOD_ALT, 0x54); // Win + Alt + T if (!registered) { // Fallback: Ctrl + Alt + T registered = RegisterHotKey(IntPtr.Zero, HOTKEY_ID, MOD_CONTROL | MOD_ALT, 0x54); if (registered) { // Let the user know via balloon tip TrayIcon.ShowBalloonTip(3000, "Theme Toggler", "קיצור המקשים Win+Alt+T תפוס. הוגדר קיצור דרך חלופי: Ctrl+Alt+T", ToolTipIcon.Info); } else { int errorCode = Marshal.GetLastWin32Error(); MessageBox.Show($"שגיאה ברישום קיצור המקשים.\nקוד שגיאה: {errorCode}\nודא שאין תוכנה אחרת המשתמשת בקיצורים אלו במקביל.", "Theme Toggler", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } // Add message filter to intercept hotkey messages var filter = new HotkeyMessageFilter(); Application.AddMessageFilter(filter); // Run the thread message loop Application.Run(); // Cleanup Application.RemoveMessageFilter(filter); UnregisterHotKey(IntPtr.Zero, HOTKEY_ID); SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged; if (_currentIconHandle != IntPtr.Zero) { DestroyIcon(_currentIconHandle); } TrayIcon.Visible = false; TrayIcon.Dispose(); } } private static void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { if (e.Category == UserPreferenceCategory.General || e.Category == UserPreferenceCategory.Color) { UpdateIcon(); } } private static void TrayIcon_Click(object? sender, EventArgs e) { var mouseEventArgs = e as MouseEventArgs; if (mouseEventArgs != null && mouseEventArgs.Button == MouseButtons.Left) { ToggleTheme(); } } public static void ToggleTheme() { try { bool currentIsLight = IsLightTheme(); SetTheme(!currentIsLight); UpdateIcon(); } catch (Exception ex) { MessageBox.Show($"שגיאה בשינוי ערכת הנושא: {ex.Message}", "Theme Toggler - שגיאה", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private static void UpdateIcon() { if (TrayIcon == null) return; bool isLight = IsLightTheme(); Bitmap bmp = new Bitmap(32, 32); if (isLight) { // Draw Sun using (var g = Graphics.FromImage(bmp)) { g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Transparent); // Sun body using (var brush = new SolidBrush(Color.FromArgb(255, 180, 0))) // Golden orange { g.FillEllipse(brush, 10, 10, 12, 12); } // Sun rays using (var pen = new Pen(Color.FromArgb(255, 180, 0), 2.5f)) { pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; for (int i = 0; i < 8; i++) { double angle = i * Math.PI / 4.0; float x1 = 16f + (float)(8.0 * Math.Cos(angle)); float y1 = 16f + (float)(8.0 * Math.Sin(angle)); float x2 = 16f + (float)(13.0 * Math.Cos(angle)); float y2 = 16f + (float)(13.0 * Math.Sin(angle)); g.DrawLine(pen, x1, y1, x2, y2); } } } TrayIcon.Text = "מצב תצוגה: בהיר\nלחץ להחלפה לכהה"; } else { // Draw Moon using (var g = Graphics.FromImage(bmp)) { g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Transparent); // Moon base (silver-white circle) using (var brush = new SolidBrush(Color.FromArgb(200, 220, 255))) { g.FillEllipse(brush, 6, 6, 20, 20); } // Subtract offset circle g.CompositingMode = CompositingMode.SourceCopy; using (var brush = new SolidBrush(Color.Transparent)) { g.FillEllipse(brush, 12, 4, 20, 20); } } TrayIcon.Text = "מצב תצוגה: כהה\nלחץ להחלפה לבהיר"; } IntPtr hIcon = bmp.GetHicon(); Icon icon = Icon.FromHandle(hIcon); // Set icon var oldIcon = TrayIcon.Icon; IntPtr oldHandle = _currentIconHandle; TrayIcon.Icon = icon; _currentIconHandle = hIcon; // Dispose old icon and bitmap resources to avoid GDI leaks if (oldIcon != null) { oldIcon.Dispose(); } if (oldHandle != IntPtr.Zero) { DestroyIcon(oldHandle); } bmp.Dispose(); } private static bool IsLightTheme() { using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"); if (key != null) { object? val = key.GetValue("AppsUseLightTheme"); if (val is int i) { return i != 0; } } return true; } private static void SetTheme(bool lightMode) { int val = lightMode ? 1 : 0; using (var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", true)) { if (key != null) { key.SetValue("AppsUseLightTheme", val, RegistryValueKind.DWord); key.SetValue("SystemUsesLightTheme", val, RegistryValueKind.DWord); } } // Notify system SendMessageTimeout((IntPtr)HWND_BROADCAST, WM_SETTINGCHANGE, UIntPtr.Zero, "ImmersiveColorSet", SMTO_ABORTIFHUNG, 1000, out _); } private static bool IsRunAtStartupEnabled() { using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", false); if (key != null) { return key.GetValue("ThemeToggler") != null; } return false; } private static void SetRunAtStartup(bool enable) { using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); if (key != null) { if (enable) { key.SetValue("ThemeToggler", $"\"{Application.ExecutablePath}\""); } else { key.DeleteValue("ThemeToggler", false); } } } private static void Startup_Click(object? sender, EventArgs e) { if (_startupMenuItem == null) return; try { bool newState = !_startupMenuItem.Checked; SetRunAtStartup(newState); _startupMenuItem.Checked = newState; } catch (Exception ex) { MessageBox.Show($"שגיאה בהגדרת הפעלה אוטומטית: {ex.Message}", "Theme Toggler - שגיאה", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } // Message filter to intercept WM_HOTKEY messages at thread level public class HotkeyMessageFilter : IMessageFilter { private const int WM_HOTKEY = 0x0312; private const int HOTKEY_ID = 9000; public bool PreFilterMessage(ref Message m) { if (m.Msg == WM_HOTKEY && m.WParam.ToInt32() == HOTKEY_ID) { Program.ToggleTheme(); return true; // Mark as handled } return false; } } }