topmostfriend/TopMostFriend/Program.cs

380 lines
15 KiB
C#
Raw Permalink Normal View History

2022-08-25 23:53:17 +00:00
using System;
using System.Collections.Generic;
2022-08-25 23:54:53 +00:00
using System.ComponentModel;
2022-08-25 23:53:17 +00:00
using System.Diagnostics;
using System.Drawing;
2022-08-25 23:54:01 +00:00
using System.IO;
2022-08-25 23:54:53 +00:00
using System.Linq;
using System.Security.Principal;
using System.Text;
2022-08-25 23:54:01 +00:00
using System.Threading;
2022-08-25 23:53:17 +00:00
using System.Windows.Forms;
namespace TopMostFriend {
public static class Program {
private static NotifyIcon SysIcon;
2022-08-25 23:59:00 +00:00
private static ContextMenuStrip CtxMenu;
2022-08-25 23:54:01 +00:00
private static HotKeyWindow HotKeys;
2022-08-25 23:56:27 +00:00
private static Icon OriginalIcon;
2022-08-25 23:53:17 +00:00
2022-08-25 23:54:01 +00:00
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}";
2022-08-25 23:56:27 +00:00
#if DEBUG
public const string FOREGROUND_HOTKEY_SETTING = @"ForegroundHotKey_DEBUG";
#else
2022-08-25 23:54:01 +00:00
public const string FOREGROUND_HOTKEY_SETTING = @"ForegroundHotKey";
2022-08-25 23:56:27 +00:00
#endif
2022-08-25 23:54:01 +00:00
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 LIST_BACKGROUND_PATH_SETTING = @"ListBackgroundPath";
public const string LIST_BACKGROUND_LAYOUT_SETTING = @"ListBackgroundLayout";
2022-08-25 23:54:53 +00:00
public const string ALWAYS_ADMIN_SETTING = @"RunAsAdministrator";
2022-08-25 23:55:31 +00:00
public const string TOGGLE_BALLOON_SETTING = @"ShowNotificationOnHotKey";
public static readonly bool ToggleBalloonDefault = Environment.OSVersion.Version.Major < 10;
2022-08-25 23:56:27 +00:00
public const string SHIFT_CLICK_BLACKLIST = @"ShiftClickToBlacklist";
public const string TITLE_BLACKLIST = @"TitleBlacklist";
public const string SHOW_HOTKEY_ICON = @"ShowHotkeyIcon";
2022-08-25 23:59:00 +00:00
public const string SHOW_WINDOW_LIST = @"ShowWindowList";
private static ToolStripItem RefreshButton;
private static ToolStripItem LastSelectedItem = null;
2022-08-25 23:56:27 +00:00
private static readonly List<string> TitleBlacklist = new List<string>();
2022-08-25 23:54:01 +00:00
2022-08-25 23:59:00 +00:00
private static ToolStripItem[] ListActionItems;
private static ToolStripItem[] AppActionItems;
2022-08-25 23:53:17 +00:00
[STAThread]
2022-08-25 23:54:53 +00:00
public static void Main(string[] args) {
2022-08-25 23:59:00 +00:00
if(Environment.OSVersion.Version.Major >= 6)
2022-08-25 23:54:01 +00:00
Win32.SetProcessDPIAware();
2022-08-25 23:53:17 +00:00
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
2022-08-25 23:59:00 +00:00
if(args.Contains(@"--reset-admin"))
2022-08-25 23:54:53 +00:00
Settings.Remove(ALWAYS_ADMIN_SETTING);
string cliToggle = args.FirstOrDefault(x => x.StartsWith(@"--hwnd="));
2022-08-25 23:59:00 +00:00
if(!string.IsNullOrEmpty(cliToggle) && int.TryParse(cliToggle.Substring(7), out int cliToggleHWnd)) {
WindowInfo cliWindow = new WindowInfo(cliToggleHWnd);
if(!cliWindow.ToggleTopMost())
TopMostFailed(cliWindow);
}
2022-08-25 23:54:53 +00:00
2022-08-25 23:59:00 +00:00
if(args.Contains(@"--stop"))
2022-08-25 23:54:53 +00:00
return;
2022-08-25 23:59:00 +00:00
if(!GlobalMutex.WaitOne(0, true)) {
2022-08-25 23:54:01 +00:00
MessageBox.Show(@"An instance of Top Most Friend is already running.", @"Top Most Friend");
return;
}
2022-08-25 23:54:53 +00:00
Settings.SetDefault(FOREGROUND_HOTKEY_SETTING, 0);
Settings.SetDefault(ALWAYS_ADMIN_SETTING, false);
2022-08-25 23:56:27 +00:00
Settings.SetDefault(SHIFT_CLICK_BLACKLIST, true);
Settings.SetDefault(SHOW_HOTKEY_ICON, true);
2022-08-25 23:59:00 +00:00
Settings.SetDefault(SHOW_WINDOW_LIST, true);
2022-08-25 23:55:31 +00:00
// 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);
2022-08-25 23:54:53 +00:00
2022-08-25 23:56:27 +00:00
if(!Settings.Has(TITLE_BLACKLIST)) {
List<string> titles = new List<string> { @"Program Manager" };
if(Environment.OSVersion.Version.Major >= 10)
titles.Add(@"Windows Shell Experience Host");
2022-08-25 23:59:00 +00:00
if(Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2)
2022-08-25 23:56:27 +00:00
titles.Add(@"Start menu");
2022-08-25 23:57:14 +00:00
else if(Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major <= 6 && Environment.OSVersion.Version.Minor < 2))
2022-08-25 23:56:27 +00:00
titles.Add(@"Start");
Settings.Set(TITLE_BLACKLIST, titles.ToArray());
}
2022-08-25 23:59:00 +00:00
if(Settings.Get<bool>(ALWAYS_ADMIN_SETTING) && !IsElevated()) {
2022-08-25 23:54:53 +00:00
Elevate();
return;
}
2022-08-25 23:54:01 +00:00
2022-08-25 23:56:27 +00:00
TitleBlacklist.Clear();
string[] titleBlacklist = Settings.Get(TITLE_BLACKLIST);
2022-08-25 23:59:00 +00:00
if(titleBlacklist != null)
2022-08-25 23:56:27 +00:00
ApplyBlacklistedTitles(titleBlacklist);
2022-08-25 23:54:01 +00:00
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);
2022-08-25 23:59:00 +00:00
} catch { }
2022-08-25 23:54:01 +00:00
}
2022-08-25 23:56:27 +00:00
OriginalIcon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
2022-08-25 23:59:00 +00:00
CtxMenu = new ContextMenuStrip {
2022-08-25 23:54:01 +00:00
BackgroundImage = backgroundImage,
BackgroundImageLayout = backgroundLayout,
};
2022-08-25 23:59:00 +00:00
CtxMenu.Closing += CtxMenu_Closing;
CtxMenu.ItemClicked += CtxMenu_ItemClicked;
ListActionItems = new ToolStripItem[] {
2022-08-25 23:53:17 +00:00
new ToolStripSeparator(),
2022-08-25 23:59:00 +00:00
RefreshButton = new ToolStripMenuItem(@"&Refresh", Properties.Resources.arrow_refresh, new EventHandler((s, e) => RefreshWindowList())),
};
AppActionItems = new ToolStripItem[] {
2022-08-25 23:54:01 +00:00
new ToolStripMenuItem(@"&Settings", Properties.Resources.cog, new EventHandler((s, e) => SettingsWindow.Display())),
2022-08-25 23:53:17 +00:00
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())),
2022-08-25 23:59:00 +00:00
};
CtxMenu.Items.AddRange(AppActionItems);
2022-08-25 23:53:17 +00:00
2022-08-25 23:59:00 +00:00
SysIcon = new NotifyIcon {
Visible = true,
Icon = OriginalIcon,
Text = @"Top Most Application Manager",
};
SysIcon.ContextMenuStrip = CtxMenu;
SysIcon.MouseDown += SysIcon_MouseDown;
2022-08-25 23:54:53 +00:00
2022-08-25 23:59:00 +00:00
HotKeys = new HotKeyWindow();
SetForegroundHotKey(Settings.Get<int>(FOREGROUND_HOTKEY_SETTING));
2022-08-25 23:54:01 +00:00
2022-08-25 23:53:17 +00:00
Application.Run();
2022-08-25 23:54:53 +00:00
Shutdown();
}
2022-08-25 23:54:01 +00:00
2022-08-25 23:56:27 +00:00
public static void AddBlacklistedTitle(string title) {
2022-08-25 23:59:00 +00:00
lock(TitleBlacklist)
2022-08-25 23:56:27 +00:00
TitleBlacklist.Add(title);
}
public static void RemoveBlacklistedTitle(string title) {
2022-08-25 23:59:00 +00:00
lock(TitleBlacklist)
2022-08-25 23:56:27 +00:00
TitleBlacklist.RemoveAll(x => x == title);
}
public static void ApplyBlacklistedTitles(string[] arr) {
2022-08-25 23:59:00 +00:00
lock(TitleBlacklist) {
2022-08-25 23:56:27 +00:00
TitleBlacklist.Clear();
TitleBlacklist.AddRange(arr);
}
}
public static bool CheckBlacklistedTitles(string title) {
2022-08-25 23:59:00 +00:00
lock(TitleBlacklist)
2022-08-25 23:56:27 +00:00
return TitleBlacklist.Contains(title);
}
public static string[] GetBlacklistedTitles() {
2022-08-25 23:59:00 +00:00
lock(TitleBlacklist)
2022-08-25 23:56:27 +00:00
return TitleBlacklist.ToArray();
}
public static void SaveBlacklistedTitles() {
2022-08-25 23:59:00 +00:00
lock(TitleBlacklist)
2022-08-25 23:56:27 +00:00
Settings.Set(TITLE_BLACKLIST, TitleBlacklist.ToArray());
}
2022-08-25 23:54:53 +00:00
public static void Shutdown() {
HotKeys?.Dispose();
SysIcon?.Dispose();
2022-08-25 23:54:01 +00:00
GlobalMutex.ReleaseMutex();
}
2022-08-25 23:54:53 +00:00
private static bool? IsElevatedValue;
public static bool IsElevated() {
2022-08-25 23:59:00 +00:00
if(!IsElevatedValue.HasValue) {
using(WindowsIdentity identity = WindowsIdentity.GetCurrent())
2022-08-25 23:54:53 +00:00
IsElevatedValue = identity != null && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
}
return IsElevatedValue.Value;
}
public static void Elevate(string args = null) {
2022-08-25 23:59:00 +00:00
if(IsElevated())
2022-08-25 23:54:53 +00:00
return;
Shutdown();
Process.Start(new ProcessStartInfo {
UseShellExecute = true,
FileName = Application.ExecutablePath,
WorkingDirectory = Environment.CurrentDirectory,
Arguments = args ?? string.Empty,
Verb = @"runas",
});
Application.Exit();
}
2022-08-25 23:54:01 +00:00
public static void SetForegroundHotKey(int keyCode) {
SetForegroundHotKey((Win32ModKeys)(keyCode & 0xFFFF), (Keys)((keyCode & 0xFFFF0000) >> 16));
}
public static void SetForegroundHotKey(Win32ModKeys mods, Keys key) {
2022-08-25 23:54:53 +00:00
try {
Settings.Set(FOREGROUND_HOTKEY_SETTING, ((int)key << 16) | (int)mods);
HotKeys.Unregister(FOREGROUND_HOTKEY_ATOM);
2022-08-25 23:59:00 +00:00
if(mods != 0 && key != 0)
2022-08-25 23:54:53 +00:00
HotKeys.Register(FOREGROUND_HOTKEY_ATOM, mods, key, ToggleForegroundWindow);
2022-08-25 23:59:00 +00:00
} catch(Win32Exception ex) {
2022-08-25 23:54:53 +00:00
Debug.WriteLine(@"Hotkey registration failed:");
Debug.WriteLine(ex);
}
2022-08-25 23:53:17 +00:00
}
private static void RefreshWindowList() {
2022-08-25 23:59:00 +00:00
List<ToolStripItem> items = new List<ToolStripItem>();
2022-08-26 00:00:13 +00:00
if(Settings.Get(SHOW_WINDOW_LIST, true)) {
IEnumerable<WindowInfo> windows = WindowInfo.GetAllWindows();
Process lastProc = null;
bool procSeparator = Settings.Get(PROCESS_SEPARATOR_SETTING, false);
bool showEmptyTitles = Settings.Get(SHOW_EMPTY_WINDOW_SETTING, false);
bool listSelf = Settings.Get(LIST_SELF_SETTING, Debugger.IsAttached);
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 = 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))
continue;
// Skip items in the blacklist
if(CheckBlacklistedTitles(title))
continue;
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 = window.IsTopMost,
});
2022-08-25 23:54:01 +00:00
}
2022-08-25 23:53:17 +00:00
2022-08-26 00:00:13 +00:00
items.AddRange(ListActionItems);
2022-08-25 23:53:17 +00:00
}
2022-08-25 23:59:00 +00:00
items.AddRange(AppActionItems);
CtxMenu.Items.Clear();
CtxMenu.Items.AddRange(items.ToArray());
2022-08-25 23:53:17 +00:00
}
2022-08-25 23:59:00 +00:00
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}");
2022-08-25 23:54:01 +00:00
}
2022-08-25 23:56:27 +00:00
private class ActionTimeout {
private readonly Action Action;
private bool Continue = true;
private int Remaining = 0;
private const int STEP = 500;
public ActionTimeout(Action action, int timeout) {
Action = action ?? throw new ArgumentNullException(nameof(action));
2022-08-25 23:59:00 +00:00
if(timeout < 1)
2022-08-25 23:56:27 +00:00
throw new ArgumentException(@"Timeout must be a positive integer.", nameof(timeout));
Remaining = timeout;
new Thread(ThreadBody) { IsBackground = true }.Start();
}
private void ThreadBody() {
do {
Thread.Sleep(STEP);
Remaining -= STEP;
2022-08-25 23:59:00 +00:00
if(!Continue)
2022-08-25 23:56:27 +00:00
return;
2022-08-25 23:59:00 +00:00
} while(Remaining > 0);
2022-08-25 23:56:27 +00:00
Action.Invoke();
}
public void Cancel() {
Continue = false;
}
}
private static ActionTimeout IconTimeout;
2022-08-25 23:54:01 +00:00
public static void ToggleForegroundWindow() {
2022-08-25 23:59:00 +00:00
WindowInfo window = WindowInfo.GetForegroundWindow();
2022-08-25 23:55:31 +00:00
2022-08-25 23:59:00 +00:00
if(window.ToggleTopMost()) {
if(Settings.Get(TOGGLE_BALLOON_SETTING, false)) {
string title = window.Title;
2022-08-25 23:56:27 +00:00
SysIcon?.ShowBalloonTip(
2022-08-25 23:59:00 +00:00
2000, window.IsTopMost ? @"Always on top" : @"No longer always on top",
2022-08-25 23:55:31 +00:00
string.IsNullOrEmpty(title) ? @"Window has no title." : title,
ToolTipIcon.Info
);
}
2022-08-25 23:56:27 +00:00
2022-08-25 23:59:00 +00:00
if(SysIcon != null && Settings.Get(SHOW_HOTKEY_ICON, true)) {
Icon icon = window.Icon;
2022-08-25 23:56:27 +00:00
2022-08-25 23:59:00 +00:00
if(icon != null) {
2022-08-25 23:56:27 +00:00
IconTimeout?.Cancel();
SysIcon.Icon = icon;
IconTimeout = new ActionTimeout(() => SysIcon.Icon = OriginalIcon, 2000);
}
}
2022-08-25 23:59:00 +00:00
} else
TopMostFailed(window);
2022-08-25 23:54:53 +00:00
}
2022-08-25 23:59:00 +00:00
private static void CtxMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) {
LastSelectedItem = e.ClickedItem;
2022-08-25 23:54:01 +00:00
}
2022-08-25 23:59:00 +00:00
private static void CtxMenu_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
if(e.CloseReason == ToolStripDropDownCloseReason.ItemClicked && LastSelectedItem == RefreshButton)
e.Cancel = true;
2022-08-25 23:53:17 +00:00
}
private static void SysIcon_MouseDown(object sender, MouseEventArgs e) {
2022-08-25 23:59:00 +00:00
if((e.Button & MouseButtons.Right) > 0)
2022-08-25 23:54:01 +00:00
RefreshWindowList();
2022-08-25 23:53:17 +00:00
}
}
}