This commit is contained in:
Stephan Maier
2024-08-27 08:10:27 +02:00
parent eb5c2fa502
commit 647f938eee
617 changed files with 73086 additions and 7137 deletions

View File

@@ -0,0 +1,106 @@
// <copyright file="DgvMouseRow.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System;
using System.Windows.Forms;
public class DgvMouseRow : IDisposable
{
private readonly Timer timerRaiseRowMouseLeave = new();
private DataGridView dgv;
private DataGridViewCellEventArgs eventArgs;
internal DgvMouseRow()
{
timerRaiseRowMouseLeave.Interval = 200;
timerRaiseRowMouseLeave.Tick += TimerRaiseRowMouseLeave_Tick;
}
~DgvMouseRow() // the finalizer
{
Dispose(false);
}
internal event Action<object, DataGridViewCellEventArgs> RowMouseEnter;
internal event Action<object, DataGridViewCellEventArgs> RowMouseLeave;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
internal void CellMouseEnter(object sender, DataGridViewCellEventArgs newEventArgs)
{
DataGridView newDgv = (DataGridView)sender;
if (dgv != newDgv || newEventArgs.RowIndex != eventArgs.RowIndex)
{
if (timerRaiseRowMouseLeave.Enabled)
{
timerRaiseRowMouseLeave.Stop();
TriggerRowMouseLeave();
}
TriggerRowMouseEnter(newDgv, newEventArgs);
}
else
{
timerRaiseRowMouseLeave.Stop();
}
dgv = newDgv;
eventArgs = newEventArgs;
}
internal void CellMouseLeave(object sender, DataGridViewCellEventArgs e)
{
timerRaiseRowMouseLeave.Start();
}
internal void MouseLeave(object sender, EventArgs e)
{
if (timerRaiseRowMouseLeave.Enabled)
{
timerRaiseRowMouseLeave.Stop();
TriggerRowMouseLeave();
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
timerRaiseRowMouseLeave.Tick -= TimerRaiseRowMouseLeave_Tick;
timerRaiseRowMouseLeave.Dispose();
dgv = null;
}
}
private void TimerRaiseRowMouseLeave_Tick(object sender, EventArgs e)
{
timerRaiseRowMouseLeave.Stop();
TriggerRowMouseLeave();
}
private void TriggerRowMouseLeave()
{
if (dgv != null)
{
RowMouseLeave?.Invoke(dgv, eventArgs);
}
dgv = null;
eventArgs = null;
}
private void TriggerRowMouseEnter(DataGridView dgv, DataGridViewCellEventArgs e)
{
RowMouseEnter?.Invoke(dgv, e);
}
}
}

View File

@@ -0,0 +1,191 @@
// <copyright file="DragDropHelper.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using FSI.BT.Tools.Global.Utilities;
using FSI.BT.Tools.SystemTrayMenu.DataClasses;
using FSI.BT.Tools.SystemTrayMenu.UserInterface;
using FSI.BT.Tools.SystemTrayMenu.Utilities;
public static class DragDropHelper
{
public static void DragEnter(object sender, DragEventArgs e)
{
object data = e.Data.GetData("UniformResourceLocator");
if (data is MemoryStream memoryStream)
{
byte[] bytes = memoryStream.ToArray();
Encoding encod = Encoding.ASCII;
string url = encod.GetString(bytes);
if (!string.IsNullOrEmpty(url))
{
e.Effect = DragDropEffects.Copy;
}
}
}
public static void DragDrop(object sender, DragEventArgs e)
{
Menu menu = (Menu)sender;
string path;
if (menu != null)
{
RowData rowData = (RowData)menu.Tag;
if (rowData != null)
{
path = rowData.ResolvedPath;
}
else
{
path = Config.Path;
}
}
else
{
path = Config.Path;
}
object data = e.Data.GetData("UniformResourceLocator");
MemoryStream ms = data as MemoryStream;
byte[] bytes = ms.ToArray();
Encoding encod = Encoding.ASCII;
string url = encod.GetString(bytes);
new Thread(CreateShortcutInBackground).Start();
void CreateShortcutInBackground()
{
CreateShortcut(url.Replace("\0", string.Empty), path);
}
}
private static void CreateShortcut(string url, string pathToStoreFile)
{
string title = GetUrlShortcutTitle(url);
string fileNamePathShortcut = pathToStoreFile + "\\" + title.Trim() + ".url";
WriteShortcut(url, null, fileNamePathShortcut);
string pathIcon = DownloadUrlIcon(url);
if (!string.IsNullOrEmpty(pathIcon))
{
WriteShortcut(url, pathIcon, fileNamePathShortcut);
}
}
private static string GetUrlShortcutTitle(string url)
{
string title = url
.Replace("/", " ")
.Replace("https", string.Empty)
.Replace("http", string.Empty);
string invalid =
new string(Path.GetInvalidFileNameChars()) +
new string(Path.GetInvalidPathChars());
foreach (char character in invalid)
{
title = title.Replace(character.ToString(), string.Empty);
}
title = Truncate(title, 128); // max 255
return title;
}
private static string Truncate(string value, int maxLength)
{
if (!string.IsNullOrEmpty(value) &&
value.Length > maxLength)
{
value = value[..maxLength];
}
return value;
}
private static void WriteShortcut(string url, string pathIcon, string fileNamePathShortcut)
{
try
{
if (File.Exists(fileNamePathShortcut))
{
File.Delete(fileNamePathShortcut);
}
StreamWriter writer = new(fileNamePathShortcut);
writer.WriteLine("[InternetShortcut]");
writer.WriteLine($"URL={url.TrimEnd('\0')}");
writer.WriteLine("IconIndex=0");
writer.WriteLine($"HotKey=0");
writer.WriteLine($"IDList=");
if (!string.IsNullOrEmpty(pathIcon))
{
writer.WriteLine($"IconFile={pathIcon}");
}
writer.Flush();
writer.Close();
}
catch (Exception ex)
{
Log.Warn($"{nameof(WriteShortcut)} failed", ex);
}
}
private static string DownloadUrlIcon(string url)
{
string pathIcon = string.Empty;
string pathToStoreIcons = Global.Vars.SystemTrayMenuSettings.PathIcoDirectory;
Uri uri = new(url);
string hostname = uri.Host.ToString();
string pathIconPng = Path.Combine(pathToStoreIcons, $"{hostname}.png");
string urlGoogleIconDownload = @"http://www.google.com/s2/favicons?sz=32&domain=" + url;
HttpClient client = new();
try
{
if (!Directory.Exists(pathToStoreIcons))
{
Directory.CreateDirectory(pathToStoreIcons);
}
using HttpResponseMessage response = client.GetAsync(urlGoogleIconDownload).Result;
using HttpContent content = response.Content;
Stream stream = content.ReadAsStreamAsync().Result;
using var fileStream = File.Create(pathIconPng);
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(fileStream);
fileStream.Close();
pathIcon = Path.Combine(pathToStoreIcons, $"{hostname}.ico");
if (!ImagingHelper.ConvertToIcon(pathIconPng, pathIcon, 32))
{
Log.Info("Failed to convert icon.");
}
}
catch (Exception ex)
{
Log.Warn($"{nameof(DownloadUrlIcon)} failed", ex);
}
try
{
if (File.Exists(pathIconPng))
{
File.Delete(pathIconPng);
}
}
catch (Exception ex)
{
Log.Warn($"{nameof(DownloadUrlIcon)} failed to delete {pathIconPng}", ex);
}
return pathIcon;
}
}
}

View File

@@ -0,0 +1,183 @@
// <copyright file="Fading.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System;
using System.Windows.Forms;
using FSI.BT.Tools.SystemTrayMenu.Utilities;
public class Fading : IDisposable
{
private const int Interval100FPS = 10; // 100fps=>1s/100fps=~10ms
private const double StepIn = 0.20;
private const double StepOut = 0.10;
private const double Transparent = 0.80;
private const double TransparentMinus = 0.60; // Transparent - StepIn
private const double TransparentPlus = 0.85; // Transparent + StepOut
private const double Shown = 1.00;
private const double ShownMinus = 0.80; // Shown - StepIn
private readonly Timer timer = new();
private FadingState state = FadingState.Idle;
private double opacity;
private bool visible;
internal Fading()
{
timer.Interval = Interval100FPS;
timer.Tick += Timer_Tick;
}
~Fading() // the finalizer
{
Dispose(false);
}
internal event Action Hide;
internal event Action Show;
internal event Action<double> ChangeOpacity;
internal enum FadingState
{
Idle,
Show,
ShowTransparent,
Hide,
}
internal bool IsHiding => state == FadingState.Hide;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
internal void Fade(FadingState state)
{
StartStopTimer(state);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
timer.Tick -= Timer_Tick;
timer.Dispose();
}
}
private void StartStopTimer(FadingState newState)
{
if (newState == FadingState.Idle)
{
state = newState;
timer.Stop();
}
else
{
state = newState;
timer.Start();
}
}
private void Timer_Tick(object sender, EventArgs e)
{
FadeStep();
}
private void FadeStep()
{
switch (state)
{
case FadingState.Show:
if (!visible)
{
visible = true;
Show?.Invoke();
opacity = 0;
ChangeOpacity?.Invoke(opacity);
}
else if (Global.Vars.SystemTrayMenuSettings.UseFading &&
opacity < ShownMinus)
{
opacity += StepIn;
ChangeOpacity?.Invoke(opacity);
}
else
{
if (!Global.Vars.SystemTrayMenuSettings.UseFading)
{
// #393 provoke a redraw for the CS_DROPSHADOW to work
opacity = ShownMinus;
ChangeOpacity?.Invoke(opacity);
}
opacity = Shown;
ChangeOpacity?.Invoke(opacity);
StartStopTimer(FadingState.Idle);
}
break;
case FadingState.ShowTransparent:
if (!visible)
{
visible = true;
Show?.Invoke();
opacity = 0;
ChangeOpacity?.Invoke(opacity);
}
else if (Global.Vars.SystemTrayMenuSettings.UseFading &&
opacity < TransparentMinus)
{
opacity += StepIn;
ChangeOpacity?.Invoke(opacity);
}
else if (Global.Vars.SystemTrayMenuSettings.UseFading &&
opacity > TransparentPlus)
{
opacity -= StepOut;
ChangeOpacity?.Invoke(opacity);
}
else
{
opacity = Transparent;
ChangeOpacity?.Invoke(opacity);
StartStopTimer(FadingState.Idle);
}
break;
case FadingState.Hide:
if (Global.Vars.SystemTrayMenuSettings.UseFading &&
opacity > StepOut)
{
opacity -= StepOut;
ChangeOpacity?.Invoke(opacity);
}
else if (visible)
{
opacity = 0;
ChangeOpacity?.Invoke(opacity);
visible = false;
Hide?.Invoke();
StartStopTimer(FadingState.Idle);
}
else
{
StartStopTimer(FadingState.Idle);
}
break;
case FadingState.Idle:
default:
StartStopTimer(FadingState.Idle);
break;
}
}
}
}

View File

@@ -0,0 +1,147 @@
// <copyright file="ImagingHelper.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
/// <summary>
/// Provides helper methods for imaging.
/// </summary>
public static class ImagingHelper
{
/// <summary>
/// Converts a PNG image to a icon (ico).
/// </summary>
/// <param name="input">The input stream.</param>
/// <param name="output">The output stream.</param>
/// <param name="size">The size (16x16 px by default).</param>
/// <param name="preserveAspectRatio">Preserve the aspect ratio.</param>
/// <returns>Wether or not the icon was succesfully generated.</returns>
public static bool ConvertToIcon(Stream input, Stream output, int size = 16, bool preserveAspectRatio = false)
{
Bitmap inputBitmap = (Bitmap)Image.FromStream(input);
if (inputBitmap != null)
{
int width, height;
if (preserveAspectRatio)
{
width = size;
height = inputBitmap.Height / inputBitmap.Width * size;
}
else
{
width = height = size;
}
Bitmap newBitmap = new(inputBitmap, new Size(width, height));
if (newBitmap != null)
{
// save the resized png into a memory stream for future use
using MemoryStream memoryStream = new();
newBitmap.Save(memoryStream, ImageFormat.Png);
BinaryWriter iconWriter = new(output);
if (output != null && iconWriter != null)
{
// 0-1 reserved, 0
iconWriter.Write((byte)0);
iconWriter.Write((byte)0);
// 2-3 image type, 1 = icon, 2 = cursor
iconWriter.Write((short)1);
// 4-5 number of images
iconWriter.Write((short)1);
// image entry 1
// 0 image width
iconWriter.Write((byte)width);
// 1 image height
iconWriter.Write((byte)height);
// 2 number of colors
iconWriter.Write((byte)0);
// 3 reserved
iconWriter.Write((byte)0);
// 4-5 color planes
iconWriter.Write((short)0);
// 6-7 bits per pixel
iconWriter.Write((short)32);
// 8-11 size of image data
iconWriter.Write((int)memoryStream.Length);
// 12-15 offset of image data
iconWriter.Write(6 + 16);
// write image data
// png data must contain the whole png data file
iconWriter.Write(memoryStream.ToArray());
iconWriter.Flush();
return true;
}
}
return false;
}
return false;
}
/// <summary>
/// Converts a PNG image to a icon (ico).
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="size">The size (16x16 px by default).</param>
/// <param name="preserveAspectRatio">Preserve the aspect ratio.</param>
/// <returns>Wether or not the icon was succesfully generated.</returns>
public static bool ConvertToIcon(string inputPath, string outputPath, int size = 16, bool preserveAspectRatio = false)
{
using FileStream inputStream = new(inputPath, FileMode.Open);
using FileStream outputStream = new(outputPath, FileMode.OpenOrCreate);
return ConvertToIcon(inputStream, outputStream, size, preserveAspectRatio);
}
public static Image RotateImage(Image img, float rotationAngle)
{
// create an empty Bitmap image
Bitmap bmp = new(img.Width, img.Height);
// turn the Bitmap into a Graphics object
Graphics gfx = Graphics.FromImage(bmp);
// now we set the rotation point to the center of our image
gfx.TranslateTransform(0.5f + ((float)bmp.Width / 2), 0.5f + ((float)bmp.Height / 2));
// now rotate the image
gfx.RotateTransform(rotationAngle);
gfx.TranslateTransform(0.5f - ((float)bmp.Width / 2), 0.5f - ((float)bmp.Height / 2));
// set the InterpolationMode to HighQualityBicubic so to ensure a high
// quality image once it is transformed to the specified size
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
// now draw our new image onto the graphics object
gfx.DrawImage(img, new Point(0, 0));
// dispose of our Graphics object
gfx.Dispose();
// return the image
return bmp;
}
}
}

View File

@@ -0,0 +1,215 @@
// <copyright file="JoystickHelper.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helpers
{
using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Reflection.Metadata;
using System.Threading;
using System.Windows.Forms;
using SharpDX.DirectInput;
public class JoystickHelper : IDisposable
{
private readonly System.Timers.Timer timerReadJoystick = new();
private readonly object lockRead = new();
private Joystick joystick;
private Keys pressingKey;
private int pressingKeyCounter;
private bool joystickHelperEnabled;
public JoystickHelper()
{
timerReadJoystick.Interval = 80;
timerReadJoystick.Elapsed += ReadJoystickLoop;
timerReadJoystick.Enabled = false;
if (Global.Vars.SystemTrayMenuSettings.SupportGamepad)
{
timerReadJoystick.Start();
}
}
~JoystickHelper() // the finalizer
{
Dispose(false);
}
public event Action<Keys> KeyPressed;
public void Enable()
{
joystickHelperEnabled = true;
}
public void Disable()
{
joystickHelperEnabled = false;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
timerReadJoystick.Elapsed -= ReadJoystickLoop;
timerReadJoystick?.Dispose();
joystick?.Dispose();
}
}
private static Keys ReadKeyFromState(JoystickUpdate state)
{
Keys keys = Keys.None;
switch (state.Offset)
{
case JoystickOffset.PointOfViewControllers0:
switch (state.Value)
{
case 0:
keys = Keys.Up;
break;
case 9000:
keys = Keys.Right;
break;
case 18000:
keys = Keys.Down;
break;
case 27000:
keys = Keys.Left;
break;
default:
break;
}
break;
case JoystickOffset.Buttons0:
if (state.Value == 128)
{
keys = Keys.Enter;
}
break;
default:
break;
}
return keys;
}
private void ReadJoystickLoop(object sender, System.Timers.ElapsedEventArgs e)
{
if (joystickHelperEnabled)
{
lock (lockRead)
{
timerReadJoystick.Stop();
if (joystick == null)
{
Thread.Sleep(3000);
InitializeJoystick();
}
else
{
ReadJoystick();
}
timerReadJoystick.Start();
}
}
}
private void ReadJoystick()
{
try
{
joystick.Poll();
JoystickUpdate[] datas = joystick.GetBufferedData();
foreach (JoystickUpdate state in datas)
{
if (state.Value < 0)
{
pressingKey = Keys.None;
pressingKeyCounter = 0;
continue;
}
Keys key = ReadKeyFromState(state);
if (key != Keys.None)
{
KeyPressed?.Invoke(key);
if (state.Offset == JoystickOffset.PointOfViewControllers0)
{
pressingKeyCounter = 0;
pressingKey = key;
}
}
}
if (pressingKey != Keys.None)
{
pressingKeyCounter += 1;
if (pressingKeyCounter > 1)
{
KeyPressed?.Invoke(pressingKey);
}
}
}
catch
{
joystick?.Dispose();
joystick = null;
}
}
private void InitializeJoystick()
{
// Initialize DirectInput
DirectInput directInput = new();
// Find a Joystick Guid
Guid joystickGuid = Guid.Empty;
foreach (DeviceInstance deviceInstance in directInput.GetDevices(
DeviceType.Gamepad,
DeviceEnumerationFlags.AllDevices))
{
joystickGuid = deviceInstance.InstanceGuid;
}
// If Gamepad not found, look for a Joystick
if (joystickGuid == Guid.Empty)
{
foreach (DeviceInstance deviceInstance in directInput.GetDevices(
DeviceType.Joystick,
DeviceEnumerationFlags.AllDevices))
{
joystickGuid = deviceInstance.InstanceGuid;
}
}
// If Joystick found
if (joystickGuid != Guid.Empty)
{
// Instantiate the joystick
joystick = new Joystick(directInput, joystickGuid);
// Set BufferSize in order to use buffered data.
joystick.Properties.BufferSize = 128;
var handle = Process.GetCurrentProcess().MainWindowHandle;
joystick.SetCooperativeLevel(handle, CooperativeLevel.NonExclusive | CooperativeLevel.Background);
// Acquire the joystick
joystick.Acquire();
}
}
}
}

View File

@@ -0,0 +1,27 @@
// <copyright file="KeyPressedEventArgs.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System;
using System.Windows.Forms;
/// <summary>
/// Event Args for the event that is fired after the hot key has been pressed.
/// </summary>
internal class KeyPressedEventArgs : EventArgs
{
private readonly Keys key;
internal KeyPressedEventArgs(KeyboardHookModifierKeys modifier, Keys key)
{
Modifier = modifier;
this.key = key;
}
internal KeyboardHookModifierKeys Modifier { get; }
internal Keys Key => key;
}
}

View File

@@ -0,0 +1,167 @@
// <copyright file="KeyboardHook.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System;
using System.Windows.Forms;
using FSI.BT.Tools.Global.DllImports;
using FSI.BT.Tools.Global.UserInterface.HotkeyTextboxControl;
using FSI.BT.Tools.SystemTrayMenu.Utilities;
/// <summary>
/// The enumeration of possible modifiers.
/// </summary>
[Flags]
public enum KeyboardHookModifierKeys : uint
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Win = 8,
}
public sealed class KeyboardHook : IDisposable
{
private readonly Window window = new();
private int currentId;
public KeyboardHook()
{
// register the event of the inner native window.
window.KeyPressed += Window_KeyPressed;
}
/// <summary>
/// A hot key has been pressed.
/// </summary>
internal event EventHandler<KeyPressedEventArgs> KeyPressed;
public void Dispose()
{
// unregister all the registered hot keys.
for (int i = currentId; i > 0; i--)
{
NativeMethods.User32UnregisterHotKey(window.Handle, i);
}
// dispose the inner native window.
window.KeyPressed -= Window_KeyPressed;
window.Dispose();
}
/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="key">The key itself that is associated with the hot key.</param>
internal void RegisterHotKey(Keys key)
{
uint keyModifiersNone = 0;
RegisterHotKey(keyModifiersNone, key);
}
internal void RegisterHotKey()
{
KeyboardHookModifierKeys modifiers = KeyboardHookModifierKeys.None;
string modifiersString = Global.Vars.SystemTrayMenuSettings.HotKey;
if (!string.IsNullOrEmpty(modifiersString))
{
if (modifiersString.ToUpperInvariant().Contains("ALT", StringComparison.InvariantCulture))
{
modifiers |= KeyboardHookModifierKeys.Alt;
}
if (modifiersString.ToUpperInvariant().Contains("CTRL", StringComparison.InvariantCulture) ||
modifiersString.ToUpperInvariant().Contains("STRG", StringComparison.InvariantCulture))
{
modifiers |= KeyboardHookModifierKeys.Control;
}
if (modifiersString.ToUpperInvariant().Contains("SHIFT", StringComparison.InvariantCulture))
{
modifiers |= KeyboardHookModifierKeys.Shift;
}
if (modifiersString.ToUpperInvariant().Contains("WIN", StringComparison.InvariantCulture))
{
modifiers |= KeyboardHookModifierKeys.Win;
}
}
RegisterHotKey(
modifiers,
HotkeyControl.HotkeyFromString(
Global.Vars.SystemTrayMenuSettings.HotKey));
}
/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="modifier">The modifiers that are associated with the hot key.</param>
/// <param name="key">The key itself that is associated with the hot key.</param>
internal void RegisterHotKey(KeyboardHookModifierKeys modifier, Keys key)
{
RegisterHotKey((uint)modifier, key);
}
private void Window_KeyPressed(object sender, KeyPressedEventArgs e)
{
KeyPressed?.Invoke(this, e);
}
private void RegisterHotKey(uint modifier, Keys key)
{
currentId += 1;
if (!NativeMethods.User32RegisterHotKey(
window.Handle, currentId, modifier, (uint)key))
{
throw new InvalidOperationException(
Global.Utilities.Translator.GetText("Could not register the hot key."));
}
}
/// <summary>
/// Represents the window that is used internally to get the messages.
/// </summary>
private class Window : NativeWindow, IDisposable
{
private const int WmHotkey = 0x0312;
public Window()
{
// create the handle for the window.
CreateHandle(new CreateParams());
}
public event EventHandler<KeyPressedEventArgs> KeyPressed;
public void Dispose()
{
DestroyHandle();
}
/// <summary>
/// Overridden to get the notifications.
/// </summary>
/// <param name="m">m.</param>
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
// check if we got a hot key pressed.
if (m.Msg == WmHotkey)
{
// get the keys.
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
KeyboardHookModifierKeys modifier = (KeyboardHookModifierKeys)((int)m.LParam & 0xFFFF);
// invoke the event to notify the parent.
KeyPressed?.Invoke(this, new KeyPressedEventArgs(modifier, key));
}
}
}
}
}

View File

@@ -0,0 +1,252 @@
//// <copyright file="GitHubUpdate.cs" company="PlaceholderCompany">
//// Copyright (c) PlaceholderCompany. All rights reserved.
//// </copyright>
//namespace FSI.BT.Tools.SystemTrayMenu.Helper.Updater
//{
// using System;
// using System.Collections.Generic;
// using System.Drawing;
// using System.Net.Http;
// using System.Reflection;
// using System.Windows.Forms;
// using FSI.BT.Tools.Global.Utilities;
// using FSI.BT.Tools.SystemTrayMenu.Utilities;
// public class GitHubUpdate
// {
// private static List<Dictionary<string, object>> releases;
// private static Form newVersionForm;
// public static void ActivateNewVersionFormOrCheckForUpdates(bool showWhenUpToDate)
// {
// if (newVersionForm != null)
// {
// newVersionForm.HandleInvoke(newVersionForm.Activate);
// }
// else
// {
// CheckForUpdates(showWhenUpToDate);
// }
// }
// private static void CheckForUpdates(bool showWhenUpToDate)
// {
// string urlGithubReleases = @"http://api.github.com/repos/Hofknecht/SystemTrayMenu/releases";
// HttpClient client = new();
// // https://developer.github.com/v3/#user-agent-required
// client.DefaultRequestHeaders.Add("User-Agent", "FSI.BT.Tools.SystemTrayMenu/" + Application.ProductVersion.ToString());
// // https://developer.github.com/v3/media/#request-specific-version
// client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3.text+json");
// try
// {
// using HttpResponseMessage response = client.GetAsync(urlGithubReleases).Result;
// using HttpContent content = response.Content;
// string responseString = content.ReadAsStringAsync().Result;
// releases = responseString.FromJson<List<Dictionary<string, object>>>();
// }
// catch (Exception ex)
// {
// Log.Warn($"{nameof(CheckForUpdates)} failed", ex);
// }
// if (releases == null)
// {
// Log.Info($"{nameof(CheckForUpdates)} failed.");
// }
// else
// {
// RemoveCurrentAndOlderVersions();
// ShowNewVersionOrUpToDateDialog(showWhenUpToDate);
// }
// newVersionForm?.Dispose();
// newVersionForm = null;
// }
// private static void RemoveCurrentAndOlderVersions()
// {
// int releasesCount = releases.Count;
// Version versionCurrent = Assembly.GetExecutingAssembly().GetName().Version;
// for (int i = 0; i < releasesCount; i++)
// {
// string tagName = releases[i]["tag_name"].ToString();
// Version versionGitHub = new(tagName.Replace("v", string.Empty));
// if (versionGitHub.CompareTo(versionCurrent) < 1)
// {
// releases.RemoveRange(i, releasesCount - i);
// break;
// }
// }
// }
// private static void ShowNewVersionOrUpToDateDialog(bool showWhenUpToDate)
// {
// if (releases.Count > 0)
// {
// if (NewVersionDialog() == DialogResult.Yes)
// {
// Log.ProcessStart("https://github.com/Hofknecht/SystemTrayMenu/releases");
// }
// }
// else if (showWhenUpToDate)
// {
// MessageBox.Show(Global.Utilities.Translator.GetText("You have the latest version of SystemTrayMenu!"));
// }
// }
// /// <summary>
// /// Creates a window to show changelog of new available versions.
// /// </summary>
// /// <param name="LatestVersionTitle">Name of latest release.</param>
// /// <param name="Changelog">Pathnotes.</param>
// /// <returns>OK = OK, Yes = Website, else = Cancel.</returns>
// private static DialogResult NewVersionDialog()
// {
// const int ClientPad = 15;
// newVersionForm = new()
// {
// StartPosition = FormStartPosition.CenterScreen,
// FormBorderStyle = FormBorderStyle.FixedDialog,
// Icon = Config.GetAppIcon(),
// ShowInTaskbar = false,
// };
// newVersionForm.FormBorderStyle = FormBorderStyle.Sizable;
// newVersionForm.MaximizeBox = true;
// newVersionForm.MinimizeBox = false;
// newVersionForm.ClientSize = new Size(600, 400);
// newVersionForm.MinimumSize = newVersionForm.ClientSize;
// newVersionForm.Text = Global.Utilities.Translator.GetText("New version available!");
// Label label = new()
// {
// Size = new Size(newVersionForm.ClientSize.Width - ClientPad, 20),
// Location = new Point(ClientPad, ClientPad),
// Text = $"{Global.Utilities.Translator.GetText("Latest available version:")} {GetLatestVersionName()}",
// };
// newVersionForm.Controls.Add(label);
// Button buttonOK = new()
// {
// DialogResult = DialogResult.OK,
// Name = "buttonOK",
// };
// buttonOK.Location = new Point(
// newVersionForm.ClientSize.Width - buttonOK.Size.Width - ClientPad,
// newVersionForm.ClientSize.Height - buttonOK.Size.Height - ClientPad);
// buttonOK.MinimumSize = new Size(75, 23);
// buttonOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
// buttonOK.Text = Global.Utilities.Translator.GetText("OK");
// buttonOK.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// buttonOK.AutoSize = true;
// newVersionForm.Controls.Add(buttonOK);
// Button buttonGoToDownloadPage = new()
// {
// DialogResult = DialogResult.Yes,
// Name = "buttonGoToDownloadPage",
// };
// buttonGoToDownloadPage.Location = new Point(
// newVersionForm.ClientSize.Width - buttonGoToDownloadPage.Size.Width - ClientPad - buttonOK.Size.Width - ClientPad,
// newVersionForm.ClientSize.Height - buttonGoToDownloadPage.Size.Height - ClientPad);
// buttonGoToDownloadPage.MinimumSize = new Size(75, 23);
// buttonGoToDownloadPage.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
// buttonGoToDownloadPage.Text = Global.Utilities.Translator.GetText("Go to download page");
// buttonGoToDownloadPage.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// buttonGoToDownloadPage.AutoSize = true;
// newVersionForm.Controls.Add(buttonGoToDownloadPage);
// TextBox textBox = new()
// {
// Location = new Point(ClientPad, label.Location.Y + label.Size.Height + 5),
// };
// textBox.Size = new Size(
// newVersionForm.ClientSize.Width - (ClientPad * 2),
// buttonOK.Location.Y - ClientPad - textBox.Location.Y);
// textBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
// textBox.Multiline = true;
// textBox.Text = GetChangelog();
// textBox.ReadOnly = true;
// textBox.ScrollBars = ScrollBars.Both;
// textBox.BackColor = Color.FromKnownColor(KnownColor.Window);
// textBox.ForeColor = Color.FromKnownColor(KnownColor.ControlText);
// newVersionForm.Controls.Add(textBox);
// newVersionForm.AcceptButton = buttonOK;
// return newVersionForm.ShowDialog();
// }
// /// <summary>
// /// Returns the latest release version name.
// /// </summary>
// /// <returns>Version name.</returns>
// private static string GetLatestVersionName()
// {
// string result = "Unknown";
// if (releases == null)
// {
// return result;
// }
// try
// {
// result = releases[0]["tag_name"].ToString().Replace("v", string.Empty);
// }
// catch (Exception ex)
// {
// Log.Warn($"{nameof(GetLatestVersionName)} failed", ex);
// }
// return result;
// }
// /// <summary>
// /// Returns the change log from current version up to the latest release version.
// /// </summary>
// /// <returns>Change log summary or error text.</returns>
// private static string GetChangelog()
// {
// string result = string.Empty;
// string errorstr = "An error occurred during update check!" + Environment.NewLine;
// if (releases == null)
// {
// return errorstr + "Could not receive changelog!";
// }
// try
// {
// for (int i = 0; i < releases.Count; i++)
// {
// Dictionary<string, object> release = releases[i];
// result += release["name"].ToString()
// + Environment.NewLine
// + release["body_text"].ToString()
// .Replace("\n\n", Environment.NewLine)
// .Replace("\n \n", Environment.NewLine)
// + Environment.NewLine + Environment.NewLine;
// if (i < releases.Count)
// {
// result += "--------------------------------------------------" +
// "-------------------------------------------------------"
// + Environment.NewLine;
// }
// }
// result = result.Replace("\n", Environment.NewLine);
// }
// catch (Exception ex)
// {
// Log.Warn($"{nameof(GetChangelog)}", ex);
// result = errorstr + ex.Message.ToString();
// }
// return result;
// }
// }
//}

View File

@@ -0,0 +1,480 @@
// <copyright file="JsonParser.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper.Updater
{
// Copyright (c) 2018 Alex Parker
// Copyright (c) 2018-2019 Peter Kirmeier
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
// Really simple JSON parser in ~300 lines
// - Attempts to parse JSON files with minimal GC allocation
// - Nice and simple "[1,2,3]".FromJson<List<int>>() API
// - Classes and structs can be parsed too!
// class Foo { public int Value; }
// "{\"Value\":10}".FromJson<Foo>()
// - Can parse JSON without type information into Dictionary<string,object> and List<object> e.g.
// "[1,2,3]".FromJson<object>().GetType() == typeof(List<object>)
// "{\"Value\":10}".FromJson<object>().GetType() == typeof(Dictionary<string,object>)
// - No JIT Emit support to support AOT compilation on iOS
// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead.
// - Only public fields and property setters on classes/structs will be written to
//
// Limitations:
// - No JIT Emit support to parse structures quickly
// - Limited to parsing <2GB JSON files (due to int.MaxValue)
// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception.
public static class JSONParser
{
[ThreadStatic]
private static Stack<List<string>> splitArrayPool;
[ThreadStatic]
private static StringBuilder stringBuilder;
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache;
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache;
public static T FromJson<T>(this string json)
{
// Initialize, if needed, the ThreadStatic variables
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
stringBuilder ??= new StringBuilder();
splitArrayPool ??= new Stack<List<string>>();
// Remove all whitespace not within strings to make parsing simpler
stringBuilder.Length = 0;
for (int i = 0; i < json.Length; i++)
{
char c = json[i];
if (c == '"')
{
i = AppendUntilStringEnd(true, i, json);
continue;
}
if (char.IsWhiteSpace(c))
{
continue;
}
stringBuilder.Append(c);
}
// Parse the thing!
return (T)ParseValue(typeof(T), stringBuilder.ToString());
}
internal static object ParseValue(Type type, string json)
{
if (type == typeof(string))
{
if (json.Length <= 2)
{
return string.Empty;
}
StringBuilder parseStringBuilder = new(json.Length);
for (int i = 1; i < json.Length - 1; ++i)
{
if (json[i] == '\\' && i + 1 < json.Length - 1)
{
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
if (j >= 0)
{
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
++i;
continue;
}
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
{
if (uint.TryParse(json.AsSpan(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c))
{
parseStringBuilder.Append((char)c);
i += 5;
continue;
}
}
}
parseStringBuilder.Append(json[i]);
}
return parseStringBuilder.ToString();
}
if (type.IsPrimitive)
{
var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
return result;
}
if (type == typeof(decimal))
{
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result);
return result;
}
if (json == "null")
{
return null;
}
if (type.IsEnum)
{
if (json[0] == '"')
{
json = json[1..^1];
}
try
{
return Enum.Parse(type, json, false);
}
catch
{
return 0;
}
}
if (type.IsArray)
{
Type arrayType = type.GetElementType();
if (json[0] != '[' || json[^1] != ']')
{
return null;
}
List<string> elems = Split(json);
Array newArray = Array.CreateInstance(arrayType, elems.Count);
for (int i = 0; i < elems.Count; i++)
{
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
}
splitArrayPool.Push(elems);
return newArray;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
Type listType = type.GetGenericArguments()[0];
if (json[0] != '[' || json[^1] != ']')
{
return null;
}
List<string> elems = Split(json);
var list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
for (int i = 0; i < elems.Count; i++)
{
list.Add(ParseValue(listType, elems[i]));
}
splitArrayPool.Push(elems);
return list;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
Type keyType, valueType;
{
Type[] args = type.GetGenericArguments();
keyType = args[0];
valueType = args[1];
}
// Refuse to parse dictionary keys that aren't of type string
if (keyType != typeof(string))
{
return null;
}
// Must be a valid dictionary element
if (json[0] != '{' || json[^1] != '}')
{
return null;
}
// The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return null;
}
var dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
{
continue;
}
string keyValue = elems[i][1..^1];
object val = ParseValue(valueType, elems[i + 1]);
dictionary.Add(keyValue, val);
}
return dictionary;
}
if (type == typeof(object))
{
return ParseAnonymousValue(json);
}
if (json[0] == '{' && json[^1] == '}')
{
return ParseObject(type, json);
}
return null;
}
private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
{
stringBuilder.Append(json[startIdx]);
for (int i = startIdx + 1; i < json.Length; i++)
{
if (json[i] == '\\')
{
if (appendEscapeCharacter)
{
stringBuilder.Append(json[i]);
}
stringBuilder.Append(json[i + 1]);
i++; // Skip next character as it is escaped
}
else if (json[i] == '"')
{
stringBuilder.Append(json[i]);
return i;
}
else
{
stringBuilder.Append(json[i]);
}
}
return json.Length - 1;
}
// Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
private static List<string> Split(string json)
{
List<string> splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : new List<string>();
splitArray.Clear();
if (json.Length == 2)
{
return splitArray;
}
int parseDepth = 0;
stringBuilder.Length = 0;
for (int i = 1; i < json.Length - 1; i++)
{
switch (json[i])
{
case '[':
case '{':
parseDepth++;
break;
case ']':
case '}':
parseDepth--;
break;
case '"':
i = AppendUntilStringEnd(true, i, json);
continue;
case ',':
case ':':
if (parseDepth == 0)
{
splitArray.Add(stringBuilder.ToString());
stringBuilder.Length = 0;
continue;
}
break;
}
stringBuilder.Append(json[i]);
}
splitArray.Add(stringBuilder.ToString());
return splitArray;
}
private static object ParseAnonymousValue(string json)
{
if (json.Length == 0)
{
return null;
}
if (json[0] == '{' && json[^1] == '}')
{
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return null;
}
var dict = new Dictionary<string, object>(elems.Count / 2);
for (int i = 0; i < elems.Count; i += 2)
{
dict.Add(elems[i][1..^1], ParseAnonymousValue(elems[i + 1]));
}
return dict;
}
if (json[0] == '[' && json[^1] == ']')
{
List<string> items = Split(json);
var finalList = new List<object>(items.Count);
for (int i = 0; i < items.Count; i++)
{
finalList.Add(ParseAnonymousValue(items[i]));
}
return finalList;
}
if (json[0] == '"' && json[^1] == '"')
{
return ParseValue(typeof(string), json); // fix https://github.com/zanders3/json/issues/29
}
if (char.IsDigit(json[0]) || json[0] == '-')
{
if (json.Contains('.'))
{
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result);
return result;
}
else
{
_ = int.TryParse(json, out int result);
return result;
}
}
if (json == "true")
{
return true;
}
if (json == "false")
{
return false;
}
// handles json == "null" as well as invalid JSON
return null;
}
private static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members)
where T : MemberInfo
{
Dictionary<string, T> nameToMember = new(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < members.Length; i++)
{
/*T member = members[i];
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
continue;
string name = member.Name;
if (member.IsDefined(typeof(DataMemberAttribute), true))
{
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
name = dataMemberAttribute.Name;
}
nameToMember.Add(name, member);*/
// The above code is not working with .Net framework 2.0, so we ignore these attributes for compatibility reasons:
nameToMember.Add(members[i].Name, members[i]);
}
return nameToMember;
}
private static object ParseObject(Type type, string json)
{
object instance = FormatterServices.GetUninitializedObject(type);
// The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return instance;
}
if (!fieldInfoCache.TryGetValue(type, out Dictionary<string, FieldInfo> nameToField))
{
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
fieldInfoCache.Add(type, nameToField);
}
if (!propertyInfoCache.TryGetValue(type, out Dictionary<string, PropertyInfo> nameToProperty))
{
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
propertyInfoCache.Add(type, nameToProperty);
}
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
{
continue;
}
string key = elems[i][1..^1];
string value = elems[i + 1];
if (nameToField.TryGetValue(key, out FieldInfo fieldInfo))
{
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
}
else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
}
}
return instance;
}
}
}

View File

@@ -0,0 +1,16 @@
// <copyright file="WindowsExplorerSort.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System.Collections.Generic;
internal class WindowsExplorerSort : IComparer<string>
{
public int Compare(string x, string y)
{
return Global.DllImports.NativeMethods.ShlwapiStrCmpLogicalW(x, y);
}
}
}

View File

@@ -0,0 +1,80 @@
// <copyright file="WindowsTaskbar.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace FSI.BT.Tools.SystemTrayMenu.Helper
{
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using static Global.DllImports.NativeMethods;
public enum TaskbarPosition
{
Unknown = -1,
Left,
Top,
Right,
Bottom,
}
public sealed class WindowsTaskbar
{
private const string ClassName = "Shell_TrayWnd";
public WindowsTaskbar()
{
IntPtr taskbarHandle = User32FindWindow(ClassName, null);
APPBARDATA data = new()
{
cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA)),
hWnd = taskbarHandle,
};
IntPtr result = Shell32SHAppBarMessage(ABM.GetTaskbarPos, ref data);
if (result == IntPtr.Zero)
{
Bounds = new Rectangle(20, 20, 20, 20);
}
else
{
Position = (TaskbarPosition)data.uEdge;
Bounds = Rectangle.FromLTRB(data.rc.left, data.rc.top, data.rc.right, data.rc.bottom);
data.cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA));
result = Shell32SHAppBarMessage(ABM.GetState, ref data);
int state = result.ToInt32();
AlwaysOnTop = (state & ABS.AlwaysOnTop) == ABS.AlwaysOnTop;
AutoHide = (state & ABS.Autohide) == ABS.Autohide;
}
}
public Rectangle Bounds
{
get;
private set;
}
public TaskbarPosition Position
{
get;
private set;
}
public Point Location => Bounds.Location;
public Size Size => Bounds.Size;
public bool AlwaysOnTop
{
get;
private set;
}
public bool AutoHide
{
get;
private set;
}
}
}