diff --git a/TopMostFriend/AboutWindow.cs b/TopMostFriend/AboutWindow.cs index f22c34b..7ca3cfc 100644 --- a/TopMostFriend/AboutWindow.cs +++ b/TopMostFriend/AboutWindow.cs @@ -9,16 +9,13 @@ namespace TopMostFriend { private const int BUTTON_HEIGHT = 23; private const int BUTTON_WIDTH = 70; - public const int WM_NCLBUTTONDOWN = 0xA1; - public const int HT_CAPTION = 0x02; - public static void Display() { using (AboutWindow about = new AboutWindow()) about.ShowDialog(); } public AboutWindow() { - Text = $@"About Top Most Friend"; + Text = @"About Top Most Friend"; Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); BackgroundImage = Properties.Resources.about; StartPosition = FormStartPosition.CenterScreen; @@ -81,7 +78,7 @@ namespace TopMostFriend { Controls.Add(creditButtonfff); Controls.Add(new Label { - Text = Application.ProductVersion, + Text = @"v" + Application.ProductVersion.Substring(0, Application.ProductVersion.Length - 2), // cut off the last dingus TextAlign = ContentAlignment.MiddleLeft, AutoSize = true, Location = new Point(127, 97), @@ -93,8 +90,8 @@ namespace TopMostFriend { protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); - Program.ReleaseCapture(); - Program.SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0); + Win32.ReleaseCapture(); + Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0); } } } diff --git a/TopMostFriend/HotKeyWindow.cs b/TopMostFriend/HotKeyWindow.cs new file mode 100644 index 0000000..ae1b27f --- /dev/null +++ b/TopMostFriend/HotKeyWindow.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace TopMostFriend { + public sealed class HotKeyWindow : Form { + public class HotKeyInfo { + public string Name { get; } + public int Atom { get; } + public int Key { get; } + public Action Action { get; } + + public HotKeyInfo(string name, int atom, int key, Action action) { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Atom = atom; + Key = key; + Action = action ?? throw new ArgumentNullException(nameof(action)); + } + } + + private readonly List RegisteredHotKeys = new List(); + + public HotKeyWindow() { + ShowInTaskbar = false; + Text = string.Empty; + FormBorderStyle = FormBorderStyle.None; + StartPosition = FormStartPosition.Manual; + Size = new Size(1, 1); + Location = new Point(-9999, -9999); + CreateHandle(); + Hide(); + } + + protected override void OnFormClosing(FormClosingEventArgs e) { + e.Cancel = e.CloseReason == CloseReason.UserClosing; + } + + protected override void WndProc(ref Message m) { + base.WndProc(ref m); + + if(m.Msg == Win32.WM_HOTKEY) { + int keyCode = m.LParam.ToInt32(); + + lock (RegisteredHotKeys) + RegisteredHotKeys.FirstOrDefault(x => x.Key == keyCode)?.Action.Invoke(); + } + } + + protected override void Dispose(bool disposing) { + lock (RegisteredHotKeys) { + HotKeyInfo[] hotKeys = RegisteredHotKeys.ToArray(); + + foreach(HotKeyInfo hotKey in hotKeys) + Unregister(hotKey.Atom); + } + + base.Dispose(disposing); + } + + public int Register(string name, Win32ModKeys modifiers, Keys key, Action action) { + if (action == null) + throw new ArgumentNullException(nameof(action)); + if(string.IsNullOrEmpty(name)) + name = Guid.NewGuid().ToString(); + + int atom = Win32.GlobalAddAtom(name); + int keyCode = ((ushort)key << 16) | (ushort)modifiers; + + if (atom == 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), @"Atom creation failed."); + + if (!Win32.RegisterHotKey(Handle, atom, modifiers, key)) { + Win32.GlobalDeleteAtom((ushort)atom); + throw new Win32Exception(Marshal.GetLastWin32Error(), @"Hotkey registration failed."); + } + + lock(RegisteredHotKeys) + RegisteredHotKeys.Add(new HotKeyInfo(name, atom, keyCode, action)); + + return atom; + } + + public void Unregister(int id) { + if (id < 1) + return; + + lock (RegisteredHotKeys) { + if (!RegisteredHotKeys.Any(x => x.Atom == id)) + return; + RegisteredHotKeys.RemoveAll(x => x.Atom == id); + } + + Win32.UnregisterHotKey(Handle, id); + Win32.GlobalDeleteAtom((ushort)id); + } + + public void Unregister(string name) { + int atom = 0; + + lock (RegisteredHotKeys) + atom = RegisteredHotKeys.FirstOrDefault(x => x.Name == name)?.Atom ?? 0; + + Unregister(atom); + } + } +} diff --git a/TopMostFriend/Program.cs b/TopMostFriend/Program.cs index bf4b8b9..8372876 100644 --- a/TopMostFriend/Program.cs +++ b/TopMostFriend/Program.cs @@ -2,41 +2,100 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; -using System.Runtime.InteropServices; -using System.Text; +using System.IO; +using System.Threading; using System.Windows.Forms; namespace TopMostFriend { public static class Program { private static NotifyIcon SysIcon; + private static HotKeyWindow HotKeys; private static readonly Process OwnProcess = Process.GetCurrentProcess(); private static int InitialItems = 0; + private const string GUID = +#if DEBUG + @"{1A22D9CA-2AA9-48F2-B007-3A48CF205CDD}"; +#else + @"{5BE25191-E1E2-48A7-B038-E986CD989E91}"; +#endif + private static readonly Mutex GlobalMutex = new Mutex(true, GUID); + + public const string FOREGROUND_HOTKEY_ATOM = @"{86795D64-770D-4BD6-AA26-FA638FBAABCF}"; + public const string FOREGROUND_HOTKEY_SETTING = @"ForegroundHotKey"; + + public const string PROCESS_SEPARATOR_SETTING = @"InsertProcessSeparator"; + public const string LIST_SELF_SETTING = @"ListSelf"; + public const string SHOW_EMPTY_WINDOW_SETTING = @"ShowEmptyWindowTitles"; + public const string SHOW_EXPLORER_SETTING = @"ShowExplorerMisc"; + public const string LIST_BACKGROUND_PATH_SETTING = @"ListBackgroundPath"; + public const string LIST_BACKGROUND_LAYOUT_SETTING = @"ListBackgroundLayout"; + [STAThread] public static void Main() { if (Environment.OSVersion.Version.Major >= 6) - SetProcessDPIAware(); + Win32.SetProcessDPIAware(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + if (!GlobalMutex.WaitOne(0, true)) { + MessageBox.Show(@"An instance of Top Most Friend is already running.", @"Top Most Friend"); + return; + } + + Settings.SetDefault(FOREGROUND_HOTKEY_SETTING, ((int)Keys.F << 16) | (int)(Win32ModKeys.MOD_CONTROL | Win32ModKeys.MOD_ALT)); + + string backgroundPath = Settings.Get(LIST_BACKGROUND_PATH_SETTING, string.Empty); + Image backgroundImage = null; + ImageLayout backgroundLayout = 0; + + if(File.Exists(backgroundPath)) { + try { + backgroundImage = Image.FromFile(backgroundPath); + backgroundLayout = (ImageLayout)Settings.Get(LIST_BACKGROUND_LAYOUT_SETTING, 0); + } catch {} + } + SysIcon = new NotifyIcon { Visible = true, Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath), Text = @"Top Most Application Manager", }; SysIcon.MouseDown += SysIcon_MouseDown; - SysIcon.ContextMenuStrip = new ContextMenuStrip(); + 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(); + SetForegroundHotKey(Settings.Get(FOREGROUND_HOTKEY_SETTING)); + Application.Run(); + HotKeys.Dispose(); SysIcon.Dispose(); + + GlobalMutex.ReleaseMutex(); + } + + public static void SetForegroundHotKey(int keyCode) { + SetForegroundHotKey((Win32ModKeys)(keyCode & 0xFFFF), (Keys)((keyCode & 0xFFFF0000) >> 16)); + } + + public static void SetForegroundHotKey(Win32ModKeys mods, Keys key) { + Settings.Set(FOREGROUND_HOTKEY_SETTING, ((int)key << 16) | (int)mods); + HotKeys.Unregister(FOREGROUND_HOTKEY_ATOM); + + if(mods != 0 && key != 0) + HotKeys.Register(FOREGROUND_HOTKEY_ATOM, mods, key, ToggleForegroundWindow); } private static void RefreshWindowList() { @@ -44,54 +103,76 @@ namespace TopMostFriend { SysIcon.ContextMenuStrip.Items.RemoveAt(0); IEnumerable windows = GetWindowList(); + Process lastProc = null; + bool procSeparator = Settings.Get(PROCESS_SEPARATOR_SETTING, false); + bool showEmptyTitles = Settings.Get(SHOW_EMPTY_WINDOW_SETTING, Debugger.IsAttached); + bool showExplorerMisc = Settings.Get(SHOW_EXPLORER_SETTING, Debugger.IsAttached); foreach(WindowEntry window in windows) { - string title = GetWindowTextLazy(window.Window); + if(procSeparator && lastProc != window.Process) { + if (lastProc != null) + SysIcon.ContextMenuStrip.Items.Insert(0, new ToolStripSeparator()); + lastProc = window.Process; + } + string title = Win32.GetWindowTextString(window.Window); + // i think it's a fair assumption that any visible window worth a damn has a window title - if (string.IsNullOrEmpty(title)) + if (!showEmptyTitles && string.IsNullOrEmpty(title)) continue; // skip explorer things with specific titles, there's probably a much better way of doing this check // and this will also probably only work properly on english windows but Fuck It what do you want from me - if (window.Process.ProcessName == @"explorer" && (title == @"Program Manager" || title == @"Start")) + if (!showExplorerMisc && window.Process.ProcessName == @"explorer" && (title == @"Program Manager" || title == @"Start")) continue; - IntPtr flags = GetWindowLongPtr(window.Window, GWL_EXSTYLE); - bool isTopMost = (flags.ToInt32() & WS_EX_TOPMOST) > 0; - - Image icon = GetWindowIcon(window.Window).ToBitmap(); + 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) => { - SetWindowPos( - window.Window, new IntPtr(isTopMost ? HWND_NOTOPMOST : HWND_TOPMOST), - 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW - ); - - if (!isTopMost) - SwitchToThisWindow(window.Window, false); - })) { + title, icon, + new EventHandler((s, e) => SetTopMost(window.Window, !isTopMost)) + ) { CheckOnClick = true, Checked = isTopMost, }); } } + public static bool IsTopMost(IntPtr hWnd) { + IntPtr flags = Win32.GetWindowLongPtr(hWnd, Win32.GWL_EXSTYLE); + return (flags.ToInt32() & Win32.WS_EX_TOPMOST) > 0; + } + + public static void 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 (state) + Win32.SwitchToThisWindow(hWnd, false); + } + + public static void ToggleForegroundWindow() { + IntPtr hWnd = Win32.GetForegroundWindow(); + SetTopMost(hWnd, !IsTopMost(hWnd)); + } + private static Icon GetWindowIcon(IntPtr hWnd) { - IntPtr hIcon = SendMessage(hWnd, WM_GETICON, ICON_SMALL2, 0); + IntPtr hIcon = Win32.SendMessage(hWnd, Win32.WM_GETICON, Win32.ICON_SMALL2, 0); if(hIcon == IntPtr.Zero) { - hIcon = SendMessage(hWnd, WM_GETICON, ICON_SMALL, 0); + hIcon = Win32.SendMessage(hWnd, Win32.WM_GETICON, Win32.ICON_SMALL, 0); if(hIcon == IntPtr.Zero) { - hIcon = SendMessage(hWnd, WM_GETICON, ICON_BIG, 0); + hIcon = Win32.SendMessage(hWnd, Win32.WM_GETICON, Win32.ICON_BIG, 0); if(hIcon == IntPtr.Zero) { - hIcon = GetClassLongPtr(hWnd, GCL_HICON); + hIcon = Win32.GetClassLongPtr(hWnd, Win32.GCL_HICON); if (hIcon == IntPtr.Zero) - hIcon = GetClassLongPtr(hWnd, GCL_HICONSM); + hIcon = Win32.GetClassLongPtr(hWnd, Win32.GCL_HICONSM); } } } @@ -103,13 +184,13 @@ namespace TopMostFriend { Process[] procs = Process.GetProcesses(); foreach (Process proc in procs) { - if (proc.Id == OwnProcess.Id) + if (!Settings.Get(LIST_SELF_SETTING, Debugger.IsAttached) && proc.Id == OwnProcess.Id) continue; IEnumerable hwnds = proc.GetWindowHandles(); foreach (IntPtr ptr in hwnds) { - if (!IsWindowVisible(ptr)) + if (!Win32.IsWindowVisible(ptr)) continue; yield return new WindowEntry(proc, ptr); @@ -128,98 +209,23 @@ namespace TopMostFriend { } private static void SysIcon_MouseDown(object sender, MouseEventArgs e) { - if (e.Button != MouseButtons.Right) - return; + if (e.Button.HasFlag(MouseButtons.Left)) + ToggleForegroundWindow(); - RefreshWindowList(); + if (e.Button.HasFlag(MouseButtons.Right)) + RefreshWindowList(); } public static IEnumerable GetWindowHandles(this Process proc) { IntPtr hwndCurr = IntPtr.Zero; do { - hwndCurr = FindWindowEx(IntPtr.Zero, hwndCurr, null, null); - GetWindowThreadProcessId(hwndCurr, out uint procId); - + 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); } - - private const int HWND_TOPMOST = -1; - private const int HWND_NOTOPMOST = -2; - private const int SWP_NOSIZE = 0x0001; - private const int SWP_NOMOVE = 0x0002; - private const int SWP_SHOWWINDOW = 0x0040; - private const int GWL_EXSTYLE = -20; - private const int GCL_HICON = -14; - private const int GCL_HICONSM = -34; - private const int WS_EX_TOPMOST = 0x08; - private const int WM_GETICON = 0x7F; - private const int ICON_SMALL = 0; - private const int ICON_BIG = 1; - private const int ICON_SMALL2 = 2; - - [DllImport(@"user32")] - private static extern bool SetProcessDPIAware(); - - [DllImport(@"user32", SetLastError = true)] - private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); - - [DllImport(@"user32", SetLastError = true)] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - private static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex) { - if (IntPtr.Size == 8) - return GetWindowLongPtr64(hWnd, nIndex); - return new IntPtr(GetWindowLong32(hWnd, nIndex)); - } - - [DllImport(@"user32", EntryPoint = "GetWindowLong")] - private static extern int GetWindowLong32(IntPtr hWnd, int nIndex); - - [DllImport(@"user32", EntryPoint = "GetWindowLongPtr")] - private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex); - - [DllImport(@"user32")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsWindowVisible(IntPtr hWnd); - - [DllImport(@"user32", CharSet = CharSet.Auto, SetLastError = true)] - private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); - - [DllImport(@"user32", SetLastError = true, CharSet = CharSet.Auto)] - private static extern int GetWindowTextLength(IntPtr hWnd); - - private static string GetWindowTextLazy(IntPtr hwnd) { - int length = GetWindowTextLength(hwnd) + 1; - StringBuilder sb = new StringBuilder(length); - GetWindowText(hwnd, sb, length); - return sb.ToString(); - } - - private static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) { - if (IntPtr.Size > 4) - return GetClassLongPtr64(hWnd, nIndex); - return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); - } - - [DllImport(@"user32", EntryPoint = "GetClassLong")] - private static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); - - [DllImport(@"user32", EntryPoint = "GetClassLongPtr")] - private static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); - - [DllImport(@"user32", SetLastError = true)] - private static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab); - - [DllImport(@"user32", SetLastError = true)] - private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int uFlags); - - [DllImport(@"user32", CharSet = CharSet.Auto, SetLastError = false)] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); - - [DllImport(@"user32")] - public static extern bool ReleaseCapture(); } } diff --git a/TopMostFriend/Properties/AssemblyInfo.cs b/TopMostFriend/Properties/AssemblyInfo.cs index f3bf902..f380713 100644 --- a/TopMostFriend/Properties/AssemblyInfo.cs +++ b/TopMostFriend/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/TopMostFriend/Properties/Resources.Designer.cs b/TopMostFriend/Properties/Resources.Designer.cs index fdf5b9c..7798a3f 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 cog { + get { + object obj = ResourceManager.GetObject("cog", 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 f751c55..122270b 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\cog.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\door_in.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/TopMostFriend/Resources/cog.png b/TopMostFriend/Resources/cog.png new file mode 100644 index 0000000..67de2c6 Binary files /dev/null and b/TopMostFriend/Resources/cog.png differ diff --git a/TopMostFriend/Settings.cs b/TopMostFriend/Settings.cs new file mode 100644 index 0000000..290e350 --- /dev/null +++ b/TopMostFriend/Settings.cs @@ -0,0 +1,58 @@ +using Microsoft.Win32; +using System; + +namespace TopMostFriend { + public static class Settings { + private const string ROOT = @"Software\flash.moe\TopMostFriend"; + + private static RegistryKey GetRoot() { + RegistryKey root = Registry.CurrentUser.OpenSubKey(ROOT, true); + + if (root == null) + root = Registry.CurrentUser.CreateSubKey(ROOT); + + return root; + } + + public static T Get(string name, T fallback = default) { + try { + return (T)Convert.ChangeType(GetRoot().GetValue(name, fallback), typeof(T)); + } catch { + return fallback; + } + } + + public static bool Has(string name) { + try { + GetRoot().GetValueKind(name); + return true; + } catch { + return false; + } + } + + public static void Set(string name, object value) { + if(value == null) { + Remove(name); + return; + } + + switch(value) { + case bool b: + value = b ? 1 : 0; + break; + } + + GetRoot().SetValue(name, value); + } + + public static void SetDefault(string name, object value) { + if (!Has(name)) + Set(name, value); + } + + public static void Remove(string name) { + GetRoot().DeleteValue(name, false); + } + } +} diff --git a/TopMostFriend/SettingsWindow.cs b/TopMostFriend/SettingsWindow.cs new file mode 100644 index 0000000..2c59043 --- /dev/null +++ b/TopMostFriend/SettingsWindow.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; + +namespace TopMostFriend { + public class SettingsWindow : Form { + public static SettingsWindow Instance; + + public static void Display() { + if(Instance != null) { + Instance.Show(); + return; + } + + Instance = new SettingsWindow(); + Instance.Show(); + } + + public int KeyCode { get; set; } + + public readonly TextBox FgKey; + public readonly CheckBox FgModCtrl; + public readonly CheckBox FgModAlt; + public readonly CheckBox FgModShift; + + public SettingsWindow() { + Text = @"Top Most Friend Settings"; + Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); + StartPosition = FormStartPosition.CenterScreen; + FormBorderStyle = FormBorderStyle.FixedSingle; + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(410, 113); + MinimizeBox = MaximizeBox = false; + MinimumSize = MaximumSize = Size; + + KeyCode = Settings.Get(Program.FOREGROUND_HOTKEY_SETTING, 0); + + Button applyButton = new Button { + Text = @"Apply", + Size = new Size(75, 23), + Location = new Point(ClientSize.Width - 81, ClientSize.Height - 30), + TabIndex = 10003, + }; + applyButton.Click += ApplyButton_Click; + Button cancelButton = new Button { + Text = @"Cancel", + Size = applyButton.Size, + Location = new Point(ClientSize.Width - 162, applyButton.Location.Y), + TabIndex = 10002, + }; + cancelButton.Click += CancelButton_Click; + Button okButton = new Button { + Text = @"OK", + Size = applyButton.Size, + Location = new Point(ClientSize.Width - 243, applyButton.Location.Y), + TabIndex = 10001, + }; + okButton.Click += OkButton_Click; + + GroupBox hotKeyGroup = new GroupBox { + Text = @"Hotkeys", + Location = new Point(6, 6), + Size = new Size(Width - 18, 70), + }; + + Controls.AddRange(new Control[] { + applyButton, cancelButton, okButton, hotKeyGroup, + }); + + Label toggleForegroundLabel = new Label { + AutoSize = true, + Text = @"Toggle always on top status on active window", + Location = new Point(8, 17), + }; + + const int mod_x = 120; + const int mod_y = 34; + + Button fgReset = new Button { + Text = @"Reset", + Location = new Point(hotKeyGroup.Width - 85, mod_y), + }; + fgReset.Click += FgReset_Click; + + FgKey = new TextBox { + Text = ((Keys)(KeyCode >> 16)).ToString(), + Location = new Point(12, mod_y + 2), + }; + FgKey.KeyDown += FgKey_KeyDown; + + FgModCtrl = new CheckBox { + Text = @"CTRL", + Location = new Point(mod_x, mod_y), + Checked = (KeyCode & (int)Win32ModKeys.MOD_CONTROL) > 0, + Appearance = Appearance.Button, + Size = new Size(50, 23), + TextAlign = ContentAlignment.MiddleCenter, + }; + FgModCtrl.Click += FgModCtrl_Click; + + FgModAlt = new CheckBox { + Text = @"ALT", + Location = new Point(mod_x + 50, mod_y), + Checked = (KeyCode & (int)Win32ModKeys.MOD_ALT) > 0, + Appearance = FgModCtrl.Appearance, + Size = FgModCtrl.Size, + TextAlign = FgModCtrl.TextAlign, + }; + FgModAlt.Click += FgModAlt_Click; + + FgModShift = new CheckBox { + Text = @"SHIFT", + Location = new Point(mod_x + 100, mod_y), + Checked = (KeyCode & (int)Win32ModKeys.MOD_SHIFT) > 0, + Appearance = FgModCtrl.Appearance, + Size = FgModCtrl.Size, + TextAlign = FgModCtrl.TextAlign, + }; + FgModShift.Click += FgModShift_Click; + + hotKeyGroup.Controls.AddRange(new Control[] { + toggleForegroundLabel, FgModCtrl, FgModAlt, FgModShift, fgReset, FgKey, + }); + } + + private void FgReset_Click(object sender, EventArgs e) { + FgModCtrl.Checked = FgModAlt.Checked = FgModShift.Checked = false; + FgKey.Text = string.Empty; + KeyCode = 0; + } + + public void Apply() { + Settings.Set(Program.FOREGROUND_HOTKEY_SETTING, KeyCode); + Program.SetForegroundHotKey(KeyCode); + } + + private void OkButton_Click(object sender, EventArgs e) { + Apply(); + Close(); + } + private void CancelButton_Click(object sender, EventArgs e) { + Close(); + } + private void ApplyButton_Click(object sender, EventArgs e) { + Apply(); + } + + private void FgModCtrl_Click(object sender, EventArgs e) { + if(sender is CheckBox cb) { + if (cb.Checked) + KeyCode |= (int)Win32ModKeys.MOD_CONTROL; + else + KeyCode &= ~(int)Win32ModKeys.MOD_CONTROL; + } + } + private void FgModAlt_Click(object sender, EventArgs e) { + if (sender is CheckBox cb) { + if (cb.Checked) + KeyCode |= (int)Win32ModKeys.MOD_ALT; + else + KeyCode &= ~(int)Win32ModKeys.MOD_ALT; + } + } + private void FgModShift_Click(object sender, EventArgs e) { + if (sender is CheckBox cb) { + if (cb.Checked) + KeyCode |= (int)Win32ModKeys.MOD_SHIFT; + else + KeyCode &= ~(int)Win32ModKeys.MOD_SHIFT; + } + } + + private void FgKey_KeyDown(object sender, KeyEventArgs e) { + if (!(sender is TextBox textBox)) + return; + e.Handled = e.SuppressKeyPress = true; + textBox.Text = e.KeyCode.ToString(); + KeyCode &= 0xFFFF; + KeyCode |= (int)e.KeyCode << 16; + } + + protected override void OnFormClosed(FormClosedEventArgs e) { + base.OnFormClosed(e); + Instance.Dispose(); + Instance = null; + } + } +} diff --git a/TopMostFriend/TopMostFriend.csproj b/TopMostFriend/TopMostFriend.csproj index 877af07..31d44ca 100644 --- a/TopMostFriend/TopMostFriend.csproj +++ b/TopMostFriend/TopMostFriend.csproj @@ -44,6 +44,9 @@ Form + + Form + @@ -51,6 +54,11 @@ True Resources.resx + + + Form + + @@ -66,6 +74,7 @@ + diff --git a/TopMostFriend/Win32.cs b/TopMostFriend/Win32.cs new file mode 100644 index 0000000..1a7bcc1 --- /dev/null +++ b/TopMostFriend/Win32.cs @@ -0,0 +1,123 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; + +namespace TopMostFriend { + [Flags] + public enum Win32ModKeys : uint { + [Description(@"Alt")] + MOD_ALT = 0x0001, + [Description(@"Ctrl")] + MOD_CONTROL = 0x0002, + [Description(@"Shift")] + MOD_SHIFT = 0x0004, + [Description(@"Windows")] + MOD_WIN = 0x0008, + MOD_NOREPEAT = 0x4000, + } + + public static class Win32 { + public const int HWND_TOPMOST = -1; + public const int HWND_NOTOPMOST = -2; + + public const int SWP_NOSIZE = 0x0001; + public const int SWP_NOMOVE = 0x0002; + public const int SWP_SHOWWINDOW = 0x0040; + + public const int GWL_EXSTYLE = -20; + + public const int GCL_HICON = -14; + public const int GCL_HICONSM = -34; + + public const int WS_EX_TOPMOST = 0x08; + + public const int HT_CAPTION = 0x02; + + public const int WM_GETICON = 0x007F; + public const int WM_NCLBUTTONDOWN = 0x00A1; + public const int WM_HOTKEY = 0x0312; + + public const int ICON_SMALL = 0; + public const int ICON_BIG = 1; + public const int ICON_SMALL2 = 2; + + [DllImport(@"user32")] + public static extern bool SetProcessDPIAware(); + + [DllImport(@"user32", SetLastError = true)] + public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); + + [DllImport(@"user32", SetLastError = true)] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex) { + if (IntPtr.Size == 8) + return GetWindowLongPtr64(hWnd, nIndex); + return new IntPtr(GetWindowLong32(hWnd, nIndex)); + } + + [DllImport(@"user32", EntryPoint = "GetWindowLong")] + public static extern int GetWindowLong32(IntPtr hWnd, int nIndex); + + [DllImport(@"user32", EntryPoint = "GetWindowLongPtr")] + public static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex); + + [DllImport(@"user32")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport(@"user32", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport(@"user32", SetLastError = true, CharSet = CharSet.Auto)] + public static extern int GetWindowTextLength(IntPtr hWnd); + + public static string GetWindowTextString(IntPtr hwnd) { + int length = GetWindowTextLength(hwnd) + 1; + StringBuilder sb = new StringBuilder(length); + GetWindowText(hwnd, sb, length); + return sb.ToString(); + } + + public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) { + if (IntPtr.Size > 4) + return GetClassLongPtr64(hWnd, nIndex); + return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); + } + + [DllImport(@"user32", EntryPoint = "GetClassLong")] + public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); + + [DllImport(@"user32", EntryPoint = "GetClassLongPtr")] + public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); + + [DllImport(@"user32", SetLastError = true)] + public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab); + + [DllImport(@"user32", SetLastError = true)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int uFlags); + + [DllImport(@"user32", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + [DllImport(@"user32")] + public static extern bool ReleaseCapture(); + + [DllImport(@"user32", SetLastError = true)] + public static extern IntPtr GetForegroundWindow(); + + [DllImport(@"user32", SetLastError = true)] + public static extern bool RegisterHotKey(IntPtr hWnd, int id, [MarshalAs(UnmanagedType.U4)] Win32ModKeys fsModifiers, [MarshalAs(UnmanagedType.U4)] Keys vk); + + [DllImport(@"user32", SetLastError = true)] + public static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + [DllImport(@"kernel32", SetLastError = true, CharSet = CharSet.Auto)] + public static extern ushort GlobalAddAtom(string lpString); + + [DllImport(@"kernel32", SetLastError = true, ExactSpelling = true)] + public static extern ushort GlobalDeleteAtom(ushort nAtom); + } +}