diff --git a/LICENSE b/LICENSE index 6273e3d..ff3bc91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2020 flashwave +Copyright (c) 2020-2021 flashwave Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. diff --git a/TopMostFriend/AboutWindow.cs b/TopMostFriend/AboutWindow.cs index 7ca3cfc..f50ab16 100644 --- a/TopMostFriend/AboutWindow.cs +++ b/TopMostFriend/AboutWindow.cs @@ -45,6 +45,15 @@ namespace TopMostFriend { websiteButton.Click += (s, e) => Process.Start(@"https://flash.moe/topmostfriend"); Controls.Add(websiteButton); + Button donateButton = new Button { + Text = @"Donate", + Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT), + TabIndex = ++tabIndex, + }; + donateButton.Location = new Point(websiteButton.Left - donateButton.Width - BUTTON_SPACING, closeButton.Top); + donateButton.Click += (s, e) => Process.Start(@"https://flash.moe/donate"); + Controls.Add(donateButton); + Button creditButton = new Button { Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top, Text = string.Empty, diff --git a/TopMostFriend/Program.cs b/TopMostFriend/Program.cs index f2e0e7b..b4baaf7 100644 --- a/TopMostFriend/Program.cs +++ b/TopMostFriend/Program.cs @@ -13,9 +13,9 @@ using System.Windows.Forms; namespace TopMostFriend { public static class Program { private static NotifyIcon SysIcon; + private static ContextMenuStrip CtxMenu; private static HotKeyWindow HotKeys; private static Icon OriginalIcon; - private static int InitialItems = 0; private const string GUID = #if DEBUG @@ -43,28 +43,38 @@ namespace TopMostFriend { public const string SHIFT_CLICK_BLACKLIST = @"ShiftClickToBlacklist"; public const string TITLE_BLACKLIST = @"TitleBlacklist"; public const string SHOW_HOTKEY_ICON = @"ShowHotkeyIcon"; + public const string SHOW_WINDOW_LIST = @"ShowWindowList"; + + private static ToolStripItem RefreshButton; + private static ToolStripItem LastSelectedItem = null; private static readonly List TitleBlacklist = new List(); + private static ToolStripItem[] ListActionItems; + private static ToolStripItem[] AppActionItems; + [STAThread] public static void Main(string[] args) { - if (Environment.OSVersion.Version.Major >= 6) + if(Environment.OSVersion.Version.Major >= 6) Win32.SetProcessDPIAware(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - if (args.Contains(@"--reset-admin")) + if(args.Contains(@"--reset-admin")) Settings.Remove(ALWAYS_ADMIN_SETTING); string cliToggle = args.FirstOrDefault(x => x.StartsWith(@"--hwnd=")); - if (!string.IsNullOrEmpty(cliToggle) && int.TryParse(cliToggle.Substring(7), out int cliToggleHWnd)) - ToggleWindow(new IntPtr(cliToggleHWnd)); + if(!string.IsNullOrEmpty(cliToggle) && int.TryParse(cliToggle.Substring(7), out int cliToggleHWnd)) { + WindowInfo cliWindow = new WindowInfo(cliToggleHWnd); + if(!cliWindow.ToggleTopMost()) + TopMostFailed(cliWindow); + } - if (args.Contains(@"--stop")) + if(args.Contains(@"--stop")) return; - if (!GlobalMutex.WaitOne(0, true)) { + if(!GlobalMutex.WaitOne(0, true)) { MessageBox.Show(@"An instance of Top Most Friend is already running.", @"Top Most Friend"); return; } @@ -73,6 +83,7 @@ namespace TopMostFriend { Settings.SetDefault(ALWAYS_ADMIN_SETTING, false); Settings.SetDefault(SHIFT_CLICK_BLACKLIST, true); Settings.SetDefault(SHOW_HOTKEY_ICON, true); + Settings.SetDefault(SHOW_WINDOW_LIST, true); // Defaulting to false on Windows 10 because it uses the stupid, annoying and intrusive new Android style notification system // This would fucking piledrive the notification history and also just be annoying in general because intrusive Settings.SetDefault(TOGGLE_BALLOON_SETTING, ToggleBalloonDefault); @@ -83,7 +94,7 @@ namespace TopMostFriend { if(Environment.OSVersion.Version.Major >= 10) titles.Add(@"Windows Shell Experience Host"); - if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2) + if(Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2) titles.Add(@"Start menu"); else if(Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major <= 6 && Environment.OSVersion.Version.Minor < 2)) titles.Add(@"Start"); @@ -91,7 +102,7 @@ namespace TopMostFriend { Settings.Set(TITLE_BLACKLIST, titles.ToArray()); } - if (Settings.Get(ALWAYS_ADMIN_SETTING) && !IsElevated()) { + if(Settings.Get(ALWAYS_ADMIN_SETTING) && !IsElevated()) { Elevate(); return; } @@ -99,7 +110,7 @@ namespace TopMostFriend { TitleBlacklist.Clear(); string[] titleBlacklist = Settings.Get(TITLE_BLACKLIST); - if (titleBlacklist != null) + if(titleBlacklist != null) ApplyBlacklistedTitles(titleBlacklist); string backgroundPath = Settings.Get(LIST_BACKGROUND_PATH_SETTING, string.Empty); @@ -110,37 +121,38 @@ namespace TopMostFriend { try { backgroundImage = Image.FromFile(backgroundPath); backgroundLayout = (ImageLayout)Settings.Get(LIST_BACKGROUND_LAYOUT_SETTING, 0); - } catch {} + } catch { } } OriginalIcon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); + CtxMenu = new ContextMenuStrip { + BackgroundImage = backgroundImage, + BackgroundImageLayout = backgroundLayout, + }; + CtxMenu.Closing += CtxMenu_Closing; + CtxMenu.ItemClicked += CtxMenu_ItemClicked; + ListActionItems = new ToolStripItem[] { + new ToolStripSeparator(), + RefreshButton = new ToolStripMenuItem(@"&Refresh", Properties.Resources.arrow_refresh, new EventHandler((s, e) => RefreshWindowList())), + }; + AppActionItems = new ToolStripItem[] { + new ToolStripMenuItem(@"&Settings", Properties.Resources.cog, new EventHandler((s, e) => SettingsWindow.Display())), + new ToolStripMenuItem(@"&About", Properties.Resources.help, new EventHandler((s, e) => AboutWindow.Display())), + new ToolStripMenuItem(@"&Quit", Properties.Resources.door_in, new EventHandler((s, e) => Application.Exit())), + }; + CtxMenu.Items.AddRange(AppActionItems); + SysIcon = new NotifyIcon { Visible = true, Icon = OriginalIcon, Text = @"Top Most Application Manager", }; + SysIcon.ContextMenuStrip = CtxMenu; SysIcon.MouseDown += SysIcon_MouseDown; - SysIcon.ContextMenuStrip = new ContextMenuStrip { - BackgroundImage = backgroundImage, - BackgroundImageLayout = backgroundLayout, - }; - SysIcon.ContextMenuStrip.Items.AddRange(new ToolStripItem[] { - new ToolStripSeparator(), - new ToolStripMenuItem(@"&Settings", Properties.Resources.cog, new EventHandler((s, e) => SettingsWindow.Display())), - new ToolStripMenuItem(@"&About", Properties.Resources.help, new EventHandler((s, e) => AboutWindow.Display())), - new ToolStripMenuItem(@"&Quit", Properties.Resources.door_in, new EventHandler((s, e) => Application.Exit())), - }); - InitialItems = SysIcon.ContextMenuStrip.Items.Count; HotKeys = new HotKeyWindow(); - - try { - SetForegroundHotKey(Settings.Get(FOREGROUND_HOTKEY_SETTING)); - } catch(Win32Exception ex) { - Console.WriteLine(@"Hotkey registration failed:"); - Console.WriteLine(ex); - } + SetForegroundHotKey(Settings.Get(FOREGROUND_HOTKEY_SETTING)); Application.Run(); @@ -148,29 +160,29 @@ namespace TopMostFriend { } public static void AddBlacklistedTitle(string title) { - lock (TitleBlacklist) + lock(TitleBlacklist) TitleBlacklist.Add(title); } public static void RemoveBlacklistedTitle(string title) { - lock (TitleBlacklist) + lock(TitleBlacklist) TitleBlacklist.RemoveAll(x => x == title); } public static void ApplyBlacklistedTitles(string[] arr) { - lock (TitleBlacklist) { + lock(TitleBlacklist) { TitleBlacklist.Clear(); TitleBlacklist.AddRange(arr); } } public static bool CheckBlacklistedTitles(string title) { - lock (TitleBlacklist) + lock(TitleBlacklist) return TitleBlacklist.Contains(title); } public static string[] GetBlacklistedTitles() { - lock (TitleBlacklist) + lock(TitleBlacklist) return TitleBlacklist.ToArray(); } public static void SaveBlacklistedTitles() { - lock (TitleBlacklist) + lock(TitleBlacklist) Settings.Set(TITLE_BLACKLIST, TitleBlacklist.ToArray()); } @@ -183,8 +195,8 @@ namespace TopMostFriend { private static bool? IsElevatedValue; public static bool IsElevated() { - if (!IsElevatedValue.HasValue) { - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + if(!IsElevatedValue.HasValue) { + using(WindowsIdentity identity = WindowsIdentity.GetCurrent()) IsElevatedValue = identity != null && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator); } @@ -192,7 +204,7 @@ namespace TopMostFriend { } public static void Elevate(string args = null) { - if (IsElevated()) + if(IsElevated()) return; Shutdown(); @@ -216,63 +228,75 @@ namespace TopMostFriend { Settings.Set(FOREGROUND_HOTKEY_SETTING, ((int)key << 16) | (int)mods); HotKeys.Unregister(FOREGROUND_HOTKEY_ATOM); - if (mods != 0 && key != 0) + if(mods != 0 && key != 0) HotKeys.Register(FOREGROUND_HOTKEY_ATOM, mods, key, ToggleForegroundWindow); - } catch (Win32Exception ex) { + } catch(Win32Exception ex) { Debug.WriteLine(@"Hotkey registration failed:"); Debug.WriteLine(ex); } } private static void RefreshWindowList() { - while (SysIcon.ContextMenuStrip.Items.Count > InitialItems) - SysIcon.ContextMenuStrip.Items.RemoveAt(0); - - IEnumerable windows = GetWindowList(); + IEnumerable windows = WindowInfo.GetAllWindows(); + List items = new List(); Process lastProc = null; bool procSeparator = Settings.Get(PROCESS_SEPARATOR_SETTING, false); bool showEmptyTitles = Settings.Get(SHOW_EMPTY_WINDOW_SETTING, false); - bool shiftClickBlacklist = Settings.Get(SHIFT_CLICK_BLACKLIST, true); + bool listSelf = Settings.Get(LIST_SELF_SETTING, Debugger.IsAttached); - foreach(WindowEntry window in windows) { - if(procSeparator && lastProc != window.Process) { - if (lastProc != null) - SysIcon.ContextMenuStrip.Items.Insert(0, new ToolStripSeparator()); - lastProc = window.Process; + foreach(WindowInfo window in windows) { + if(!listSelf && window.IsOwnWindow) + continue; + + if(procSeparator && lastProc != window.Owner) { + if(lastProc != null) + items.Add(new ToolStripSeparator()); + lastProc = window.Owner; } - string title = Win32.GetWindowTextString(window.Window); - + string title = window.Title; + // i think it's a fair assumption that any visible window worth a damn has a window title - if (!showEmptyTitles && string.IsNullOrEmpty(title)) + if(!showEmptyTitles && string.IsNullOrEmpty(title)) continue; // Skip items in the blacklist - if (CheckBlacklistedTitles(title)) + if(CheckBlacklistedTitles(title)) continue; - Image icon = GetWindowIcon(window.Window)?.ToBitmap() ?? null; - bool isTopMost = IsTopMost(window.Window); - - SysIcon.ContextMenuStrip.Items.Insert(0, new ToolStripMenuItem( - title, icon, - new EventHandler((s, e) => { - if (shiftClickBlacklist && Control.ModifierKeys.HasFlag(Keys.Shift)) { - AddBlacklistedTitle(title); - SaveBlacklistedTitles(); - } else - SetTopMost(window.Window, !isTopMost); - }) - ) { + items.Add(new ToolStripMenuItem(title, window.IconBitmap, new EventHandler((s, e) => { + if(Settings.Get(SHIFT_CLICK_BLACKLIST, true) && Control.ModifierKeys.HasFlag(Keys.Shift)) { + AddBlacklistedTitle(title); + SaveBlacklistedTitles(); + } else if(!window.ToggleTopMost()) + TopMostFailed(window); + })) { CheckOnClick = true, - Checked = isTopMost, + Checked = window.IsTopMost, }); } + + items.AddRange(ListActionItems); + items.AddRange(AppActionItems); + + CtxMenu.Items.Clear(); + CtxMenu.Items.AddRange(items.ToArray()); } - public static bool IsTopMost(IntPtr hWnd) { - IntPtr flags = Win32.GetWindowLongPtr(hWnd, Win32.GWL_EXSTYLE); - return (flags.ToInt32() & Win32.WS_EX_TOPMOST) > 0; + private static void TopMostFailed(WindowInfo window) { + MessageBoxButtons buttons = MessageBoxButtons.OK; + StringBuilder sb = new StringBuilder(); + sb.AppendLine(@"Wasn't able to change topmost status on this window."); + + if(!IsElevated()) { + sb.AppendLine(@"Do you want to restart Top Most Friend as administrator and try again?"); + buttons = MessageBoxButtons.YesNo; + } + + DialogResult result = MessageBox.Show(sb.ToString(), @"Top Most Friend", buttons, MessageBoxIcon.Error); + + if(result == DialogResult.Yes) + Elevate($@"--hwnd={window.Handle}"); } private class ActionTimeout { @@ -283,7 +307,7 @@ namespace TopMostFriend { public ActionTimeout(Action action, int timeout) { Action = action ?? throw new ArgumentNullException(nameof(action)); - if (timeout < 1) + if(timeout < 1) throw new ArgumentException(@"Timeout must be a positive integer.", nameof(timeout)); Remaining = timeout; new Thread(ThreadBody) { IsBackground = true }.Start(); @@ -294,9 +318,9 @@ namespace TopMostFriend { Thread.Sleep(STEP); Remaining -= STEP; - if (!Continue) + if(!Continue) return; - } while (Remaining > 0); + } while(Remaining > 0); Action.Invoke(); } @@ -306,132 +330,46 @@ namespace TopMostFriend { } } - public static bool SetTopMost(IntPtr hWnd, bool state) { - Win32.SetWindowPos( - hWnd, new IntPtr(state ? Win32.HWND_TOPMOST : Win32.HWND_NOTOPMOST), - 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_SHOWWINDOW - ); - - if(IsTopMost(hWnd) != state) { - MessageBoxButtons buttons = MessageBoxButtons.OK; - StringBuilder sb = new StringBuilder(); - sb.AppendLine(@"Wasn't able to change topmost status on this window."); - - if (!IsElevated()) { - sb.AppendLine(@"Do you want to restart Top Most Friend as administrator and try again?"); - buttons = MessageBoxButtons.YesNo; - } - - DialogResult result = MessageBox.Show(sb.ToString(), @"Top Most Friend", buttons, MessageBoxIcon.Error); - - if (result == DialogResult.Yes) - Elevate($@"--hwnd={hWnd}"); - return false; - } - - if (state) - Win32.SwitchToThisWindow(hWnd, false); - - return true; - } - private static ActionTimeout IconTimeout; public static void ToggleForegroundWindow() { - IntPtr hWnd = Win32.GetForegroundWindow(); - - if (ToggleWindow(hWnd)) { - if (Settings.Get(TOGGLE_BALLOON_SETTING, false)) { - string title = Win32.GetWindowTextString(hWnd); + WindowInfo window = WindowInfo.GetForegroundWindow(); + if(window.ToggleTopMost()) { + if(Settings.Get(TOGGLE_BALLOON_SETTING, false)) { + string title = window.Title; SysIcon?.ShowBalloonTip( - 2000, IsTopMost(hWnd) ? @"Always on top" : @"No longer always on top", + 2000, window.IsTopMost ? @"Always on top" : @"No longer always on top", string.IsNullOrEmpty(title) ? @"Window has no title." : title, ToolTipIcon.Info ); } - if (SysIcon != null && Settings.Get(SHOW_HOTKEY_ICON, true)) { - Icon icon = GetWindowIcon(hWnd); + if(SysIcon != null && Settings.Get(SHOW_HOTKEY_ICON, true)) { + Icon icon = window.Icon; - if (icon != null) { + if(icon != null) { IconTimeout?.Cancel(); SysIcon.Icon = icon; IconTimeout = new ActionTimeout(() => SysIcon.Icon = OriginalIcon, 2000); } } - } + } else + TopMostFailed(window); } - public static bool ToggleWindow(IntPtr hWnd) { - return SetTopMost(hWnd, !IsTopMost(hWnd)); + private static void CtxMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { + LastSelectedItem = e.ClickedItem; } - private static Icon GetWindowIcon(IntPtr hWnd) { - IntPtr hIcon = Win32.SendMessage(hWnd, Win32.WM_GETICON, Win32.ICON_SMALL2, 0); - - if(hIcon == IntPtr.Zero) { - hIcon = Win32.SendMessage(hWnd, Win32.WM_GETICON, Win32.ICON_SMALL, 0); - - if(hIcon == IntPtr.Zero) { - hIcon = Win32.SendMessage(hWnd, Win32.WM_GETICON, Win32.ICON_BIG, 0); - - if(hIcon == IntPtr.Zero) { - hIcon = Win32.GetClassLongPtr(hWnd, Win32.GCL_HICON); - - if (hIcon == IntPtr.Zero) - hIcon = Win32.GetClassLongPtr(hWnd, Win32.GCL_HICONSM); - } - } - } - - return hIcon == IntPtr.Zero ? null : Icon.FromHandle(hIcon); - } - - private static IEnumerable GetWindowList() { - Process[] procs = Process.GetProcesses(); - Process self = Process.GetCurrentProcess(); - - foreach (Process proc in procs) { - if (!Settings.Get(LIST_SELF_SETTING, Debugger.IsAttached) && proc == self) - continue; - - IEnumerable hwnds = proc.GetWindowHandles(); - - foreach (IntPtr ptr in hwnds) { - if (!Win32.IsWindowVisible(ptr)) - continue; - - yield return new WindowEntry(proc, ptr); - } - } - } - - private class WindowEntry { - public Process Process; - public IntPtr Window; - - public WindowEntry(Process proc, IntPtr win) { - Process = proc; - Window = win; - } + private static void CtxMenu_Closing(object sender, ToolStripDropDownClosingEventArgs e) { + if(e.CloseReason == ToolStripDropDownCloseReason.ItemClicked && LastSelectedItem == RefreshButton) + e.Cancel = true; } private static void SysIcon_MouseDown(object sender, MouseEventArgs e) { - if (e.Button.HasFlag(MouseButtons.Right)) + if((e.Button & MouseButtons.Right) > 0) RefreshWindowList(); } - - public static IEnumerable GetWindowHandles(this Process proc) { - IntPtr hwndCurr = IntPtr.Zero; - - do { - hwndCurr = Win32.FindWindowEx(IntPtr.Zero, hwndCurr, null, null); - Win32.GetWindowThreadProcessId(hwndCurr, out uint procId); - - if(proc.Id == procId) - yield return hwndCurr; - } while (hwndCurr != IntPtr.Zero); - } } } diff --git a/TopMostFriend/Properties/AssemblyInfo.cs b/TopMostFriend/Properties/AssemblyInfo.cs index 94e34e2..29b50e9 100644 --- a/TopMostFriend/Properties/AssemblyInfo.cs +++ b/TopMostFriend/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("flashwave")] [assembly: AssemblyProduct("TopMostFriend")] -[assembly: AssemblyCopyright("flashwave 2020")] +[assembly: AssemblyCopyright("flashwave 2020-2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("1.4.2.0")] -[assembly: AssemblyFileVersion("1.4.2.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/TopMostFriend/Properties/Resources.Designer.cs b/TopMostFriend/Properties/Resources.Designer.cs index 7798a3f..fb8717c 100644 --- a/TopMostFriend/Properties/Resources.Designer.cs +++ b/TopMostFriend/Properties/Resources.Designer.cs @@ -70,6 +70,16 @@ namespace TopMostFriend.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap arrow_refresh { + get { + object obj = ResourceManager.GetObject("arrow_refresh", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/TopMostFriend/Properties/Resources.resx b/TopMostFriend/Properties/Resources.resx index 122270b..5aecbd4 100644 --- a/TopMostFriend/Properties/Resources.resx +++ b/TopMostFriend/Properties/Resources.resx @@ -121,6 +121,9 @@ ..\Resources\about.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\arrow_refresh.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\cog.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/TopMostFriend/Resources/arrow_refresh.png b/TopMostFriend/Resources/arrow_refresh.png new file mode 100644 index 0000000..0de2656 Binary files /dev/null and b/TopMostFriend/Resources/arrow_refresh.png differ diff --git a/TopMostFriend/SettingsWindow.cs b/TopMostFriend/SettingsWindow.cs index c723b72..600cf92 100644 --- a/TopMostFriend/SettingsWindow.cs +++ b/TopMostFriend/SettingsWindow.cs @@ -28,6 +28,7 @@ namespace TopMostFriend { public readonly CheckBox FlToggleNotification; public readonly CheckBox FlShiftClickBlacklist; public readonly CheckBox FlShowHotkeyIcon; + public readonly CheckBox FlShowWindowList; public SettingsWindow() { Text = @"Top Most Friend Settings"; @@ -35,7 +36,7 @@ namespace TopMostFriend { StartPosition = FormStartPosition.CenterScreen; FormBorderStyle = FormBorderStyle.FixedSingle; AutoScaleMode = AutoScaleMode.Dpi; - ClientSize = new Size(430, 278); + ClientSize = new Size(430, 298); MinimizeBox = MaximizeBox = false; MinimumSize = MaximumSize = Size; @@ -72,17 +73,18 @@ namespace TopMostFriend { GroupBox flagsGroup = new GroupBox { Text = @"Flags", Location = new Point(6, 76), - Size = new Size(Width - 18, 110), + Size = new Size(Width - 18, 130), }; GroupBox blackListGroup = new GroupBox { Text = @"Blacklist", - Location = new Point(6, 186), + Location = new Point(6, 206), Size = new Size(Width - 18, 55), }; Controls.AddRange(new Control[] { - applyButton, cancelButton, okButton, hotKeyGroup, flagsGroup, blackListGroup, + applyButton, cancelButton, okButton, + hotKeyGroup, flagsGroup, blackListGroup, }); Label toggleForegroundLabel = new Label { @@ -184,8 +186,18 @@ namespace TopMostFriend { AutoSize = true, TabIndex = 204, }; + FlShowWindowList = new CheckBox { + Text = @"Show list of open windows in the task bar context menu", + Location = new Point(10, 100), + Checked = Settings.Get(Program.SHOW_WINDOW_LIST, true), + AutoSize = true, + TabIndex = 205, + }; - flagsGroup.Controls.AddRange(new[] { FlAlwaysAdmin, FlToggleNotification, FlShiftClickBlacklist, FlShowHotkeyIcon, }); + flagsGroup.Controls.AddRange(new[] { + FlAlwaysAdmin, FlToggleNotification, FlShiftClickBlacklist, + FlShowHotkeyIcon, FlShowWindowList, + }); Button titleBlacklist = new Button { Size = new Size(120, 23), @@ -217,6 +229,7 @@ namespace TopMostFriend { Settings.Set(Program.TOGGLE_BALLOON_SETTING, FlToggleNotification.Checked); Settings.Set(Program.SHIFT_CLICK_BLACKLIST, FlShiftClickBlacklist.Checked); Settings.Set(Program.SHOW_HOTKEY_ICON, FlShowHotkeyIcon.Checked); + Settings.Set(Program.SHOW_WINDOW_LIST, FlShowWindowList.Checked); Program.SetForegroundHotKey(KeyCode); } diff --git a/TopMostFriend/TopMostFriend.csproj b/TopMostFriend/TopMostFriend.csproj index 5145d1e..3201bbf 100644 --- a/TopMostFriend/TopMostFriend.csproj +++ b/TopMostFriend/TopMostFriend.csproj @@ -62,6 +62,7 @@ Form + @@ -78,6 +79,7 @@ + diff --git a/TopMostFriend/Win32.cs b/TopMostFriend/Win32.cs index 1a7bcc1..3cb94f0 100644 --- a/TopMostFriend/Win32.cs +++ b/TopMostFriend/Win32.cs @@ -43,6 +43,8 @@ namespace TopMostFriend { public const int ICON_BIG = 1; public const int ICON_SMALL2 = 2; + public delegate bool EnumWindowsProc([In] IntPtr hWnd, [In] int lParam); + [DllImport(@"user32")] public static extern bool SetProcessDPIAware(); @@ -114,6 +116,9 @@ namespace TopMostFriend { [DllImport(@"user32", SetLastError = true)] public static extern bool UnregisterHotKey(IntPtr hWnd, int id); + [DllImport(@"user32", SetLastError = true)] + public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, int lParam); + [DllImport(@"kernel32", SetLastError = true, CharSet = CharSet.Auto)] public static extern ushort GlobalAddAtom(string lpString); diff --git a/TopMostFriend/WindowInfo.cs b/TopMostFriend/WindowInfo.cs new file mode 100644 index 0000000..8d86c6c --- /dev/null +++ b/TopMostFriend/WindowInfo.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; + +namespace TopMostFriend { + public class WindowInfo { + public IntPtr Handle { get; } + public Process Owner { get; } + + public string Title => Win32.GetWindowTextString(Handle); + + public long Flags => Win32.GetWindowLongPtr(Handle, Win32.GWL_EXSTYLE).ToInt32(); + public bool IsTopMost { + get => (Flags & Win32.WS_EX_TOPMOST) > 0; + set { + Win32.SetWindowPos( + Handle, new IntPtr(value ? Win32.HWND_TOPMOST : Win32.HWND_NOTOPMOST), + 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_SHOWWINDOW + ); + } + } + + public bool IsOwnWindow + => Owner == Process.GetCurrentProcess(); + + public Icon Icon { + get { + IntPtr icon = Win32.SendMessage(Handle, Win32.WM_GETICON, Win32.ICON_SMALL2, 0); + + if(icon == IntPtr.Zero) { + icon = Win32.SendMessage(Handle, Win32.WM_GETICON, Win32.ICON_SMALL, 0); + + if(icon == IntPtr.Zero) { + icon = Win32.SendMessage(Handle, Win32.WM_GETICON, Win32.ICON_BIG, 0); + + if(icon == IntPtr.Zero) { + icon = Win32.GetClassLongPtr(Handle, Win32.GCL_HICON); + + if(icon == IntPtr.Zero) + icon = Win32.GetClassLongPtr(Handle, Win32.GCL_HICONSM); + } + } + } + + return icon == IntPtr.Zero ? null : Icon.FromHandle(icon); + } + } + + public Image IconBitmap + => Icon?.ToBitmap(); + + public WindowInfo(int handle) + : this(new IntPtr(handle)) {} + + public WindowInfo(IntPtr handle) + : this(handle, FindOwner(handle)) {} + + public WindowInfo(IntPtr handle, Process owner) { + Handle = handle; + Owner = owner ?? throw new ArgumentNullException(nameof(owner)); + } + + public void SwitchTo() { + Win32.SwitchToThisWindow(Handle, false); + } + + public bool ToggleTopMost() { + bool expected = !IsTopMost; + IsTopMost = expected; + bool success = IsTopMost == expected; + if(expected && success) + SwitchTo(); + return success; + } + + public static Process FindOwner(IntPtr hWnd) { + Win32.GetWindowThreadProcessId(hWnd, out uint procId); + return Process.GetProcessById((int)procId); + } + + public static WindowInfo GetForegroundWindow() { + return new WindowInfo(Win32.GetForegroundWindow()); + } + + public static IEnumerable GetAllWindows(bool includeHidden = false) { + List windows = new List(); + Win32.EnumWindows(new Win32.EnumWindowsProc((hWnd, lParam) => { + if(includeHidden || Win32.IsWindowVisible(hWnd)) + windows.Add(hWnd); + return true; + }), 0); + return windows.Select(w => new WindowInfo(w)); + } + } +}