v1.6.0 - 2021-07-09

This commit is contained in:
flash 2022-08-26 02:01:04 +02:00
parent 103d3d4bff
commit 19f16b4f64
23 changed files with 1342 additions and 290 deletions

View file

@ -3,3 +3,5 @@
utility that lets you quickly force any program to be always on top
should work on any version of windows that supports .net framework 4.0
[Click here to download!](https://github.com/flashwave/topmostfriend/releases/latest)

View file

@ -15,7 +15,7 @@ namespace TopMostFriend {
}
public AboutWindow() {
Text = @"About Top Most Friend";
Text = Locale.String(@"AboutTitle");
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
BackgroundImage = Properties.Resources.about;
StartPosition = FormStartPosition.CenterScreen;
@ -24,11 +24,12 @@ namespace TopMostFriend {
ClientSize = Properties.Resources.about.Size;
MaximizeBox = MinimizeBox = false;
MaximumSize = MinimumSize = Size;
TopMost = true;
int tabIndex = 0;
Button closeButton = new Button {
Text = @"Close",
Text = Locale.String(@"AboutClose"),
Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
TabIndex = ++tabIndex,
};
@ -37,7 +38,7 @@ namespace TopMostFriend {
Controls.Add(closeButton);
Button websiteButton = new Button {
Text = @"Website",
Text = Locale.String(@"AboutWebsite"),
Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
TabIndex = ++tabIndex,
};
@ -46,7 +47,7 @@ namespace TopMostFriend {
Controls.Add(websiteButton);
Button donateButton = new Button {
Text = @"Donate",
Text = Locale.String(@"AboutDonate"),
Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
TabIndex = ++tabIndex,
};

View file

@ -0,0 +1,35 @@
using System;
using System.Threading;
namespace TopMostFriend {
public 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));
if(timeout < 1)
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;
if(!Continue)
return;
} while(Remaining > 0);
Action.Invoke();
}
public void Cancel() {
Continue = false;
}
}
}

View file

@ -35,26 +35,27 @@ namespace TopMostFriend {
MinimizeBox = MaximizeBox = false;
MinimumSize = Size;
DialogResult = DialogResult.Cancel;
TopMost = true;
BlacklistView = new ListBox {
TabIndex = 101,
IntegralHeight = false,
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
Location = new Point(BUTTON_WIDTH + (SPACING * 2), SPACING),
Location = new Point(SPACING, SPACING),
ClientSize = new Size(ClientSize.Width - BUTTON_WIDTH - (int)(SPACING * 3.5), ClientSize.Height - (int)(SPACING * 2.5)),
};
BlacklistView.SelectedIndexChanged += BlacklistView_SelectedIndexChanged;
BlacklistView.MouseDoubleClick += BlacklistView_MouseDoubleClick;
AddButton = new Button {
Anchor = AnchorStyles.Top | AnchorStyles.Left,
Text = @"Add",
Anchor = AnchorStyles.Top | AnchorStyles.Right,
Text = Locale.String(@"BlacklistAdd"),
ClientSize = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
Location = new Point(SPACING, SPACING),
Location = new Point(BlacklistView.Width + (SPACING * 2), SPACING),
TabIndex = 201,
};
EditButton = new Button {
Text = @"Edit",
Text = Locale.String(@"BlacklistEdit"),
Location = new Point(AddButton.Location.X, AddButton.Location.Y + AddButton.Height + SPACING),
Enabled = false,
Anchor = AddButton.Anchor,
@ -62,7 +63,7 @@ namespace TopMostFriend {
TabIndex = 202,
};
RemoveButton = new Button {
Text = @"Remove",
Text = Locale.String(@"BlacklistRemove"),
Location = new Point(AddButton.Location.X, EditButton.Location.Y + AddButton.Height + SPACING),
Enabled = false,
Anchor = AddButton.Anchor,
@ -75,16 +76,16 @@ namespace TopMostFriend {
RemoveButton.Click += RemoveButton_Click;
CancelButton = new Button {
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Text = @"Cancel",
Location = new Point(SPACING, ClientSize.Height - AddButton.ClientSize.Height - SPACING),
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Text = Locale.String(@"BlacklistCancel"),
Location = new Point(AddButton.Location.X, ClientSize.Height - AddButton.ClientSize.Height - SPACING),
ClientSize = AddButton.ClientSize,
TabIndex = 10001,
};
Button acceptButton = new Button {
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Text = @"Done",
Location = new Point(SPACING, ClientSize.Height - ((AddButton.ClientSize.Height + SPACING) * 2)),
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Text = Locale.String(@"BlacklistDone"),
Location = new Point(AddButton.Location.X, ClientSize.Height - ((AddButton.ClientSize.Height + SPACING) * 2)),
ClientSize = AddButton.ClientSize,
TabIndex = 10002,
};
@ -106,16 +107,18 @@ namespace TopMostFriend {
public string Original { get; }
public string String { get => TextBox.Text; }
private TextBox TextBox;
private readonly TextBox TextBox;
public BlacklistEditorWindow(string original = null) {
Original = original ?? string.Empty;
Text = original == null ? @"Adding new entry..." : $@"Editing {original}...";
Text = Locale.String(original == null ? @"BlacklistEditorAdding" : @"BlacklistEditorEditing", original);
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedToolWindow;
FormBorderStyle = FormBorderStyle.FixedSingle;
ClientSize = new Size(500, 39);
MaximizeBox = MinimizeBox = false;
MaximumSize = MinimumSize = Size;
TopMost = true;
Button cancelButton = new Button {
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right,
@ -123,7 +126,7 @@ namespace TopMostFriend {
Name = @"cancelButton",
Size = new Size(75, 23),
TabIndex = 102,
Text = @"Cancel",
Text = Locale.String(@"BlacklistEditorCancel"),
};
cancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
@ -133,14 +136,14 @@ namespace TopMostFriend {
Name = @"saveButton",
Size = new Size(75, 23),
TabIndex = 101,
Text = @"Save",
Text = Locale.String(@"BlacklistEditorSave"),
};
saveButton.Click += (s, e) => { DialogResult = DialogResult.OK; Close(); };
TextBox = new TextBox {
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
Location = new Point(8, 9),
Name = @"deviceSelection",
Name = @"deviceSelection", // ??????
Size = new Size(ClientSize.Width - 8 - cancelButton.Width - saveButton.Width - 19, 23),
TabIndex = 100,
Text = Original,

View file

@ -0,0 +1,357 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace TopMostFriend {
public class FirstRunWindow : Form {
public static void Display() {
using(FirstRunWindow firstRun = new FirstRunWindow())
firstRun.ShowDialog();
}
private bool CanClose = false;
private bool IsClosing = false;
private bool IsSizing = false;
private Button NextBtn { get; }
private Button PrevBtn { get; }
private Action NextAct = null;
private Action PrevAct = null;
private bool NextVisible { get => NextBtn.Visible; set => NextBtn.Visible = value; }
private bool PrevVisible { get => PrevBtn.Visible; set => PrevBtn.Visible = value; }
private Panel WorkArea { get; }
public FirstRunWindow() {
Text = Program.TITLE + @" v" + Application.ProductVersion.Substring(0, Application.ProductVersion.Length - 2);
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.FixedSingle;
AutoScaleMode = AutoScaleMode.Dpi;
MaximizeBox = MinimizeBox = false;
TopMost = true;
ClientSize = new Size(410, 80);
Controls.Add(new PictureBox {
Image = Properties.Resources.firstrun,
Size = Properties.Resources.firstrun.Size,
Location = new Point(0, 0),
});
NextBtn = new Button {
Text = Locale.String(@"FirstRunNext"),
Anchor = AnchorStyles.Right | AnchorStyles.Bottom,
Visible = false,
Location = new Point(ClientSize.Width - 81, ClientSize.Height - 29),
};
NextBtn.Click += NextBtn_Click;
Controls.Add(NextBtn);
PrevBtn = new Button {
Text = Locale.String(@"FirstRunPrev"),
Anchor = AnchorStyles.Left | AnchorStyles.Bottom,
Visible = false,
Location = new Point(6, ClientSize.Height - 29),
};
PrevBtn.Click += PrevBtn_Click;
Controls.Add(PrevBtn);
WorkArea = new Panel {
Dock = DockStyle.Fill,
};
Controls.Add(WorkArea);
}
private void PrevBtn_Click(object sender, EventArgs e) {
if(!PrevVisible)
return;
WorkArea.Controls.Clear();
if(PrevAct == null)
Close();
else
Invoke(PrevAct);
}
private void NextBtn_Click(object sender, EventArgs e) {
if(!NextVisible)
return;
WorkArea.Controls.Clear();
if(NextAct == null)
Close();
else
Invoke(NextAct);
}
protected override void OnShown(EventArgs e) {
base.OnShown(e);
Update();
Thread.Sleep(500);
ShowPageIntro();
}
protected override void OnFormClosing(FormClosingEventArgs e) {
if(e.CloseReason == CloseReason.UserClosing && !CanClose) {
e.Cancel = true;
if(!IsClosing) {
IsClosing = true;
SetHeight(80, new Action(() => {
CanClose = true;
Thread.Sleep(100);
Close();
}));
}
return;
}
base.OnFormClosing(e);
}
public void SetHeight(int height, Action onFinish = null) {
if(height < 80)
throw new ArgumentException(@"target height must be more than or equal to 80.", nameof(height));
if(IsSizing)
return;
IsSizing = true;
const int timeout = 1000 / 60;
double time = 0;
double period = timeout / 400d;
int currentHeight = ClientSize.Height;
int currentY = Location.Y;
int diffHeight = height - currentHeight;
int diffY = diffHeight / 2;
Action setHeight = new Action(() => {
int newHeight = currentHeight + (int)Math.Ceiling(time * diffHeight);
int newY = currentY - (int)Math.Ceiling(time * diffY);
ClientSize = new Size(ClientSize.Width, newHeight);
Location = new Point(Location.X, newY);
});
new Thread(() => {
Stopwatch sw = new Stopwatch();
try {
do {
sw.Restart();
Invoke(setHeight);
time += period;
int delay = timeout - (int)sw.ElapsedMilliseconds;
if(delay > 1)
Thread.Sleep(delay);
} while(time < 1d + period);
} finally {
sw.Stop();
if(onFinish != null)
Invoke(onFinish);
IsSizing = false;
}
}) {
IsBackground = true,
Priority = ThreadPriority.AboveNormal,
}.Start();
}
public void ShowPageIntro() {
Text = Locale.String(@"FirstRunWelcomeTitle");
PrevVisible = false;
NextVisible = true;
NextAct = ShowPageHotKey;
SetHeight(190, new Action(() => {
WorkArea.Controls.Add(new Label {
Text = Locale.String(@"FirstRunWelcomeIntro"),
Location = new Point(10, 90),
Size = new Size(ClientSize.Width - 20, 200),
});
}));
}
public void ShowPageHotKey() {
Text = Locale.String(@"FirstRunHotKeyTitle");
PrevVisible = NextVisible = true;
PrevAct = ShowPageIntro;
NextAct = () => {
Program.SetForegroundHotKey(Settings.Get(Program.FOREGROUND_HOTKEY_SETTING, 0));
ShowPageElevation();
};
SetHeight(230, new Action(() => {
WorkArea.Controls.Add(new Label {
Text = Locale.String(@"FirstRunHotKeyExplain"),
Location = new Point(10, 90),
Size = new Size(ClientSize.Width - 20, 40),
});
SettingsWindow.CreateHotKeyInput(
WorkArea,
() => Settings.Get(Program.FOREGROUND_HOTKEY_SETTING, 0),
keyCode => Settings.Set(Program.FOREGROUND_HOTKEY_SETTING, keyCode),
0,
110
);
CheckBox flShowNotification = new CheckBox {
Text = Locale.String(@"FirstRunHotKeyNotify"),
Location = new Point(12, 170),
Checked = Settings.Get(Program.TOGGLE_BALLOON_SETTING, Program.ToggleBalloonDefault),
AutoSize = true,
TabIndex = 201,
};
flShowNotification.CheckedChanged += (s, e) => {
Settings.Set(Program.TOGGLE_BALLOON_SETTING, flShowNotification.Checked);
};
WorkArea.Controls.Add(flShowNotification);
}));
}
public void ShowPageElevation() {
Text = Locale.String(@"FirstRunAdminTitle");
PrevVisible = NextVisible = true;
PrevAct = ShowPageHotKey;
NextAct = ShowPageThanks;
SetHeight(280, () => {
WorkArea.Controls.Add(new Label {
Text = Locale.String(@"FirstRunAdminExplain"),
Location = new Point(10, 90),
Size = new Size(ClientSize.Width - 20, 40),
});
bool alwaysAdmin = Settings.Get(Program.ALWAYS_ADMIN_SETTING, false);
bool implicitAdmin = Settings.Get(Program.ALWAYS_RETRY_ELEVATED, false);
RadioButton rdAsk = new RadioButton {
Text = Locale.String(@"FirstRunAdminOptionAsk"),
Location = new Point(10, 140),
Size = new Size(ClientSize.Width - 20, 30),
Appearance = Appearance.Button,
Checked = !alwaysAdmin && !implicitAdmin,
};
rdAsk.CheckedChanged += (s, e) => {
if(rdAsk.Checked) {
Settings.Set(Program.ALWAYS_ADMIN_SETTING, false);
Settings.Set(Program.ALWAYS_RETRY_ELEVATED, false);
}
};
WorkArea.Controls.Add(rdAsk);
RadioButton rdImplicit = new RadioButton {
Text = Locale.String(@"FirstRunAdminOptionImplicit"),
Location = new Point(rdAsk.Location.X, rdAsk.Location.Y + 36),
Size = rdAsk.Size,
Appearance = rdAsk.Appearance,
Checked = implicitAdmin && !alwaysAdmin,
};
rdImplicit.CheckedChanged += (s, e) => {
if(rdImplicit.Checked) {
Settings.Set(Program.ALWAYS_ADMIN_SETTING, false);
Settings.Set(Program.ALWAYS_RETRY_ELEVATED, true);
}
};
WorkArea.Controls.Add(rdImplicit);
RadioButton rdAlways = new RadioButton {
Text = Locale.String(@"FirstRunAdminOptionAlways"),
Location = new Point(rdAsk.Location.X, rdImplicit.Location.Y + 36),
Size = rdAsk.Size,
Appearance = rdAsk.Appearance,
Checked = alwaysAdmin,
};
rdAlways.CheckedChanged += (s, e) => {
if(rdAlways.Checked) {
Settings.Set(Program.ALWAYS_ADMIN_SETTING, true);
Settings.Set(Program.ALWAYS_RETRY_ELEVATED, false);
}
};
WorkArea.Controls.Add(rdAlways);
});
}
public void ShowPageThanks() {
Text = Locale.String(@"FirstRunThanksTitle");
PrevVisible = NextVisible = true;
PrevAct = ShowPageElevation;
NextAct = CheckRestartNeeded;
SetHeight(270, () => {
Label thankYou = new Label {
Text = Locale.String(@"FirstRunThanksThank"),
Location = new Point(10, 90),
Size = new Size(ClientSize.Width - 20, 20),
};
WorkArea.Controls.Add(thankYou);
string updateLinkString = Locale.String(@"FirstRunThanksUpdate");
int websiteStart = updateLinkString.IndexOf(@"[WEB]");
updateLinkString = updateLinkString.Substring(0, websiteStart) + updateLinkString.Substring(websiteStart + 5);
int websiteEnd = updateLinkString.IndexOf(@"[/WEB]");
updateLinkString = updateLinkString.Substring(0, websiteEnd) + updateLinkString.Substring(websiteEnd + 6);
int changelogStart = updateLinkString.IndexOf(@"[CHANGELOG]");
updateLinkString = updateLinkString.Substring(0, changelogStart) + updateLinkString.Substring(changelogStart + 11);
int changelogEnd = updateLinkString.IndexOf(@"[/CHANGELOG]");
updateLinkString = updateLinkString.Substring(0, changelogEnd) + updateLinkString.Substring(changelogEnd + 12);
LinkLabel updateLink;
WorkArea.Controls.Add(updateLink = new LinkLabel {
Text = updateLinkString,
Location = new Point(10, 120),
Size = new Size(ClientSize.Width - 20, 34),
Font = thankYou.Font,
Links = {
new LinkLabel.Link(websiteStart, websiteEnd - websiteStart, @"https://flash.moe/topmostfriend"),
new LinkLabel.Link(changelogStart, changelogEnd - changelogStart, @"https://flash.moe/topmostfriend/changelog.php"),
},
});
updateLink.LinkClicked += (s, e) => {
Process.Start((string)e.Link.LinkData);
};
string settingsLinkString = Locale.String(@"FirstRunThanksSettings");
int settingsStart = settingsLinkString.IndexOf(@"[SETTINGS]");
settingsLinkString = settingsLinkString.Substring(0, settingsStart) + settingsLinkString.Substring(settingsStart + 10);
int settingsEnd = settingsLinkString.IndexOf(@"[/SETTINGS]");
settingsLinkString = settingsLinkString.Substring(0, settingsEnd) + settingsLinkString.Substring(settingsEnd + 11);
LinkLabel settingsLink;
WorkArea.Controls.Add(settingsLink = new LinkLabel {
Text = settingsLinkString,
Location = new Point(10, 160),
Size = new Size(ClientSize.Width - 20, 30),
Font = thankYou.Font,
Links = {
new LinkLabel.Link(settingsStart, settingsEnd - settingsStart),
},
});
settingsLink.LinkClicked += (s, e) => {
SettingsWindow.Display();
};
WorkArea.Controls.Add(new Label {
Text = Locale.String(@"FirstRunThanksAdmin"),
Location = new Point(10, 200),
Size = new Size(ClientSize.Width - 20, 80),
});
});
}
public void CheckRestartNeeded() {
if(Settings.Get(Program.ALWAYS_ADMIN_SETTING, false))
UAC.RestartElevated();
Close();
}
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
namespace TopMostFriend.Languages {
[XmlRoot(@"Language")]
public class Language {
[XmlElement(@"Info")]
public LanguageInfo Info { get; set; }
[XmlArray(@"Strings")]
[XmlArrayItem(@"String", Type = typeof(LanguageString))]
public LanguageString[] Strings { get; set; }
public LanguageString GetString(string name) {
if(name == null)
throw new ArgumentNullException(nameof(name));
return Strings.FirstOrDefault(s => name.Equals(s.Name));
}
}
}

View file

@ -0,0 +1,21 @@
using System.Xml.Serialization;
namespace TopMostFriend.Languages {
public class LanguageInfo {
[XmlElement(@"Id")]
public string Id { get; set; }
[XmlElement(@"NameNative")]
public string NameNative { get; set; }
[XmlElement(@"NameEnglish")]
public string NameEnglish { get; set; }
[XmlElement(@"TargetVersion")]
public string TargetVersion { get; set; }
public override string ToString() {
return $@"{NameNative} / {NameEnglish} ({Id})";
}
}
}

View file

@ -0,0 +1,19 @@
using System.Xml.Serialization;
namespace TopMostFriend.Languages {
public class LanguageString {
[XmlAttribute(@"name")]
public string Name { get; set; }
[XmlText]
public string Value { get; set; }
public string Format(params object[] args) {
return string.Format(Value, args);
}
public override string ToString() {
return $@"{Name}: {Value}";
}
}
}

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8" ?>
<Language>
<Info>
<Id>en-GB</Id>
<NameNative>British English</NameNative>
<NameEnglish>British English</NameEnglish>
<TargetVersion>1.6.0</TargetVersion>
</Info>
<Strings>
<String name="AlreadyRunning">An instance of {0} is already running.</String>
<String name="TrayRefresh">&amp;Refresh</String>
<String name="TraySettings">&amp;Settings</String>
<String name="TrayAbout">&amp;About</String>
<String name="TrayQuit">&amp;Quit</String>
<String name="NotifyOnTop">Now always on top</String>
<String name="NotifyNoLonger">No longer always on top</String>
<String name="NotifyNoTitle">Window has no title.</String>
<String name="ErrUnableAlterStatus1">Wasn't able to change always on top status on this window.</String>
<String name="ErrUnableAlterStatus2">Do you want to try again as an Administrator? A User Account Control dialog will pop up after selecting Yes.</String>
<String name="ErrUnableAlterStatusProtected">{0} was still unable to change always on top status for the window. It might be protected by Windows.</String>
<String name="AboutTitle">About {0}</String>
<String name="AboutClose">Close</String>
<String name="AboutWebsite">Website</String>
<String name="AboutDonate">Donate</String>
<String name="SettingsTitle">{0} Settings</String>
<String name="SettingsApply">Apply</String>
<String name="SettingsCancel">Cancel</String>
<String name="SettingsOk">OK</String>
<String name="SettingsHotKeysTitle">Hot Keys</String>
<String name="SettingsHotKeysReset">Reset</String>
<String name="SettingsHotKeysToggle">Toggle always on top status on active window</String>
<String name="SettingsOptionsTitle">Options</String>
<String name="SettingsOptionsToggleNotify">Show notification when using toggle hot key</String>
<String name="SettingsOptionsToggleNotifyIcon">Show icon of window affected by hot key</String>
<String name="SettingsOptionsElevatedRetry">Always retry changing always on top status as Administrator on first failure</String>
<String name="SettingsOptionsShiftBlacklist">SHIFT+CLICK items in the tray area list to add to the title blacklist</String>
<String name="SettingsOptionsRevertOnExit">Revert status to before {0} altered them on exit</String>
<String name="SettingsOptionsShowTrayList">Show list of open windows in the tray area menu</String>
<String name="SettingsOptionsAlwaysAdmin">Always run as Administrator</String>
<String name="SettingsLanguageTitle">Language</String>
<String name="SettingsLanguageChangedInfo">You should restart {0} for the language change to fully take effect. A lot of text will still appear in the previously selected language.</String>
<String name="SettingsOtherTitle">Other</String>
<String name="SettingsOtherBlacklistButton">Manage Blacklist...</String>
<String name="SettingsOtherBlacklistWindowTitle">Title Blacklist</String>
<String name="SettingsOtherStartupButton">Start with Windows...</String>
<String name="SettingsOtherStartupConfirm">
Should {0} start with Windows?
Clicking Yes will create a shortcut to the {0} in your Start-up folder, clicking No will delete the shortcut.
Make sure you've placed your {0} executable in a permanent location before clicking Yes.
</String>
<String name="SettingsOtherResetButton">Reset All Settings...</String>
<String name="SettingsOtherResetConfirm">
This will reset all {0} settings and restart the application.
Are you absolutely sure?
</String>
<String name="BlacklistAdd">Add</String>
<String name="BlacklistEdit">Edit</String>
<String name="BlacklistRemove">Remove</String>
<String name="BlacklistCancel">Cancel</String>
<String name="BlacklistDone">Done</String>
<String name="BlacklistEditorAdding">Adding new entry...</String>
<String name="BlacklistEditorEditing">Editing {0}...</String>
<String name="BlacklistEditorCancel">Cancel</String>
<String name="BlacklistEditorSave">Save</String>
<String name="FirstRunNext">Next</String>
<String name="FirstRunPrev">Previous</String>
<String name="FirstRunWelcomeTitle">Welcome to {0}!</String>
<String name="FirstRunWelcomeIntro">
{0} is a utility that lets you manage the always-on-top state of windows from programs you have open.
You will now be taken through a few steps to configure {0} to your liking!
</String>
<String name="FirstRunHotKeyTitle">Selecting a hot key</String>
<String name="FirstRunHotKeyExplain">
Select a global hot key for toggling the always on top status of whatever window is currently in the foreground.
If you don't wish to specify one just hit Reset followed by Next.
</String>
<String name="FirstRunHotKeyNotify">Show a notification when the hot key has been used</String>
<String name="FirstRunAdminTitle">Administrator actions</String>
<String name="FirstRunAdminExplain">
Sometimes {0} is not able to change the always on top state for certain windows without having administrative privileges.
{0} provides multiple options for this situation:
</String>
<String name="FirstRunAdminOptionAsk">Ask me what to do when it's required.</String>
<String name="FirstRunAdminOptionImplicit">Ask for administrative privileges automatically when required.</String>
<String name="FirstRunAdminOptionAlways">Always run {0} as Administrator (not recommended).</String>
<String name="FirstRunThanksTitle">Thank you!</String>
<String name="FirstRunThanksThank">Thank you for using {0}!</String>
<String name="FirstRunThanksUpdate">Be sure to check the {0} [WEB]website[/WEB] and [CHANGELOG]changelog[/CHANGELOG] for updates from time to time.</String>
<String name="FirstRunThanksSettings">There are more options you may be interested in taking a look at in the [SETTINGS]Settings[/SETTINGS] window.</String>
<String name="FirstRunThanksAdmin">If you have chosen to always run {0} as Administrator, you will receive a User Account Control prompt after hitting next.</String>
</Strings>
</Language>

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8" ?>
<Language>
<Info>
<Id>nl-NL</Id>
<NameNative>Nederlands</NameNative>
<NameEnglish>Dutch</NameEnglish>
<TargetVersion>1.6.0</TargetVersion>
</Info>
<Strings>
<String name="AlreadyRunning">{0} is al gestart.</String>
<String name="TrayRefresh">&amp;Verversen</String>
<String name="TraySettings">&amp;Instellingen</String>
<String name="TrayAbout">&amp;Over</String>
<String name="TrayQuit">&amp;Sluiten</String>
<String name="NotifyOnTop">Nu altijd op voorgrond</String>
<String name="NotifyNoLonger">Niet langer altijd op voorgrond</String>
<String name="NotifyNoTitle">Venster heeft geen titel.</String>
<String name="ErrUnableAlterStatus1">Het was niet mogelijk om de altijd op voorgrond status van dit venster te veranderen.</String>
<String name="ErrUnableAlterStatus2">Wil je het opnieuw proberen als Administrator? Een Gebruikersaccountbeheer dialoog zal verschijnen als je op Ja klikt.</String>
<String name="ErrUnableAlterStatusProtected">{0} kon nog steeds niet de altijd op voorgrond status van dit venster veranderen. Het wordt mogelijk beschermd door Windows.</String>
<String name="AboutTitle">Over {0}</String>
<String name="AboutClose">Sluiten</String>
<String name="AboutWebsite">Website</String>
<String name="AboutDonate">Doneer</String>
<String name="SettingsTitle">{0} Instellingen</String>
<String name="SettingsApply">Toepassen</String>
<String name="SettingsCancel">Annuleren</String>
<String name="SettingsOk">OK</String>
<String name="SettingsHotKeysTitle">Sneltoetsen</String>
<String name="SettingsHotKeysReset">Herstel</String>
<String name="SettingsHotKeysToggle">Schakel altijd op voorgrond status op actief venster</String>
<String name="SettingsOptionsTitle">Opties</String>
<String name="SettingsOptionsToggleNotify">Toon notificatie als de sneltoets gebruikt is</String>
<String name="SettingsOptionsToggleNotifyIcon">Toon icoon van venster dat beïnvloed was door de sneltoets</String>
<String name="SettingsOptionsElevatedRetry">Probeer altijd opnieuw als Administrator bij de eerste mislukking van het veranderen van de status</String>
<String name="SettingsOptionsShiftBlacklist">SHIFT+CLICK items in de systeemvak lijst om ze toe te voegen aan de titel blacklist</String>
<String name="SettingsOptionsRevertOnExit">Herstel status naar voordat {0} ze heeft aangepast tijdens het sluiten</String>
<String name="SettingsOptionsShowTrayList">Geef lijst met open venster weer in het systeemvak menu</String>
<String name="SettingsOptionsAlwaysAdmin">Start altijd als Administrator</String>
<String name="SettingsLanguageTitle">Taal (Language)</String>
<String name="SettingsLanguageChangedInfo">Je moet {0} opnieuw starten om de verandering van de taal volledig effect te laten nemen. Veel tekst zal nog steeds verschijnen in de voorheen geselecteerde taal.</String>
<String name="SettingsOtherTitle">Overig</String>
<String name="SettingsOtherBlacklistButton">Beheer Blacklist...</String>
<String name="SettingsOtherBlacklistWindowTitle">Titel Blacklist</String>
<String name="SettingsOtherStartupButton">Start met Windows...</String>
<String name="SettingsOtherStartupConfirm">
Moet {0} tegelijkertijd met Windows starten?
Als je op Ja klikt zal een een snelkoppeling aangemaakt worden in de Opstarten map van het start menu, als je op Nee klikt wordt de snelkoppeling verwijderd.
Zorg ervoor dat je het EXE bestand voor {0} in een permanente locatie hebt voordat je op Ja klikt.
</String>
<String name="SettingsOtherResetButton">Herstel alle instellingen...</String>
<String name="SettingsOtherResetConfirm">
Dit zal alle instellingen verwijderen en het programma opnieuw opstarten.
Weet je het absoluut zeker?
</String>
<String name="BlacklistAdd">Toevoegen</String>
<String name="BlacklistEdit">Bewerken</String>
<String name="BlacklistRemove">Verwijderen</String>
<String name="BlacklistCancel">Annuleren</String>
<String name="BlacklistDone">Klaar</String>
<String name="BlacklistEditorAdding">Nieuw item toevoegen...</String>
<String name="BlacklistEditorEditing">{0} bewerken...</String>
<String name="BlacklistEditorCancel">Annuleren</String>
<String name="BlacklistEditorSave">Opslaan</String>
<String name="FirstRunNext">Volgende</String>
<String name="FirstRunPrev">Vorige</String>
<String name="FirstRunWelcomeTitle">Welkom bij {0}!</String>
<String name="FirstRunWelcomeIntro">
{0} is een hulpprogramma dat jou controle geeft over de altijd-op-voorgrond status van programma's die je open hebt.
Je zal nu door een aantal stappen genomen worden om {0} naar jouw wens te configureren!
</String>
<String name="FirstRunHotKeyTitle">Sneltoets selecteren</String>
<String name="FirstRunHotKeyExplain">
Selecteer een globale sneltoets voor het wijzigen van de altijd op voorgrond status van het venster dat op dat moment in de voorgrond staat.
Als je geen sneltoets toe wil wijzen, druk op Herstel en vervolgens op Volgende.
</String>
<String name="FirstRunHotKeyNotify">Toon een melding wanneer de sneltoets is gebruikt</String>
<String name="FirstRunAdminTitle">Administrator acties</String>
<String name="FirstRunAdminExplain">
Soms kan {0} niet de altijd op voorgrond status van bepaalde vensters aanpassen omdat daar administratieve rechten voor nodig zijn.
{0} geeft je de volgende opties voor deze situatie:
</String>
<String name="FirstRunAdminOptionAsk">Vraag mij wat te doen wanneer het nodig is.</String>
<String name="FirstRunAdminOptionImplicit">Vraag mij automatisch om administratieve rechten wanneer het nodig is.</String>
<String name="FirstRunAdminOptionAlways">Start {0} altijd als Administrator (niet aanbevolen).</String>
<String name="FirstRunThanksTitle">Bedankt!</String>
<String name="FirstRunThanksThank">Bedankt voor het gebruiken van {0}!</String>
<String name="FirstRunThanksUpdate">Controlleer de {0} [WEB]website[/WEB] en [CHANGELOG]changelog[/CHANGELOG] eens in de zoveel tijd om te kijken of er updates zijn.</String>
<String name="FirstRunThanksSettings">Naast de opties die je al gekregen hebt, zijn er nog meer beschikbaar in het [SETTINGS]Instellingen[/SETTINGS] venster als je daarin geïnteresseerd bent.</String>
<String name="FirstRunThanksAdmin">Als je gekozen hebt om {0} altijd als Administrator te starten, zal je een Gebruikersaccountbeheer dialoog zien nadat je op volgende klikt.</String>
</Strings>
</Language>

89
TopMostFriend/Locale.cs Normal file
View file

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using TopMostFriend.Languages;
namespace TopMostFriend {
public static class Locale {
public const string DEFAULT = @"en-GB";
private static XmlSerializer Serializer { get; }
private static Dictionary<string, Language> Languages { get; }
private static Language ActiveLanguage { get; set; }
static Locale() {
Serializer = new XmlSerializer(typeof(Language));
Languages = new Dictionary<string, Language>();
Assembly currentAsm = Assembly.GetExecutingAssembly();
string[] resources = currentAsm.GetManifestResourceNames();
foreach(string resource in resources)
if(resource.StartsWith(@"TopMostFriend.Languages.") && resource.EndsWith(@".xml"))
using(Stream resourceStream = currentAsm.GetManifestResourceStream(resource))
LoadLanguage(resourceStream);
}
public static string LoadLanguage(Stream stream) {
Language lang = (Language)Serializer.Deserialize(stream);
foreach(LanguageString ls in lang.Strings)
ls.Value = ls.Value.Trim();
Languages.Add(lang.Info.Id, lang);
if(ActiveLanguage == null && DEFAULT.Equals(lang.Info.Id))
ActiveLanguage = lang;
#if DEBUG
Debug.WriteLine(@" ==========");
Debug.WriteLine(lang.Info);
foreach(LanguageString str in lang.Strings)
Debug.WriteLine(str);
Debug.WriteLine(string.Empty);
#endif
return lang.Info.Id;
}
public static LanguageInfo GetCurrentLanguage() {
return ActiveLanguage.Info;
}
public static LanguageInfo[] GetAvailableLanguages() {
return Languages.Values.Select(l => l.Info).ToArray();
}
public static string GetPreferredLanguage() {
return Settings.Has(Program.LANGUAGE)
? Settings.Get(Program.LANGUAGE, DEFAULT)
: CultureInfo.InstalledUICulture.Name;
}
public static void SetLanguage(string langId) {
if(!Languages.ContainsKey(langId))
langId = DEFAULT;
ActiveLanguage = Languages[langId];
}
public static void SetLanguage(LanguageInfo langInfo) {
SetLanguage(langInfo.Id);
}
public static string String(string name, params object[] args) {
LanguageString str = ActiveLanguage.GetString(name);
if(str == null)
return name;
List<object> rargs = new List<object> { Program.TITLE };
rargs.AddRange(args);
return str.Format(rargs.ToArray());
}
public static void Meow() {
Debug.WriteLine(@"meow");
}
}
}

View file

@ -5,7 +5,6 @@ using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Windows.Forms;
@ -17,6 +16,8 @@ namespace TopMostFriend {
private static HotKeyWindow HotKeys;
private static Icon OriginalIcon;
public const string TITLE = @"Top Most Friend";
private const string GUID =
#if DEBUG
@"{1A22D9CA-2AA9-48F2-B007-3A48CF205CDD}";
@ -25,6 +26,8 @@ namespace TopMostFriend {
#endif
private static readonly Mutex GlobalMutex = new Mutex(true, GUID);
private const string CUSTOM_LANGUAGE = @"TopMostFriendLanguage.xml";
public const string FOREGROUND_HOTKEY_ATOM = @"{86795D64-770D-4BD6-AA26-FA638FBAABCF}";
#if DEBUG
public const string FOREGROUND_HOTKEY_SETTING = @"ForegroundHotKey_DEBUG";
@ -44,6 +47,10 @@ namespace TopMostFriend {
public const string TITLE_BLACKLIST = @"TitleBlacklist";
public const string SHOW_HOTKEY_ICON = @"ShowHotkeyIcon";
public const string SHOW_WINDOW_LIST = @"ShowWindowList";
public const string LAST_VERSION = @"LastVersion";
public const string ALWAYS_RETRY_ELEVATED = @"AlwaysRetryElevated";
public const string REVERT_ON_EXIT = @"RevertOnExit";
public const string LANGUAGE = @"Language";
private static ToolStripItem RefreshButton;
private static ToolStripItem LastSelectedItem = null;
@ -53,8 +60,39 @@ namespace TopMostFriend {
private static ToolStripItem[] ListActionItems;
private static ToolStripItem[] AppActionItems;
private static readonly Dictionary<IntPtr, bool> OriginalStates = new Dictionary<IntPtr, bool>();
[STAThread]
public static void Main(string[] args) {
public static int Main(string[] args) {
Settings.Set(LAST_VERSION, Application.ProductVersion);
IEnumerable<string> cliToggleNew = args.Where(a => a.StartsWith(@"--toggle=")).Select(a => a.Substring(9));
if(cliToggleNew.Any()) {
bool is32bit = IntPtr.Size == 4;
foreach(string hwndStr in cliToggleNew) {
IntPtr hwnd;
if(is32bit) {
if(int.TryParse(hwndStr, out int hwnd32))
hwnd = new IntPtr(hwnd32);
else
return 1;
} else {
if(long.TryParse(hwndStr, out long hwnd64))
hwnd = new IntPtr(hwnd64);
else
return 1;
}
// pass 0 to skip implicit FindOwnerId call
WindowInfo wi = new WindowInfo(hwnd, 0);
if(!wi.ToggleTopMost(!args.Contains($@"--background={hwndStr}")))
return 2;
}
return 0;
}
if(Environment.OSVersion.Version.Major >= 6)
Win32.SetProcessDPIAware();
@ -64,26 +102,53 @@ namespace TopMostFriend {
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)) {
WindowInfo cliWindow = new WindowInfo(cliToggleHWnd);
if(!cliWindow.ToggleTopMost())
TopMostFailed(cliWindow);
string cliToggleOld = args.FirstOrDefault(a => a.StartsWith(@"--hwnd="))?.Substring(7);
if(!string.IsNullOrEmpty(cliToggleOld)) {
IntPtr cliToggleHWnd = IntPtr.Zero;
if(IntPtr.Size == 4) {
if(int.TryParse(cliToggleOld, out int hwnd32))
cliToggleHWnd = new IntPtr(hwnd32);
} else {
if(long.TryParse(cliToggleOld, out long hwnd64))
cliToggleHWnd = new IntPtr(hwnd64);
}
if(cliToggleHWnd != IntPtr.Zero) {
WindowInfo cliWindow = new WindowInfo(cliToggleHWnd);
if(!cliWindow.ToggleTopMost())
TopMostFailed(cliWindow);
}
}
if(args.Contains(@"--stop"))
return;
return 0;
if(File.Exists(CUSTOM_LANGUAGE)) {
string customLanguage;
using(Stream s = File.OpenRead(CUSTOM_LANGUAGE))
try {
customLanguage = Locale.LoadLanguage(s);
} catch {
customLanguage = Locale.DEFAULT;
}
Locale.SetLanguage(customLanguage);
} else
Locale.SetLanguage(Locale.GetPreferredLanguage());
if(!GlobalMutex.WaitOne(0, true)) {
MessageBox.Show(@"An instance of Top Most Friend is already running.", @"Top Most Friend");
return;
MessageBox.Show(Locale.String(@"AlreadyRunning"), TITLE, MessageBoxButtons.OK, MessageBoxIcon.Information);
return -1;
}
bool isFirstRun = !Settings.Has(FOREGROUND_HOTKEY_SETTING);
Settings.SetDefault(FOREGROUND_HOTKEY_SETTING, 0);
Settings.SetDefault(ALWAYS_ADMIN_SETTING, false);
Settings.SetDefault(SHIFT_CLICK_BLACKLIST, true);
Settings.SetDefault(SHOW_HOTKEY_ICON, true);
Settings.SetDefault(SHOW_WINDOW_LIST, true);
Settings.SetDefault(ALWAYS_RETRY_ELEVATED, false);
Settings.SetDefault(REVERT_ON_EXIT, false);
// 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);
@ -102,9 +167,9 @@ namespace TopMostFriend {
Settings.Set(TITLE_BLACKLIST, titles.ToArray());
}
if(Settings.Get<bool>(ALWAYS_ADMIN_SETTING) && !IsElevated()) {
Elevate();
return;
if(Settings.Get<bool>(ALWAYS_ADMIN_SETTING) && !UAC.IsElevated) {
UAC.RestartElevated();
return -2;
}
TitleBlacklist.Clear();
@ -134,19 +199,19 @@ namespace TopMostFriend {
CtxMenu.ItemClicked += CtxMenu_ItemClicked;
ListActionItems = new ToolStripItem[] {
new ToolStripSeparator(),
RefreshButton = new ToolStripMenuItem(@"&Refresh", Properties.Resources.arrow_refresh, new EventHandler((s, e) => RefreshWindowList())),
RefreshButton = new ToolStripMenuItem(Locale.String(@"TrayRefresh"), 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())),
new ToolStripMenuItem(Locale.String(@"TraySettings"), Properties.Resources.cog, new EventHandler((s, e) => SettingsWindow.Display())),
new ToolStripMenuItem(Locale.String(@"TrayAbout"), Properties.Resources.help, new EventHandler((s, e) => AboutWindow.Display())),
new ToolStripMenuItem(Locale.String(@"TrayQuit"), 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",
Text = TITLE,
};
SysIcon.ContextMenuStrip = CtxMenu;
SysIcon.MouseDown += SysIcon_MouseDown;
@ -154,9 +219,26 @@ namespace TopMostFriend {
HotKeys = new HotKeyWindow();
SetForegroundHotKey(Settings.Get<int>(FOREGROUND_HOTKEY_SETTING));
if(isFirstRun)
FirstRunWindow.Display();
Application.Run();
if(Settings.Get(REVERT_ON_EXIT, false))
RevertTopMostStatus();
Shutdown();
return 0;
}
public static void RevertTopMostStatus() {
foreach(KeyValuePair<IntPtr, bool> originalState in OriginalStates) {
WindowInfo wi = new WindowInfo(originalState.Key);
if(wi.IsTopMost != originalState.Value)
if(!wi.ToggleTopMost(false))
TopMostFailed(wi, false);
}
}
public static void AddBlacklistedTitle(string title) {
@ -192,33 +274,6 @@ namespace TopMostFriend {
GlobalMutex.ReleaseMutex();
}
private static bool? IsElevatedValue;
public static bool IsElevated() {
if(!IsElevatedValue.HasValue) {
using(WindowsIdentity identity = WindowsIdentity.GetCurrent())
IsElevatedValue = identity != null && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
}
return IsElevatedValue.Value;
}
public static void Elevate(string args = null) {
if(IsElevated())
return;
Shutdown();
Process.Start(new ProcessStartInfo {
UseShellExecute = true,
FileName = Application.ExecutablePath,
WorkingDirectory = Environment.CurrentDirectory,
Arguments = args ?? string.Empty,
Verb = @"runas",
});
Application.Exit();
}
public static void SetForegroundHotKey(int keyCode) {
SetForegroundHotKey((Win32ModKeys)(keyCode & 0xFFFF), (Keys)((keyCode & 0xFFFF0000) >> 16));
}
@ -270,8 +325,13 @@ namespace TopMostFriend {
if(Settings.Get(SHIFT_CLICK_BLACKLIST, true) && Control.ModifierKeys.HasFlag(Keys.Shift)) {
AddBlacklistedTitle(title);
SaveBlacklistedTitles();
} else if(!window.ToggleTopMost())
TopMostFailed(window);
} else {
if(window.ToggleTopMost()) {
if(!OriginalStates.ContainsKey(window.Handle))
OriginalStates[window.Handle] = !window.IsTopMost;
} else
TopMostFailed(window);
}
})) {
CheckOnClick = true,
Checked = window.IsTopMost,
@ -287,50 +347,29 @@ namespace TopMostFriend {
CtxMenu.Items.AddRange(items.ToArray());
}
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.");
private static void TopMostFailed(WindowInfo window, bool switchWindow = true) {
bool retryElevated = Settings.Get(ALWAYS_RETRY_ELEVATED, false),
isElevated = UAC.IsElevated;
if(!IsElevated()) {
sb.AppendLine(@"Do you want to restart Top Most Friend as administrator and try again?");
buttons = MessageBoxButtons.YesNo;
if(!retryElevated) {
MessageBoxButtons buttons = MessageBoxButtons.OK;
StringBuilder sb = new StringBuilder();
sb.AppendLine(Locale.String(@"ErrUnableAlterStatus1"));
if(!isElevated) {
sb.AppendLine(Locale.String(@"ErrUnableAlterStatus2"));
buttons = MessageBoxButtons.YesNo;
}
retryElevated = MessageBox.Show(sb.ToString(), TITLE, buttons, MessageBoxIcon.Error) == DialogResult.Yes;
}
DialogResult result = MessageBox.Show(sb.ToString(), @"Top Most Friend", buttons, MessageBoxIcon.Error);
if(result == DialogResult.Yes)
Elevate($@"--hwnd={window.Handle}");
}
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));
if(timeout < 1)
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;
if(!Continue)
return;
} while(Remaining > 0);
Action.Invoke();
}
public void Cancel() {
Continue = false;
if(retryElevated) {
if(window.ToggleTopMostElevated(switchWindow)) {
if(!OriginalStates.ContainsKey(window.Handle))
OriginalStates[window.Handle] = !window.IsTopMost;
} else
MessageBox.Show(Locale.String(@"ErrUnableAlterStatusProtected"), TITLE, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
@ -343,8 +382,8 @@ namespace TopMostFriend {
if(Settings.Get(TOGGLE_BALLOON_SETTING, false)) {
string title = window.Title;
SysIcon?.ShowBalloonTip(
2000, window.IsTopMost ? @"Always on top" : @"No longer always on top",
string.IsNullOrEmpty(title) ? @"Window has no title." : title,
2000, Locale.String(window.IsTopMost ? @"NotifyOnTop" : @"NotifyNoLonger"),
string.IsNullOrEmpty(title) ? Locale.String(@"NotifyNoTitle") : title,
ToolTipIcon.Info
);
}

View file

@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.5.1.0")]
[assembly: AssemblyFileVersion("1.5.1.0")]
[assembly: AssemblyVersion("1.6.0.0")]
[assembly: AssemblyFileVersion("1.6.0.0")]

View file

@ -100,6 +100,16 @@ namespace TopMostFriend.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap firstrun {
get {
object obj = ResourceManager.GetObject("firstrun", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View file

@ -130,6 +130,9 @@
<data name="door_in" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\door_in.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="firstrun" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\firstrun.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="help" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\help.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -26,9 +26,7 @@ namespace TopMostFriend {
}
public static string[] Get(string name, string[] fallback = null) {
byte[] buffer = GetRoot().GetValue(name, null) as byte[];
if (buffer == null)
if(!(GetRoot().GetValue(name, null) is byte[] buffer))
return fallback;
List<string> strings = new List<string>();

View file

@ -1,6 +1,9 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using TopMostFriend.Languages;
namespace TopMostFriend {
public class SettingsWindow : Form {
@ -29,184 +32,172 @@ namespace TopMostFriend {
public readonly CheckBox FlShiftClickBlacklist;
public readonly CheckBox FlShowHotkeyIcon;
public readonly CheckBox FlShowWindowList;
public readonly CheckBox FlAlwaysRetryAsAdmin;
public readonly CheckBox FlRevertOnExit;
public readonly ComboBox LangSelect;
public SettingsWindow() {
Text = @"Top Most Friend Settings";
Text = Locale.String(@"SettingsTitle");
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.FixedSingle;
AutoScaleMode = AutoScaleMode.Dpi;
ClientSize = new Size(430, 298);
ClientSize = new Size(430, 407);
MinimizeBox = MaximizeBox = false;
MinimumSize = MaximumSize = Size;
TopMost = true;
KeyCode = Settings.Get(Program.FOREGROUND_HOTKEY_SETTING, 0);
Button applyButton = new Button {
Text = @"Apply",
Text = Locale.String(@"SettingsApply"),
Size = new Size(75, 23),
Location = new Point(ClientSize.Width - 81, ClientSize.Height - 30),
TabIndex = 10003,
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
};
applyButton.Click += ApplyButton_Click;
Button cancelButton = new Button {
Text = @"Cancel",
Text = Locale.String(@"SettingsCancel"),
Size = applyButton.Size,
Location = new Point(ClientSize.Width - 162, applyButton.Location.Y),
TabIndex = 10002,
Anchor = applyButton.Anchor,
};
cancelButton.Click += CancelButton_Click;
Button okButton = new Button {
Text = @"OK",
Text = Locale.String(@"SettingsOk"),
Size = applyButton.Size,
Location = new Point(ClientSize.Width - 243, applyButton.Location.Y),
TabIndex = 10001,
Anchor = applyButton.Anchor,
};
okButton.Click += OkButton_Click;
GroupBox hotKeyGroup = new GroupBox {
Text = @"Hotkeys",
Text = Locale.String(@"SettingsHotKeysTitle"),
Location = new Point(6, 6),
Size = new Size(Width - 18, 70),
Anchor = AnchorStyles.Left | AnchorStyles.Right,
};
GroupBox flagsGroup = new GroupBox {
Text = @"Flags",
Location = new Point(6, 76),
Size = new Size(Width - 18, 130),
Text = Locale.String(@"SettingsOptionsTitle"),
Location = new Point(6, 80),
Size = new Size(Width - 18, 170),
Anchor = hotKeyGroup.Anchor,
};
GroupBox blackListGroup = new GroupBox {
Text = @"Blacklist",
Location = new Point(6, 206),
GroupBox langGroup = new GroupBox {
Text = Locale.String(@"SettingsLanguageTitle"),
Location = new Point(6, 254),
Size = new Size(Width - 18, 55),
Anchor = hotKeyGroup.Anchor,
};
GroupBox otherGroup = new GroupBox {
Text = Locale.String(@"SettingsOtherTitle"),
Location = new Point(6, 313),
Size = new Size(Width - 18, 55),
Anchor = hotKeyGroup.Anchor,
};
Controls.AddRange(new Control[] {
applyButton, cancelButton, okButton,
hotKeyGroup, flagsGroup, blackListGroup,
hotKeyGroup, flagsGroup, langGroup, otherGroup,
});
Label toggleForegroundLabel = new Label {
hotKeyGroup.Controls.Add(new Label {
AutoSize = true,
Text = @"Toggle always on top status on active window",
Text = Locale.String(@"SettingsHotKeysToggle"),
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),
TabIndex = 105,
};
fgReset.Click += FgReset_Click;
FgKey = new TextBox {
Text = ((Keys)(KeyCode >> 16)).ToString(),
Location = new Point(12, mod_y + 2),
TabIndex = 101,
};
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,
TabIndex = 102,
};
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,
TabIndex = 103,
};
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,
TabIndex = 104,
};
FgModShift.Click += FgModShift_Click;
FgModWindows = new CheckBox {
Text = @"WIN",
Location = new Point(mod_x + 150, mod_y),
Checked = (KeyCode & (int)Win32ModKeys.MOD_WIN) > 0,
Appearance = FgModCtrl.Appearance,
Size = FgModCtrl.Size,
TextAlign = FgModCtrl.TextAlign,
TabIndex = 105,
};
FgModWindows.Click += FgModWindows_Click;
hotKeyGroup.Controls.AddRange(new Control[] {
toggleForegroundLabel, FgModCtrl, FgModAlt, FgModShift, FgModWindows, fgReset, FgKey,
});
FlAlwaysAdmin = new CheckBox {
Text = @"Always run as administrator",
CreateHotKeyInput(
hotKeyGroup,
() => KeyCode,
keyCode => KeyCode = keyCode
);
FlToggleNotification = new CheckBox {
Text = Locale.String(@"SettingsOptionsToggleNotify"),
Location = new Point(10, 20),
Checked = Settings.Get(Program.ALWAYS_ADMIN_SETTING, false),
Checked = Settings.Get(Program.TOGGLE_BALLOON_SETTING, Program.ToggleBalloonDefault),
AutoSize = true,
TabIndex = 201,
};
FlToggleNotification = new CheckBox {
Text = @"Show notification when using toggle hotkey",
FlShowHotkeyIcon = new CheckBox {
Text = Locale.String(@"SettingsOptionsToggleNotifyIcon"),
Location = new Point(10, 40),
Checked = Settings.Get(Program.TOGGLE_BALLOON_SETTING, Program.ToggleBalloonDefault),
Checked = Settings.Get(Program.SHOW_HOTKEY_ICON, true),
AutoSize = true,
TabIndex = 202,
};
FlShiftClickBlacklist = new CheckBox {
Text = @"SHIFT+CLICK items in the list to add to the title blacklist",
FlAlwaysRetryAsAdmin = new CheckBox {
Text = Locale.String(@"SettingsOptionsElevatedRetry"),
Location = new Point(10, 60),
Checked = Settings.Get(Program.SHIFT_CLICK_BLACKLIST, true),
Checked = Settings.Get(Program.ALWAYS_RETRY_ELEVATED, false),
AutoSize = true,
TabIndex = 203,
};
FlShowHotkeyIcon = new CheckBox {
Text = @"Show icon of window affected by hotkey",
FlShiftClickBlacklist = new CheckBox {
Text = Locale.String(@"SettingsOptionsShiftBlacklist"),
Location = new Point(10, 80),
Checked = Settings.Get(Program.SHOW_HOTKEY_ICON, true),
Checked = Settings.Get(Program.SHIFT_CLICK_BLACKLIST, true),
AutoSize = true,
TabIndex = 204,
};
FlShowWindowList = new CheckBox {
Text = @"Show list of open windows in the task bar context menu",
FlRevertOnExit = new CheckBox {
Text = Locale.String(@"SettingsOptionsRevertOnExit"),
Location = new Point(10, 100),
Checked = Settings.Get(Program.SHOW_WINDOW_LIST, true),
Checked = Settings.Get(Program.REVERT_ON_EXIT, false),
AutoSize = true,
TabIndex = 205,
};
FlShowWindowList = new CheckBox {
Text = Locale.String(@"SettingsOptionsShowTrayList"),
Location = new Point(10, 120),
Checked = Settings.Get(Program.SHOW_WINDOW_LIST, true),
AutoSize = true,
TabIndex = 206,
};
FlAlwaysAdmin = new CheckBox {
Text = Locale.String(@"SettingsOptionsAlwaysAdmin"),
Location = new Point(10, 140),
Checked = Settings.Get(Program.ALWAYS_ADMIN_SETTING, false),
AutoSize = true,
TabIndex = 207,
};
flagsGroup.Controls.AddRange(new[] {
CheckBox[] options = new[] {
FlAlwaysAdmin, FlToggleNotification, FlShiftClickBlacklist,
FlShowHotkeyIcon, FlShowWindowList,
});
FlShowHotkeyIcon, FlShowWindowList, FlAlwaysRetryAsAdmin,
FlRevertOnExit,
};
flagsGroup.Controls.AddRange(options);
foreach(CheckBox option in options)
if((option.Width + (option.Left * 2)) > flagsGroup.ClientSize.Width)
ClientSize = new Size(option.Width + 30, ClientSize.Height);
LangSelect = new ComboBox {
Anchor = AnchorStyles.Left | AnchorStyles.Right,
DropDownStyle = ComboBoxStyle.DropDownList,
Size = new Size(langGroup.Width - 24, 21),
Location = new Point(12, 22),
};
LangSelect.Items.AddRange(Locale.GetAvailableLanguages());
LangSelect.SelectedItem = Locale.GetCurrentLanguage();
langGroup.Controls.Add(LangSelect);
Button titleBlacklist = new Button {
Size = new Size(120, 23),
Location = new Point(10, 20),
Text = @"Manage...",
Text = Locale.String(@"SettingsOtherBlacklistButton"),
TabIndex = 301,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowOnly,
};
titleBlacklist.Click += (s, e) => {
string[] newList = BlacklistWindow.Display(@"Title Blacklist", Program.GetBlacklistedTitles());
string[] newList = BlacklistWindow.Display(Locale.String(@"SettingsOtherBlacklistWindowTitle"), Program.GetBlacklistedTitles());
if(newList != null) {
Program.ApplyBlacklistedTitles(newList);
@ -214,22 +205,207 @@ namespace TopMostFriend {
}
};
blackListGroup.Controls.AddRange(new[] { titleBlacklist });
Button startWithWindows = new Button {
Size = new Size(120, 23),
Location = new Point(134, 20),
Text = Locale.String(@"SettingsOtherStartupButton"),
TabIndex = 302,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowOnly,
};
startWithWindows.Click += (s, e) => {
DialogResult dr = MessageBox.Show(Locale.String(@"SettingsOtherStartupConfirm"), Program.TITLE, MessageBoxButtons.YesNo, MessageBoxIcon.Question);
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), @"TopMostFriend.lnk");
if(File.Exists(path))
File.Delete(path);
if(dr == DialogResult.Yes) {
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(path);
shortcut.TargetPath = Application.ExecutablePath;
shortcut.Save();
}
};
Button resetSettings = new Button {
Size = new Size(120, 23),
Location = new Point(258, 20),
Text = Locale.String(@"SettingsOtherResetButton"),
TabIndex = 303,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowOnly,
};
resetSettings.Click += (s, e) => {
DialogResult dr = MessageBox.Show(Locale.String(@"SettingsOtherResetConfirm"), Program.TITLE, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
if(dr == DialogResult.Yes) {
Settings.Remove(Program.FOREGROUND_HOTKEY_SETTING);
Settings.Remove(Program.PROCESS_SEPARATOR_SETTING);
Settings.Remove(Program.LIST_SELF_SETTING);
Settings.Remove(Program.LIST_BACKGROUND_PATH_SETTING);
Settings.Remove(Program.LIST_BACKGROUND_LAYOUT_SETTING);
Settings.Remove(Program.ALWAYS_ADMIN_SETTING);
Settings.Remove(Program.TOGGLE_BALLOON_SETTING);
Settings.Remove(Program.SHIFT_CLICK_BLACKLIST);
Settings.Remove(Program.TITLE_BLACKLIST);
Settings.Remove(Program.SHOW_HOTKEY_ICON);
Settings.Remove(Program.SHOW_WINDOW_LIST);
Settings.Remove(Program.LAST_VERSION);
Settings.Remove(Program.ALWAYS_RETRY_ELEVATED);
Settings.Remove(Program.REVERT_ON_EXIT);
Program.Shutdown();
Process.Start(Application.ExecutablePath);
Application.Exit();
}
};
Button[] otherButtons = new[] { titleBlacklist, startWithWindows, resetSettings };
otherGroup.Controls.AddRange(otherButtons);
}
private void FgReset_Click(object sender, EventArgs e) {
FgModCtrl.Checked = FgModAlt.Checked = FgModShift.Checked = false;
FgKey.Text = string.Empty;
KeyCode = 0;
public static void CreateHotKeyInput(
Control target,
Func<int> getKeyCode,
Action<int> setKeyCode,
int offsetX = 0,
int offsetY = 0
) {
int modX = 120 + offsetX;
int modY = 34 + offsetY;
int keyCode = getKeyCode();
Button fgReset = new Button {
Text = Locale.String(@"SettingsHotKeysReset"),
Location = new Point(target.Width - 85 + offsetX, modY),
TabIndex = 105,
};
TextBox fgKey = new TextBox {
Text = ((Keys)(keyCode >> 16)).ToString(),
Location = new Point(12 + offsetX, modY + 2),
TabIndex = 101,
};
CheckBox fgModCtrl = new CheckBox {
Text = @"CTRL",
Location = new Point(modX, modY),
Checked = (keyCode & (int)Win32ModKeys.MOD_CONTROL) > 0,
Appearance = Appearance.Button,
Size = new Size(50, 23),
TextAlign = ContentAlignment.MiddleCenter,
TabIndex = 102,
};
CheckBox fgModWindows = new CheckBox {
Text = @"WIN",
Location = new Point(modX + 50, modY),
Checked = (keyCode & (int)Win32ModKeys.MOD_WIN) > 0,
Appearance = fgModCtrl.Appearance,
Size = fgModCtrl.Size,
TextAlign = fgModCtrl.TextAlign,
TabIndex = 103,
};
CheckBox fgModAlt = new CheckBox {
Text = @"ALT",
Location = new Point(modX + 100, modY),
Checked = (keyCode & (int)Win32ModKeys.MOD_ALT) > 0,
Appearance = fgModCtrl.Appearance,
Size = fgModCtrl.Size,
TextAlign = fgModCtrl.TextAlign,
TabIndex = 104,
};
CheckBox fgModShift = new CheckBox {
Text = @"SHIFT",
Location = new Point(modX + 150, modY),
Checked = (keyCode & (int)Win32ModKeys.MOD_SHIFT) > 0,
Appearance = fgModCtrl.Appearance,
Size = fgModCtrl.Size,
TextAlign = fgModCtrl.TextAlign,
TabIndex = 105,
};
fgReset.Click += (s, e) => {
fgModCtrl.Checked = fgModAlt.Checked = fgModShift.Checked = false;
fgKey.Text = ((Keys)0).ToString();
setKeyCode(0);
};
fgModCtrl.Click += (s, e) => {
if(s is CheckBox cb) {
if(cb.Checked)
keyCode |= (int)Win32ModKeys.MOD_CONTROL;
else
keyCode &= ~(int)Win32ModKeys.MOD_CONTROL;
setKeyCode(keyCode);
}
};
fgModAlt.Click += (s, e) => {
if(s is CheckBox cb) {
if(cb.Checked)
keyCode |= (int)Win32ModKeys.MOD_ALT;
else
keyCode &= ~(int)Win32ModKeys.MOD_ALT;
setKeyCode(keyCode);
}
};
fgModShift.Click += (s, e) => {
if(s is CheckBox cb) {
if(cb.Checked)
keyCode |= (int)Win32ModKeys.MOD_SHIFT;
else
keyCode &= ~(int)Win32ModKeys.MOD_SHIFT;
setKeyCode(keyCode);
}
};
fgModWindows.Click += (s, e) => {
if(s is CheckBox cb) {
if(cb.Checked)
keyCode |= (int)Win32ModKeys.MOD_WIN;
else
keyCode &= ~(int)Win32ModKeys.MOD_WIN;
setKeyCode(keyCode);
}
};
fgKey.KeyDown += (s, e) => {
if(!(s is TextBox textBox))
return;
e.Handled = e.SuppressKeyPress = true;
textBox.Text = e.KeyCode.ToString();
keyCode &= 0xFFFF;
keyCode |= (int)e.KeyCode << 16;
setKeyCode(keyCode);
};
target.Controls.AddRange(new Control[] {
fgModCtrl, fgModAlt, fgModShift, fgModWindows, fgReset, fgKey,
});
}
public void Apply() {
if(LangSelect.SelectedItem is LanguageInfo li) {
if(li != Locale.GetCurrentLanguage()) {
if(Locale.GetPreferredLanguage() == li.Id)
Settings.Remove(Program.LANGUAGE);
else
Settings.Set(Program.LANGUAGE, li.Id);
Locale.SetLanguage(li);
MessageBox.Show(Locale.String(@"SettingsLanguageChangedInfo"), Program.TITLE, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
Settings.Set(Program.FOREGROUND_HOTKEY_SETTING, KeyCode);
Settings.Set(Program.ALWAYS_ADMIN_SETTING, FlAlwaysAdmin.Checked);
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);
Settings.Set(Program.ALWAYS_RETRY_ELEVATED, FlAlwaysRetryAsAdmin.Checked);
Settings.Set(Program.REVERT_ON_EXIT, FlRevertOnExit.Checked);
Program.SetForegroundHotKey(KeyCode);
}
@ -244,49 +420,6 @@ namespace TopMostFriend {
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 FgModWindows_Click(object sender, EventArgs e) {
if(sender is CheckBox cb) {
if(cb.Checked)
KeyCode |= (int)Win32ModKeys.MOD_WIN;
else
KeyCode &= ~(int)Win32ModKeys.MOD_WIN;
}
}
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();

View file

@ -44,12 +44,20 @@
<Compile Include="AboutWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="ActionTimeout.cs" />
<Compile Include="BlacklistWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="FirstRunWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="HotKeyWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Languages\Language.cs" />
<Compile Include="Languages\LanguageInfo.cs" />
<Compile Include="Languages\LanguageString.cs" />
<Compile Include="Locale.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
@ -61,10 +69,12 @@
<Compile Include="SettingsWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UAC.cs" />
<Compile Include="Win32.cs" />
<Compile Include="WindowInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
@ -80,6 +90,9 @@
<None Include="Resources\about.png" />
<None Include="Resources\cog.png" />
<None Include="Resources\arrow_refresh.png" />
<None Include="Resources\firstrun.png" />
<EmbeddedResource Include="Languages\en-GB.xml" />
<EmbeddedResource Include="Languages\nl-NL.xml" />
<Content Include="TopMostFriend.ico" />
</ItemGroup>
<ItemGroup>
@ -88,5 +101,16 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

81
TopMostFriend/UAC.cs Normal file
View file

@ -0,0 +1,81 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Principal;
using System.Windows.Forms;
namespace TopMostFriend {
public static class UAC {
private static bool? IsElevatedValue;
private static string ExecutablePathValue;
static UAC() {
ExecutablePath = null;
}
public static bool IsElevated {
get {
if(!IsElevatedValue.HasValue)
using(WindowsIdentity identity = WindowsIdentity.GetCurrent())
IsElevatedValue = identity != null && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
return IsElevatedValue.Value;
}
}
public static string ExecutablePath {
get => ExecutablePathValue;
set {
ExecutablePathValue = string.IsNullOrWhiteSpace(value) || !File.Exists(value)
? Assembly.GetEntryAssembly().Location
: value;
}
}
public static int RunElevatedTask(string args) {
if(string.IsNullOrWhiteSpace(args))
throw new ArgumentException(@"No arguments provided.", nameof(args));
try {
Process process = Process.Start(new ProcessStartInfo {
UseShellExecute = true,
FileName = ExecutablePath,
WorkingDirectory = Environment.CurrentDirectory,
Arguments = args,
Verb = @"runas",
});
process.WaitForExit();
return process.ExitCode;
} catch(Win32Exception ex) {
return ex.ErrorCode;
}
}
public static int ToggleWindowTopMost(WindowInfo window, bool switchWindow)
=> ToggleWindowTopMost(window.Handle, switchWindow);
public static int ToggleWindowTopMost(IntPtr handle, bool switchWindow)
=> RunElevatedTask(switchWindow ? $@"--toggle={handle}" : $@"--toggle={handle} --background={handle}");
public static void RestartElevated() {
if(IsElevated)
return;
Program.Shutdown();
Process.Start(new ProcessStartInfo {
UseShellExecute = true,
FileName = ExecutablePath,
WorkingDirectory = Environment.CurrentDirectory,
Arguments = string.Join(@" ", Environment.GetCommandLineArgs()),
Verb = @"runas",
});
Application.Exit();
}
}
}

View file

@ -52,12 +52,10 @@ namespace TopMostFriend {
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 extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex) {
if (IntPtr.Size == 8)
return GetWindowLongPtr64(hWnd, nIndex);
return new IntPtr(GetWindowLong32(hWnd, nIndex));
return IntPtr.Size == 8 ? GetWindowLongPtr64(hWnd, nIndex) : new IntPtr(GetWindowLong32(hWnd, nIndex));
}
[DllImport(@"user32", EntryPoint = "GetWindowLong")]
@ -84,9 +82,7 @@ namespace TopMostFriend {
}
public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) {
if (IntPtr.Size > 4)
return GetClassLongPtr64(hWnd, nIndex);
return new IntPtr(GetClassLongPtr32(hWnd, nIndex));
return IntPtr.Size > 4 ? GetClassLongPtr64(hWnd, nIndex) : new IntPtr(GetClassLongPtr32(hWnd, nIndex));
}
[DllImport(@"user32", EntryPoint = "GetClassLong")]

View file

@ -3,18 +3,18 @@ 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 int ProcessId { get; }
public Process Owner => Process.GetProcessById(ProcessId);
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;
get => (Win32.GetWindowLongPtr(Handle, Win32.GWL_EXSTYLE).ToInt32() & Win32.WS_EX_TOPMOST) > 0;
set {
Win32.SetWindowPos(
Handle, new IntPtr(value ? Win32.HWND_TOPMOST : Win32.HWND_NOTOPMOST),
@ -52,33 +52,34 @@ namespace TopMostFriend {
public Image IconBitmap
=> Icon?.ToBitmap();
public WindowInfo(int handle)
: this(new IntPtr(handle)) {}
public WindowInfo(IntPtr handle)
: this(handle, FindOwner(handle)) {}
: this(handle, FindOwnerId(handle)) { }
public WindowInfo(IntPtr handle, Process owner) {
public WindowInfo(IntPtr handle, int processId) {
Handle = handle;
Owner = owner ?? throw new ArgumentNullException(nameof(owner));
ProcessId = processId;
}
public void SwitchTo() {
Win32.SwitchToThisWindow(Handle, false);
}
public bool ToggleTopMost() {
public bool ToggleTopMost(bool switchWindow = true) {
bool expected = !IsTopMost;
IsTopMost = expected;
bool success = IsTopMost == expected;
if(expected && success)
if(switchWindow && expected && success)
SwitchTo();
return success;
}
public static Process FindOwner(IntPtr hWnd) {
Win32.GetWindowThreadProcessId(hWnd, out uint procId);
return Process.GetProcessById((int)procId);
public bool ToggleTopMostElevated(bool switchWindow = true) {
return UAC.ToggleWindowTopMost(this, switchWindow) == 0;
}
public static int FindOwnerId(IntPtr hWnd) {
Win32.GetWindowThreadProcessId(hWnd, out int procId);
return procId;
}
public static WindowInfo GetForegroundWindow() {

BIN
firstrun.psd Normal file

Binary file not shown.