diff --git a/src/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs b/src/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs index 7841690..72d0152 100644 --- a/src/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs +++ b/src/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs @@ -264,6 +264,7 @@ namespace EveOPreview.Configuration.Implementation public Color ActiveClientHighlightColor { get; set; } public Color OverlayLabelColor { get; set; } public int OverlayLabelSize { get; set; } + [JsonProperty("IconName")] public string IconName { get; set; } public int ActiveClientHighlightThickness { get; set; } @@ -271,6 +272,9 @@ namespace EveOPreview.Configuration.Implementation [JsonProperty("LoginThumbnailLocation")] public Point LoginThumbnailLocation { get; set; } + [JsonProperty("ToggleTrackingHotkey")] + public string ToggleTrackingHotkey { get; set; } + [JsonProperty] private Dictionary> PerClientLayout { get; set; } [JsonProperty] @@ -284,7 +288,7 @@ namespace EveOPreview.Configuration.Implementation [JsonProperty] private List PriorityClients { get; set; } [JsonProperty] - private List ExecutablesToPreview { get; set; } + public List ExecutablesToPreview { get; set; } public Point GetThumbnailLocation(string currentClient, string activeClient, Point defaultLocation) { diff --git a/src/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs b/src/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs index e57466b..83c8e2a 100644 --- a/src/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs +++ b/src/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs @@ -26,6 +26,8 @@ namespace EveOPreview.Configuration List CycleGroup5BackwardHotkeys { get; set; } Dictionary CycleGroup5ClientsOrder { get; set; } + string ToggleTrackingHotkey { get; set; } + Dictionary PerClientActiveClientHighlightColor { get; set; } Dictionary PerClientThumbnailSize { get; set; } @@ -89,6 +91,7 @@ namespace EveOPreview.Configuration bool IsPriorityClient(string currentClient); bool IsExecutableToPreview(string processName); + List ExecutablesToPreview { get; set; } bool IsThumbnailDisabled(string currentClient); void ToggleThumbnail(string currentClient, bool isDisabled); diff --git a/src/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs b/src/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs index a3e648b..9b50216 100644 --- a/src/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs +++ b/src/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs @@ -7,6 +7,9 @@ using System.Net; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Threading; +using System.Runtime.InteropServices; +using System.Text; +using System.Diagnostics; using EveOPreview.Configuration; using EveOPreview.Mediator.Messages; using EveOPreview.UI.Hotkeys; @@ -49,6 +52,7 @@ namespace EveOPreview.Services private int _hideThumbnailsDelay; private List _cycleClientHotkeyHandlers = new List(); + private HotkeyHandler _toggleTrackingHotkey; #endregion public ThumbnailManager(IMediator mediator, IThumbnailConfiguration configuration, IProcessMonitor processMonitor, IWindowManager windowManager, IThumbnailViewFactory factory) @@ -91,6 +95,51 @@ namespace EveOPreview.Services RegisterCycleClientHotkey(this._configuration.CycleGroup5ForwardHotkeys?.Select(x => this._configuration.StringToKey(x)), true, this._configuration.CycleGroup5ClientsOrder); RegisterCycleClientHotkey(this._configuration.CycleGroup5BackwardHotkeys?.Select(x => this._configuration.StringToKey(x)), false, this._configuration.CycleGroup5ClientsOrder); + + // Setup toggle tracking hotkey (Ctrl+T) + var mainHandle = this._processMonitor.GetMainProcess().Handle; + System.Diagnostics.Debug.WriteLine($"Registering hotkey with main window handle: {mainHandle}"); + this._toggleTrackingHotkey = new HotkeyHandler(mainHandle, Keys.Control | Keys.T); + this._toggleTrackingHotkey.Pressed += (object s, HandledEventArgs e) => + { + System.Diagnostics.Debug.WriteLine("Hotkey handler triggered!"); + if (!Control.IsKeyLocked(Keys.Scroll)) + { + System.Diagnostics.Debug.WriteLine("Scroll lock is off, passing through"); + e.Handled = false; + return; + } + + var foregroundHandle = this._windowManager.GetForegroundWindowHandle(); + if (foregroundHandle != IntPtr.Zero) + { + uint processId; + GetWindowThreadProcessId(foregroundHandle, out processId); + var process = Process.GetProcessById((int)processId); + + if (process != null) + { + System.Diagnostics.Debug.WriteLine($"Found process: {process.ProcessName}"); + if (_configuration.IsExecutableToPreview(process.ProcessName)) + { + System.Diagnostics.Debug.WriteLine("Removing from executables to preview"); + var exesToPreview = _configuration.ExecutablesToPreview.ToList(); + exesToPreview.Remove(process.ProcessName.ToLower()); + _configuration.ExecutablesToPreview = exesToPreview; + } + else + { + System.Diagnostics.Debug.WriteLine("Adding to executables to preview"); + var exesToPreview = _configuration.ExecutablesToPreview.ToList(); + exesToPreview.Add(process.ProcessName.ToLower()); + _configuration.ExecutablesToPreview = exesToPreview; + } + e.Handled = true; + } + } + }; + var registered = this._toggleTrackingHotkey.Register(); + System.Diagnostics.Debug.WriteLine($"Hotkey registration result: {registered}"); } public IThumbnailView GetClientByTitle(string title) @@ -954,5 +1003,118 @@ namespace EveOPreview.Services && (top > ThumbnailManager.WINDOW_POSITION_THRESHOLD_LOW) && (top < ThumbnailManager.WINDOW_POSITION_THRESHOLD_HIGH) && (width > ThumbnailManager.WINDOW_SIZE_THRESHOLD) && (height > ThumbnailManager.WINDOW_SIZE_THRESHOLD); } + + private bool IsTrackedProcess(int processId) + { + return _thumbnailViews.Any(x => Process.GetProcessById(processId)?.MainWindowHandle == x.Key); + } + + private void TrackProcess(System.Diagnostics.Process process) + { + if (process == null || IsTrackedProcess(process.Id)) + { + return; + } + + System.Diagnostics.Debug.WriteLine($"Tracking process {process.ProcessName} (PID: {process.Id})"); + + // Get all windows belonging to this process + var windows = new List(); + EnumWindows((IntPtr hWnd, IntPtr lParam) => + { + uint processId; + GetWindowThreadProcessId(hWnd, out processId); + if (processId == process.Id && IsWindow(hWnd) && IsWindowVisible(hWnd)) + { + windows.Add(hWnd); + } + return true; + }, IntPtr.Zero); + + foreach (var windowHandle in windows) + { + if (_thumbnailViews.ContainsKey(windowHandle)) + { + continue; + } + + System.Diagnostics.Debug.WriteLine($"Creating thumbnail for window handle: {windowHandle}"); + + Size initialSize = this._configuration.ThumbnailSize; + string windowTitle = GetWindowTitle(windowHandle); + string title = $"{windowTitle} (PID: {process.Id})"; + + IThumbnailView view = this._thumbnailViewFactory.Create(windowHandle, title, initialSize); + view.IsOverlayEnabled = this._configuration.ShowThumbnailOverlays; + view.SetFrames(this._configuration.ShowThumbnailFrames); + view.SetSizeLimitations(this._configuration.ThumbnailMinimumSize, this._configuration.ThumbnailMaximumSize); + view.SetTopMost(this._configuration.ShowThumbnailsAlwaysOnTop); + + view.ThumbnailLocation = this._configuration.GetThumbnailLocation(view.Title, this._activeClient.Title, view.ThumbnailLocation); + + this._thumbnailViews.Add(view.Id, view); + + view.ThumbnailResized = this.ThumbnailViewResized; + view.ThumbnailMoved = this.ThumbnailViewMoved; + view.ThumbnailFocused = this.ThumbnailViewFocused; + view.ThumbnailLostFocus = this.ThumbnailViewLostFocus; + view.ThumbnailActivated = this.ThumbnailActivated; + view.ThumbnailDeactivated = this.ThumbnailDeactivated; + + view.RegisterHotkey(this._configuration.GetClientHotkey(view.Title)); + this.ApplyClientLayout(view); + } + } + + private void UntrackProcess(int processId) + { + var windowsToRemove = _thumbnailViews + .Where(x => { + uint windowProcessId; + GetWindowThreadProcessId(x.Key, out windowProcessId); + return windowProcessId == processId; + }) + .ToList(); + + foreach (var entry in windowsToRemove) + { + IThumbnailView view = entry.Value; + this._thumbnailViews.Remove(entry.Key); + + view.UnregisterHotkey(); + view.ThumbnailResized = null; + view.ThumbnailMoved = null; + view.ThumbnailFocused = null; + view.ThumbnailLostFocus = null; + view.ThumbnailActivated = null; + view.ThumbnailDeactivated = null; + + view.Close(); + } + } + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + [DllImport("user32.dll")] + private static extern bool IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + private static string GetWindowTitle(IntPtr hWnd) + { + StringBuilder sb = new StringBuilder(256); + GetWindowText(hWnd, sb, 256); + return sb.ToString(); + } } } \ No newline at end of file