using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Threading; using System.Text.Json; namespace DD2Switcher { public class Settings { public int FirstIndex { get; set; } = -1; public int LastIndex { get; set; } = -1; public Keys SequenceKeybind { get; set; } = Keys.F1; } internal static class Program { private static int NumProc = 19; private static Process[] windows = new Process[NumProc]; private static string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "DD2Switcher.json"); // Public access to tracked windows for the settings form public static Process[] GetTrackedWindows() { return windows; } public static void UntrackWindow(int index) { if (index >= 0 && index < NumProc) { windows[index] = null; // Update ActiveIndex if needed if (ActiveIndex == index) { ActiveIndex = -1; } // Update first/last indices if needed if (FirstIndex == index) { FirstIndex = -1; } if (LastIndex == index) { LastIndex = -1; } } } public static void StartSequenceMode() { Console.WriteLine($"StartSequenceMode called. FirstIndex: {FirstIndex}, LastIndex: {LastIndex}"); // Compute default indices if not set by user if (FirstIndex == -1 || LastIndex == -1) { FirstIndex = FindFirstNonNullWindow(); LastIndex = FindLastNonNullWindow(); Console.WriteLine($"Computed default indices: FirstIndex={FirstIndex}, LastIndex={LastIndex}"); // Ensure LastIndex is different from FirstIndex if there are multiple windows if (FirstIndex != -1 && LastIndex != -1 && FirstIndex == LastIndex) { int nextWindow = FindNextNonNullWindow(FirstIndex + 1); if (nextWindow != -1) { LastIndex = nextWindow; Console.WriteLine($"Adjusted LastIndex to: {LastIndex} (different from FirstIndex)"); } } } if (FirstIndex >= 0 && LastIndex >= 0 && FirstIndex <= LastIndex) { CurrentState = SequenceState.PROCESSING; CurrentSequenceIndex = FirstIndex; Console.WriteLine($"Starting sequence mode, tabbing to index {CurrentSequenceIndex + 1}"); TabTo(CurrentSequenceIndex + 1); // Tab to first window CurrentState = SequenceState.WAITING; Console.WriteLine($"State changed to: {CurrentState}"); } else { Console.WriteLine("Cannot start sequence mode - invalid first/last indices"); } } public static void NextSequenceStep() { Console.WriteLine( $"NextSequenceStep called. State: {CurrentState}, CurrentIndex: {CurrentSequenceIndex}, LastIndex: {LastIndex}"); if (CurrentState == SequenceState.INACTIVE) return; CurrentState = SequenceState.PROCESSING; CurrentSequenceIndex++; Console.WriteLine($"Advanced to index {CurrentSequenceIndex}"); if (CurrentSequenceIndex > LastIndex) { // End of sequence - tab back to first Console.WriteLine("End of sequence reached, tabbing back to first window"); TabTo(FirstIndex + 1); ExitSequenceMode(); } else { Console.WriteLine($"Tabbing to index {CurrentSequenceIndex + 1}"); TabTo(CurrentSequenceIndex + 1); CurrentState = SequenceState.WAITING; Console.WriteLine($"State changed to: {CurrentState}"); } } public static void ExitSequenceMode() { CurrentState = SequenceState.INACTIVE; CurrentSequenceIndex = -1; Console.WriteLine($"State changed to: {CurrentState}"); } public static bool IsInSequenceMode() { return CurrentState != SequenceState.INACTIVE; } private static void LoadSettings() { Console.WriteLine($"Attempting to load settings from: {settingsPath}"); try { if (File.Exists(settingsPath)) { Console.WriteLine("Settings file exists, reading..."); string json = File.ReadAllText(settingsPath); Console.WriteLine($"Read JSON: {json}"); var settings = JsonSerializer.Deserialize(json); FirstIndex = settings.FirstIndex; LastIndex = settings.LastIndex; SequenceKeybind = settings.SequenceKeybind; Console.WriteLine( $"Loaded settings: First={FirstIndex}, Last={LastIndex}, Keybind={SequenceKeybind}"); } else { Console.WriteLine($"Settings file does not exist at: {settingsPath}"); Console.WriteLine("Using default settings"); } } catch (Exception ex) { Console.WriteLine($"Error loading settings: {ex.Message}"); Console.WriteLine($"Stack trace: {ex.StackTrace}"); } } private static void SaveSettings() { try { var settings = new Settings { FirstIndex = FirstIndex, LastIndex = LastIndex, SequenceKeybind = SequenceKeybind }; string json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(settingsPath, json); Console.WriteLine($"Saved settings: First={FirstIndex}, Last={LastIndex}, Keybind={SequenceKeybind}"); } catch (Exception ex) { Console.WriteLine($"Error saving settings: {ex.Message}"); } } private static int FindFirstNonNullWindow() { for (int i = 0; i < NumProc; i++) { if (windows[i] != null && windows[i].MainWindowHandle != IntPtr.Zero) { return i; } } return -1; } private static int FindLastNonNullWindow() { for (int i = NumProc - 1; i >= 0; i--) { if (windows[i] != null && windows[i].MainWindowHandle != IntPtr.Zero) { return i; } } return -1; } private static int FindNextNonNullWindow(int startIndex) { for (int i = startIndex; i < NumProc; i++) { if (windows[i] != null && windows[i].MainWindowHandle != IntPtr.Zero) { return i; } } return -1; } private static void SetDefaultFirstLastIndices() { if (FirstIndex == -1) { FirstIndex = FindFirstNonNullWindow(); Console.WriteLine($"Set default FirstIndex to: {FirstIndex}"); } if (LastIndex == -1) { LastIndex = FindLastNonNullWindow(); Console.WriteLine($"Set default LastIndex to: {LastIndex}"); } // Ensure LastIndex is different from FirstIndex if there are multiple windows if (FirstIndex != -1 && LastIndex != -1 && FirstIndex == LastIndex) { // Find the next non-null window after FirstIndex int nextWindow = FindNextNonNullWindow(FirstIndex + 1); if (nextWindow != -1) { LastIndex = nextWindow; Console.WriteLine($"Adjusted LastIndex to: {LastIndex} (different from FirstIndex)"); } } } public static void UpdateSequenceHotkey(Keys newKey) { Console.WriteLine($"UpdateSequenceHotkey called with new key: {newKey} (code: {(int)newKey})"); Console.WriteLine($"Old SequenceKeybind before update: {SequenceKeybind} (code: {(int)SequenceKeybind})"); // Unregister old hotkey if (sequenceHotkeyId != -1) { Console.WriteLine($"Unregistering old hotkey ID: {sequenceHotkeyId}"); HotKeyManager.UnregisterHotKey(sequenceHotkeyId); } // Register new hotkey sequenceHotkeyId = HotKeyManager.RegisterHotKey(newKey, KeyModifiers.NoRepeat); Console.WriteLine($"Registered new hotkey ID: {sequenceHotkeyId} for key: {newKey}"); SequenceKeybind = newKey; Console.WriteLine($"New SequenceKeybind after update: {SequenceKeybind} (code: {(int)SequenceKeybind})"); SaveSettings(); } public static void SaveFirstLastIndices() { SaveSettings(); } // Static properties for first/last selection persistence public static int FirstIndex { get; set; } = -1; public static int LastIndex { get; set; } = -1; // Sequence mode state engine public enum SequenceState { INACTIVE, WAITING, PROCESSING } public static SequenceState CurrentState { get; set; } = SequenceState.INACTIVE; public static int CurrentSequenceIndex { get; set; } = -1; public static Keys SequenceKeybind { get; set; } = Keys.F1; private static int sequenceHotkeyId = -1; private static int ActiveIndex = -1; private static bool AltPressed = false; // Simple list to track last 5 windows private static List lastWindows = new List(); private static readonly IntPtr defaultAffinity = new(0xFF000000); private static readonly IntPtr fullAffinity = new(0xFFFFFFFF); private static readonly KeyboardHook keyboardHook = new(); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern short GetKeyState(int nVirtKey); [DllImport("kernel32.dll", SetLastError = true)] [return:MarshalAs(UnmanagedType.Bool)] static extern bool AllocConsole(); [DllImport("user32.dll")] private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo); [DllImport("user32.dll")] private static extern IntPtr SetWindowsHookEx(int idHook, IntPtr lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandle(string lpModuleName); private const int WH_MOUSE_LL = 14; private const int WM_LBUTTONDOWN = 0x0201; private const int WM_RBUTTONDOWN = 0x0204; private const int WM_MBUTTONDOWN = 0x0207; private static IntPtr mouseHookId = IntPtr.Zero; private static IntPtr mouseHookProc = IntPtr.Zero; private static MouseHookProc mouseHookDelegate; // Keep reference to prevent GC private delegate IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam); private static void HandleSequenceEvent() { if (CurrentState == SequenceState.WAITING) { Console.WriteLine($"Event detected in state: {CurrentState}, advancing to next step"); NextSequenceStep(); } } private static IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { int wParamInt = (int)wParam; if (wParamInt == WM_LBUTTONDOWN || wParamInt == WM_RBUTTONDOWN || wParamInt == WM_MBUTTONDOWN) { if (CurrentState == SequenceState.WAITING) { Console.WriteLine($"Mouse click detected in state: {CurrentState}"); HandleSequenceEvent(); } } } return CallNextHookEx(mouseHookId, nCode, wParam, lParam); } private static void CleanWindows() { for (int i = 0; i < NumProc; i++) { var window = windows[i]; if (window == null) continue; if (window.MainWindowHandle == IntPtr.Zero) { Console.WriteLine($"Window at index {i} has no main window, removing from tracked windows"); windows[i] = null; } } } private static void AdjustAffinities() { CleanWindows(); for (int i = 0; i < NumProc; i++) { var window = windows[i]; if (window == null) continue; if (i != ActiveIndex) { try { window.ProcessorAffinity = defaultAffinity; } catch (Exception e) { windows[i] = null; } } } var active = windows[ActiveIndex]; if (active != null) { try { active.ProcessorAffinity = fullAffinity; } catch (Exception e) { windows[ActiveIndex] = null; } } } private static void AdjustPriorities() { CleanWindows(); for (int i = 0; i < NumProc; i++) { var window = windows[i]; if (window == null) continue; if (i != ActiveIndex) { try { window.PriorityClass = ProcessPriorityClass.Idle; } catch (Exception e) { windows[i] = null; } } } var active = windows[ActiveIndex]; if (active != null) { try { active.PriorityClass = ProcessPriorityClass.High; } catch (Exception e) { windows[ActiveIndex] = null; } } } private static Process GetForegroundProcess() { var foregroundWindow = GetForegroundWindow(); var process = Process.GetProcesses(); Process foregroundProcess = null; foreach (var p in process) if (foregroundWindow == p.MainWindowHandle) { foregroundProcess = p; break; } if (foregroundProcess == null) return null; return foregroundProcess; } private static Boolean ProcessTracked(int id) { for (int i = 0; i < NumProc; i++) { if (windows[i] != null && windows[i].Id == id) { return true; } } return false; } private static void Track(Process process) { // First find the first null slot int firstNullIndex = -1; for (int i = 0; i < NumProc; i++) { if (windows[i] == null) { firstNullIndex = i; break; } } if (firstNullIndex == -1) { Console.WriteLine("No slots available for tracking"); return; } // Compact the array by shifting non-null elements to the left for (int i = firstNullIndex + 1; i < NumProc; i++) { if (windows[i] != null) { windows[firstNullIndex] = windows[i]; windows[i] = null; firstNullIndex++; } } // Add the new process at the first null slot windows[firstNullIndex] = process; PushHistory(firstNullIndex); ActiveIndex = firstNullIndex; Console.WriteLine($"Added {process.ProcessName} to tracked windows at index {firstNullIndex}"); } private static void TrackProcess() { CleanWindows(); var foregroundWindow = GetForegroundWindow(); var foregroundProcess = Process.GetProcesses().FirstOrDefault(p => p.MainWindowHandle == foregroundWindow); if (foregroundProcess == null) { Console.WriteLine("No foreground process found"); return; } Console.WriteLine($"Foreground process: {foregroundProcess.ProcessName} PID: {foregroundProcess.Id}"); var processes = Process.GetProcesses().OrderBy(p => p.Id).ToList(); foreach (var process in processes) { // Console.WriteLine($"Checking {process.ProcessName} at pid {process.Id}"); if (process.ProcessName == foregroundProcess.ProcessName) { Console.WriteLine($"Found {foregroundProcess.ProcessName} at pid {process.Id}"); if (ProcessTracked(process.Id)) continue; Track(process); } } } private static void Swap(int index) { index = (index - 1) % NumProc; if (index < 0) index = NumProc - 1; if (index >= NumProc) return; CleanWindows(); Console.WriteLine($"Swapping window at index {index}"); var process = GetForegroundProcess(); if (process == null) return; Console.WriteLine($"Foreground process: {process}"); bool found = false; for (int i = 0; i < NumProc; i++) { var window = windows[i]; if (window != null && window.Id == process.Id) { found = true; break; } } if (!found) { for (int i = 0; i < NumProc; i++) { var window = windows[i]; if (window == null) { Console.WriteLine($"Adding foreground window to tracked at index {i}..."); windows[i] = process; break; } } } for (int i = 0; i < NumProc; i++) { var window = windows[i]; if (window != null && window.Id == process.Id) { windows[i] = windows[index]; windows[index] = window; Console.WriteLine($"Swapped window at index {i} to {index}"); return; } } } private static void TabTo(int index) { Console.WriteLine($"TabTo called with index: {index}"); index = (index - 1) % NumProc; if (index < 0) index = NumProc - 1; if (index >= NumProc) return; CleanWindows(); Console.WriteLine($"Tab to window at index {index}"); // Find the next non-null window if the target is null int originalIndex = index; while (index < NumProc && (windows[index] == null || windows[index].MainWindowHandle == IntPtr.Zero)) { index++; } if (index >= NumProc) { // Try from the beginning index = 0; while (index < originalIndex && (windows[index] == null || windows[index].MainWindowHandle == IntPtr.Zero)) { index++; } } if (index >= NumProc || windows[index] == null || windows[index].MainWindowHandle == IntPtr.Zero) { Console.WriteLine("No valid windows found to tab to"); return; } var window = windows[index]; Console.WriteLine( $"Window at index {index}: {(window == null ? "NULL" : window.ProcessName + " PID:" + window.Id)}"); if (window == null || window.MainWindowHandle == IntPtr.Zero) { Console.WriteLine($"Window at index {index} does not exist, removing from tracked windows"); windows[index] = null; } else { if (ActiveIndex != -1) PushHistory(ActiveIndex); Console.WriteLine($"Setting foreground window to: {window.ProcessName} PID:{window.Id}"); bool result = SetForegroundWindow(window.MainWindowHandle); Console.WriteLine($"SetForegroundWindow result: {result}"); // Force window to front with additional methods if (result) { // Bring window to front and activate it SetForegroundWindow(window.MainWindowHandle); System.Threading.Thread.Sleep(50); // Small delay // Check if it actually worked var currentForeground = GetForegroundWindow(); bool actuallySwitched = (currentForeground == window.MainWindowHandle); Console.WriteLine( $"Actually switched: {actuallySwitched} (Current: {currentForeground}, Target: {window.MainWindowHandle})"); } ActiveIndex = index; AdjustAffinities(); AdjustPriorities(); Console.WriteLine($"Successfully switched to window at index {index}"); } } private static void TabToPrevious() { return; try { var foreground = GetForegroundProcess(); if (!ProcessTracked(foreground.Id)) { Console.WriteLine("Foreground process not tracked, skipping"); return; } } catch (Exception e) { Console.WriteLine($"Error setting foreground window: {e}"); } if (lastWindows.Count == 0) { Console.WriteLine("No previous window to switch to"); return; } CleanWindows(); for (int i = lastWindows.Count - 1; i >= 0; i--) { int index = lastWindows[i]; if (index != ActiveIndex) { TabTo(index + 1); // Our windows are 1-indexed because... I don't remember why break; } } } private static bool IsCapsLockOn() { return (GetKeyState(0x14) & 1) == 1; } private static void ToggleCapsLock() { keybd_event(0x14, 0, 0, 0); // KEYEVENTF_KEYDOWN keybd_event(0x14, 0, 0x0002, 0); // KEYEVENTF_KEYUP } private static void PushHistory(int index) { lastWindows.Add(index); if (lastWindows.Count > 50) lastWindows.RemoveAt(0); } [STAThread] private static void Main() { // AllocConsole(); // Enable console for debug output Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // Load settings LoadSettings(); var processes = Process.GetProcesses(); var currentProcess = Process.GetCurrentProcess(); foreach (var process in processes) if (process.Id != currentProcess.Id && process.ProcessName == currentProcess.ProcessName) { process.Kill(); Process.GetCurrentProcess().Kill(); } bool onlyAlt = false; KeyboardHook.Start(); // Set up mouse hook for sequence mode mouseHookDelegate = new MouseHookProc(MouseHookCallback); mouseHookProc = Marshal.GetFunctionPointerForDelegate(mouseHookDelegate); mouseHookId = SetWindowsHookEx(WH_MOUSE_LL, mouseHookProc, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0); KeyboardHook.KeyDown += (sender, e) => { Console.WriteLine($"Key down: {e}"); if (e == 164 && !onlyAlt) { Console.WriteLine("Only alt"); onlyAlt = true; } else { Console.WriteLine("Not only alt"); onlyAlt = false; } }; KeyboardHook.KeyUp += (sender, e) => { Console.WriteLine($"Key up: {e}"); if (e == 164 && onlyAlt) { // Left alt Console.WriteLine("Tab to previous"); onlyAlt = false; TabToPrevious(); } // Handle sequence mode event detection with KeyUp if (CurrentState == SequenceState.WAITING) { // Ignore the sequence keybind itself if (e != (int)SequenceKeybind) { Console.WriteLine($"Key up detected in state: {CurrentState} - Key: {e}"); HandleSequenceEvent(); } else { Console.WriteLine($"Ignoring sequence keybind release: {e}"); } } }; HotKeyManager.RegisterHotKey(Keys.Capital, KeyModifiers.NoRepeat); // Register main number keys (0-9) for (int i = 0; i < 10; i++) HotKeyManager.RegisterHotKey(Keys.D0 + i, KeyModifiers.Alt); // Register numpad keys (1-9) for (int i = 0; i < 9; i++) HotKeyManager.RegisterHotKey(Keys.NumPad1 + i, KeyModifiers.Alt); HotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Alt); sequenceHotkeyId = HotKeyManager.RegisterHotKey(SequenceKeybind, KeyModifiers.NoRepeat); Console.WriteLine( $"Initial sequence hotkey registration - ID: {sequenceHotkeyId}, Key: {SequenceKeybind} (code: {(int)SequenceKeybind})"); HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) { Console.WriteLine($"Hotkey pressed: {e.Key} with modifiers {e.Modifiers}"); Console.WriteLine($"Current sequence keybind: {SequenceKeybind}"); Console.WriteLine($"Key codes - Pressed: {(int)e.Key}, Expected: {(int)SequenceKeybind}"); // Check for sequence mode keybind Console.WriteLine( $"Checking sequence keybind - Key: {e.Key} == {SequenceKeybind} = {e.Key == SequenceKeybind}"); Console.WriteLine($"Checking modifiers - Received: {e.Modifiers}, Expected: {KeyModifiers.NoRepeat}"); if (e.Key == SequenceKeybind) { Console.WriteLine("Sequence keybind detected!"); if (CurrentState != SequenceState.INACTIVE) { Console.WriteLine("Advancing sequence step"); NextSequenceStep(); } else { Console.WriteLine("Starting sequence mode"); StartSequenceMode(); } return; } else { Console.WriteLine( $"Sequence keybind check failed - Key match: {e.Key == SequenceKeybind}, Modifiers match: {e.Modifiers == KeyModifiers.NoRepeat}"); } // Cancel sequence mode on any manual window switching if (CurrentState != SequenceState.INACTIVE && e.Modifiers == KeyModifiers.Alt) { Console.WriteLine("Manual window switching detected, cancelling sequence mode"); ExitSequenceMode(); } if (e.Key == Keys.Oemtilde && e.Modifiers == KeyModifiers.Alt && IsCapsLockOn()) { TrackProcess(); ToggleCapsLock(); return; } int index; if (e.Key >= Keys.D0 && e.Key <= Keys.D9) { index = e.Key - Keys.D0; } else if (e.Key >= Keys.NumPad1 && e.Key <= Keys.NumPad9) { index = 10 + (e.Key - Keys.NumPad1); } else { return; } if (e.Modifiers == KeyModifiers.Alt) { if (IsCapsLockOn()) { Swap(index); ToggleCapsLock(); } else { TabTo(index); } } } // Run the WinForms application with Form1 as the main form Application.Run(new Form1()); KeyboardHook.Stop(); // Clean up mouse hook if (mouseHookId != IntPtr.Zero) { UnhookWindowsHookEx(mouseHookId); } } } }