diff --git a/OnTopReplica/AppPaths.cs b/OnTopReplica/AppPaths.cs
new file mode 100644
index 0000000..cea16b5
--- /dev/null
+++ b/OnTopReplica/AppPaths.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace OnTopReplica {
+ public static class AppPaths {
+
+ const string AppDataFolder = "OnTopReplica";
+
+ public static void SetupPaths() {
+ var roamingAppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ var roamingAppDataApplicationPath = Path.Combine(roamingAppData, AppDataFolder);
+
+ if (!Directory.Exists(roamingAppDataApplicationPath)) {
+ Directory.CreateDirectory(roamingAppDataApplicationPath);
+ }
+ PrivateRoamingFolderPath = roamingAppDataApplicationPath;
+ }
+
+ public static string PrivateRoamingFolderPath { get; private set; }
+
+ public static string GenerateCrashDumpPath() {
+ var now = DateTime.Now;
+
+ string dump = string.Format("OnTopReplica-dump-{0}{1}{2}-{3}{4}.txt",
+ now.Year, now.Month, now.Day,
+ now.Hour, now.Minute);
+
+ return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), dump);
+ }
+ }
+}
diff --git a/OnTopReplica/AspectRatioForm.cs b/OnTopReplica/AspectRatioForm.cs
index 9701e3c..87f99f7 100644
--- a/OnTopReplica/AspectRatioForm.cs
+++ b/OnTopReplica/AspectRatioForm.cs
@@ -116,7 +116,8 @@ namespace OnTopReplica {
MinimumSize.Width);
//Determine new height while keeping aspect ratio
- int newHeight = (int)((newWidth - ExtraPadding.Horizontal - clientSizeConversionWidth) / AspectRatio) + ExtraPadding.Vertical + clientSizeConversionHeight;
+ var clientConversionDifference = ClientWindowDifference;
+ int newHeight = (int)((newWidth - ExtraPadding.Horizontal - clientConversionDifference.Width) / AspectRatio) + ExtraPadding.Vertical + clientConversionDifference.Height;
//Apply and move form to recenter
Size = new Size(newWidth, newHeight);
@@ -134,9 +135,7 @@ namespace OnTopReplica {
AspectRatio = ((double)aspectRatioSource.Width / (double)aspectRatioSource.Height);
_keepAspectRatio = true;
-#if DEBUG
- System.Diagnostics.Trace.WriteLine(string.Format("Setting aspect ratio of {0} (for {1}).", AspectRatio, aspectRatioSource));
-#endif
+ Log.Write("Setting new aspect ratio {0} (for {1})", AspectRatio, aspectRatioSource);
if (forceRefresh) {
RefreshAspectRatio();
@@ -163,11 +162,13 @@ namespace OnTopReplica {
///
protected override void WndProc(ref Message m) {
if (KeepAspectRatio && m.Msg == WM.SIZING) {
+ var clientSizeConversion = ClientWindowDifference;
+
var rc = (Native.NRectangle)Marshal.PtrToStructure(m.LParam, typeof(Native.NRectangle));
int res = m.WParam.ToInt32();
- int width = (rc.Right - rc.Left) - clientSizeConversionWidth - ExtraPadding.Horizontal;
- int height = (rc.Bottom - rc.Top) - clientSizeConversionHeight - ExtraPadding.Vertical;
+ int width = (rc.Right - rc.Left) - clientSizeConversion.Width - ExtraPadding.Horizontal;
+ int height = (rc.Bottom - rc.Top) - clientSizeConversion.Height - ExtraPadding.Vertical;
if (res == WMSZ.LEFT || res == WMSZ.RIGHT) {
//Left or right resize, adjust top and bottom
@@ -175,7 +176,7 @@ namespace OnTopReplica {
int diffHeight = height - targetHeight;
rc.Top += (int)(diffHeight / 2.0);
- rc.Bottom = rc.Top + targetHeight + ExtraPadding.Vertical + clientSizeConversionHeight;
+ rc.Bottom = rc.Top + targetHeight + ExtraPadding.Vertical + clientSizeConversion.Height;
}
else if (res == WMSZ.TOP || res == WMSZ.BOTTOM) {
//Up or down resize, adjust left and right
@@ -183,15 +184,15 @@ namespace OnTopReplica {
int diffWidth = width - targetWidth;
rc.Left += (int)(diffWidth / 2.0);
- rc.Right = rc.Left + targetWidth + ExtraPadding.Horizontal + clientSizeConversionWidth;
+ rc.Right = rc.Left + targetWidth + ExtraPadding.Horizontal + clientSizeConversion.Width;
}
else if (res == WMSZ.RIGHT + WMSZ.BOTTOM || res == WMSZ.LEFT + WMSZ.BOTTOM) {
//Lower corner resize, adjust bottom
- rc.Bottom = rc.Top + (int)(width / AspectRatio) + ExtraPadding.Vertical + clientSizeConversionHeight;
+ rc.Bottom = rc.Top + (int)(width / AspectRatio) + ExtraPadding.Vertical + clientSizeConversion.Height;
}
else if (res == WMSZ.LEFT + WMSZ.TOP || res == WMSZ.RIGHT + WMSZ.TOP) {
//Upper corner resize, adjust top
- rc.Top = rc.Bottom - (int)(width / AspectRatio) - ExtraPadding.Vertical - clientSizeConversionHeight;
+ rc.Top = rc.Bottom - (int)(width / AspectRatio) - ExtraPadding.Vertical - clientSizeConversion.Height;
}
Marshal.StructureToPtr(rc, m.LParam, false);
@@ -204,15 +205,15 @@ namespace OnTopReplica {
#region ClientSize/Size conversion helpers
- int clientSizeConversionWidth, clientSizeConversionHeight;
-
///
/// Converts a client size measurement to a window size measurement.
///
/// Size of the window's client area.
/// Size of the whole window.
public Size FromClientSizeToSize(Size clientSize) {
- return new Size(clientSize.Width + clientSizeConversionWidth, clientSize.Height + clientSizeConversionHeight);
+ var difference = ClientWindowDifference;
+
+ return new Size(clientSize.Width + difference.Width, clientSize.Height + difference.Height);
}
///
@@ -221,18 +222,24 @@ namespace OnTopReplica {
/// Size of the whole window.
/// Size of the window's client area.
public Size FromSizeToClientSize(Size size) {
- return new Size(size.Width - clientSizeConversionWidth, size.Height - clientSizeConversionHeight);
- }
+ var difference = ClientWindowDifference;
- protected override void OnShown(EventArgs e) {
- base.OnShown(e);
-
- clientSizeConversionWidth = Size.Width - ClientSize.Width;
- clientSizeConversionHeight = Size.Height - ClientSize.Height;
+ return new Size(size.Width - difference.Width, size.Height - difference.Height);
}
#endregion
+ ///
+ /// Gets the difference in pixels between a client size value and a window size value (depending on window decoration).
+ ///
+ protected Size ClientWindowDifference {
+ get {
+ long style = WindowMethods.GetWindowLong(this.Handle, WindowMethods.WindowLong.Style).ToInt64();
+ long exStyle = WindowMethods.GetWindowLong(this.Handle, WindowMethods.WindowLong.ExStyle).ToInt64();
+ return WindowMethods.ConvertClientToWindowRect(new NRectangle(0, 0, 0, 0), style, exStyle).Size;
+ }
+ }
+
}
}
diff --git a/OnTopReplica/FocusedTextBox.cs b/OnTopReplica/FocusedTextBox.cs
index b2a2cc6..fcfb4c2 100644
--- a/OnTopReplica/FocusedTextBox.cs
+++ b/OnTopReplica/FocusedTextBox.cs
@@ -31,8 +31,6 @@ namespace OnTopReplica {
e.SuppressKeyPress = true;
}
- //Console.WriteLine("{0} ({1})", e.KeyCode, e.KeyValue);
-
base.OnKeyUp(e);
}
diff --git a/OnTopReplica/Log.cs b/OnTopReplica/Log.cs
new file mode 100644
index 0000000..ac1f967
--- /dev/null
+++ b/OnTopReplica/Log.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace OnTopReplica {
+ static class Log {
+
+ const string LogFileName = "lastrun.log.txt";
+
+ private readonly static StreamWriter Writer;
+
+ static Log() {
+ try {
+ var filepath = Path.Combine(AppPaths.PrivateRoamingFolderPath, LogFileName);
+ Writer = new StreamWriter(new FileStream(filepath, FileMode.Create));
+ Writer.AutoFlush = true;
+ }
+ catch (Exception) {
+ Writer = null;
+
+#if DEBUG
+ throw;
+#endif
+ }
+ }
+
+ public static void Write(string message) {
+ WriteLine(message);
+ }
+
+ public static void Write(string format, object arg0) {
+ WriteLine(string.Format(format, arg0));
+ }
+
+ public static void Write(string format, object arg0, object arg1) {
+ WriteLine(string.Format(format, arg0, arg1));
+ }
+
+ public static void Write(string format, params object[] args) {
+ WriteLine(string.Format(format, args));
+ }
+
+ public static void WriteDetails(string caption, string format, params object[] args) {
+ WriteLines(caption, string.Format(format, args));
+ }
+
+ public static void WriteException(string message, Exception exception) {
+ if (exception != null) {
+ WriteLines(message, exception.ToString());
+ }
+ else {
+ WriteLines(message, "(no last exception)");
+ }
+ }
+
+ private static void WriteLine(string message) {
+ var s = string.Format("{0,-8:HH:mm:ss} {1}", DateTime.Now, message);
+ Writer.WriteLine(s);
+ }
+
+ private static void WriteLines(params string[] messages) {
+ if (messages.Length > 0)
+ WriteLine(messages[0]);
+ if (messages.Length > 1) {
+ for (int i = 1; i < messages.Length; ++i) {
+ Writer.WriteLine(" {0}", messages[i]);
+ }
+ }
+ }
+
+ }
+}
diff --git a/OnTopReplica/MainForm.cs b/OnTopReplica/MainForm.cs
index 9450846..9be80f9 100644
--- a/OnTopReplica/MainForm.cs
+++ b/OnTopReplica/MainForm.cs
@@ -188,8 +188,10 @@ namespace OnTopReplica {
case WM.NCHITTEST:
//Make transparent to hit-testing if in click through mode
- if (ClickThroughEnabled && (ModifierKeys & Keys.Alt) != Keys.Alt) {
+ if (ClickThroughEnabled) {
m.Result = (IntPtr)HT.TRANSPARENT;
+
+ RefreshClickThroughComeBack();
return;
}
break;
@@ -264,7 +266,7 @@ namespace OnTopReplica {
/// Region of the window to clone or null.
public void SetThumbnail(WindowHandle handle, ThumbnailRegion region) {
try {
- System.Diagnostics.Trace.WriteLine(string.Format("Cloning window HWND {0} of class {1}.", handle.Handle, handle.Class));
+ Log.Write("Cloning window HWND {0} of class {1}", handle.Handle, handle.Class);
CurrentThumbnailWindowHandle = handle;
_thumbnailPanel.SetThumbnailHandle(handle, region);
@@ -273,7 +275,7 @@ namespace OnTopReplica {
SetAspectRatio(_thumbnailPanel.ThumbnailPixelSize, !FullscreenManager.IsFullscreen);
}
catch (Exception ex) {
- System.Diagnostics.Trace.Fail("Unable to set thumbnail.", ex.ToString());
+ Log.WriteException("Unable to set new thumbnail", ex);
ThumbnailError(ex, false, Strings.ErrorUnableToCreateThumbnail);
_thumbnailPanel.UnsetThumbnail();
diff --git a/OnTopReplica/MainForm_Features.cs b/OnTopReplica/MainForm_Features.cs
index 2e2586a..594f23d 100644
--- a/OnTopReplica/MainForm_Features.cs
+++ b/OnTopReplica/MainForm_Features.cs
@@ -1,10 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using OnTopReplica.Native;
using OnTopReplica.Properties;
-using WindowsFormsAero.TaskDialog;
+using System;
using System.Drawing;
using System.Windows.Forms;
+using WindowsFormsAero.TaskDialog;
namespace OnTopReplica {
//Contains some feature implementations of MainForm
@@ -36,6 +35,7 @@ namespace OnTopReplica {
#region Click-through
bool _clickThrough = false;
+
readonly Color DefaultNonClickTransparencyKey;
public bool ClickThroughEnabled {
@@ -43,21 +43,56 @@ namespace OnTopReplica {
return _clickThrough;
}
set {
- //Adjust opacity if fully opaque
- /*if (value && Opacity == 1.0)
- Opacity = 0.75;
- if (!value)
- Opacity = 1.0;*/
-
- //Enable transparency and force as top-most
TransparencyKey = (value) ? Color.Black : DefaultNonClickTransparencyKey;
- if (value)
+ if (value) {
+ //Re-force as top most (always helps in some cases)
TopMost = true;
+ }
_clickThrough = value;
}
}
+ //Must NOT be equal to any other valid opacity value
+ const double ClickThroughHoverOpacity = 0.6;
+
+ Timer _clickThroughComeBackTimer = null;
+ long _clickThroughComeBackTicks;
+ const int ClickThroughComeBackTimerInterval = 1000;
+
+ ///
+ /// When the mouse hovers over a fully opaque click-through form,
+ /// this fades the form to semi-transparency
+ /// and starts a timeout to get back to full opacity.
+ ///
+ private void RefreshClickThroughComeBack() {
+ if (this.Opacity == 1.0) {
+ this.Opacity = ClickThroughHoverOpacity;
+ }
+
+ if (_clickThroughComeBackTimer == null) {
+ _clickThroughComeBackTimer = new Timer();
+ _clickThroughComeBackTimer.Tick += _clickThroughComeBackTimer_Tick;
+ _clickThroughComeBackTimer.Interval = ClickThroughComeBackTimerInterval;
+ }
+ _clickThroughComeBackTicks = DateTime.UtcNow.Ticks;
+ _clickThroughComeBackTimer.Start();
+ }
+
+ void _clickThroughComeBackTimer_Tick(object sender, EventArgs e) {
+ var diff = DateTime.UtcNow.Subtract(new DateTime(_clickThroughComeBackTicks));
+ if (diff.TotalSeconds > 2) {
+ var mousePointer = WindowMethods.GetCursorPos();
+
+ if (!this.ContainsMousePointer(mousePointer)) {
+ if (this.Opacity == ClickThroughHoverOpacity) {
+ this.Opacity = 1.0;
+ }
+ _clickThroughComeBackTimer.Stop();
+ }
+ }
+ }
+
#endregion
#region Chrome
diff --git a/OnTopReplica/MainForm_MenuEvents.cs b/OnTopReplica/MainForm_MenuEvents.cs
index eb9ad0c..09a0899 100644
--- a/OnTopReplica/MainForm_MenuEvents.cs
+++ b/OnTopReplica/MainForm_MenuEvents.cs
@@ -73,11 +73,10 @@ namespace OnTopReplica {
}
private void Menu_Opacity_click(object sender, EventArgs e) {
- //Get clicked menu item
- ToolStripMenuItem tsi = sender as ToolStripMenuItem;
+ ToolStripMenuItem tsi = (ToolStripMenuItem)sender;
- if (tsi != null && this.Visible) {
- //Get opacity from the tag
+ if (this.Visible) {
+ //Target opacity is stored in the item's tag
this.Opacity = (double)tsi.Tag;
Program.Platform.OnFormStateChange(this);
}
diff --git a/OnTopReplica/MessagePumpManager.cs b/OnTopReplica/MessagePumpManager.cs
index ad2b48d..6d91797 100644
--- a/OnTopReplica/MessagePumpManager.cs
+++ b/OnTopReplica/MessagePumpManager.cs
@@ -15,7 +15,7 @@ namespace OnTopReplica {
_processors[processor.GetType()] = processor;
processor.Initialize(form);
- System.Diagnostics.Trace.WriteLine(string.Format("Registered message pump processor: {0}", processor.GetType()));
+ Log.Write("Registered message pump processor: {0}", processor.GetType());
}
///
@@ -27,10 +27,10 @@ namespace OnTopReplica {
//Register window shell hook
if (!HookMethods.RegisterShellHookWindow(form.Handle)) {
- Console.Error.WriteLine("Failed to register shell hook window.");
+ Log.Write("Failed to register shell hook window");
}
else {
- System.Diagnostics.Trace.WriteLine("Shell hook window registered successfully.");
+ Log.Write("Shell hook window registered successfully");
}
//Register message pump processors
@@ -65,10 +65,10 @@ namespace OnTopReplica {
public void Dispose() {
if (!HookMethods.DeregisterShellHookWindow(Form.Handle)) {
- Console.Error.WriteLine("Failed to deregister shell hook window.");
+ Log.Write("Failed to deregister shell hook window");
}
else {
- System.Diagnostics.Trace.WriteLine("Deregistered shell hook window successfully.");
+ Log.Write("Deregistered shell hook window successfully");
}
foreach (var processor in _processors.Values) {
diff --git a/OnTopReplica/MessagePumpProcessors/GroupSwitchManager.cs b/OnTopReplica/MessagePumpProcessors/GroupSwitchManager.cs
index e665ef9..76b9099 100644
--- a/OnTopReplica/MessagePumpProcessors/GroupSwitchManager.cs
+++ b/OnTopReplica/MessagePumpProcessors/GroupSwitchManager.cs
@@ -71,8 +71,6 @@ namespace OnTopReplica.MessagePumpProcessors {
}
private void HandleForegroundWindowChange(IntPtr activeWindow) {
- System.Diagnostics.Trace.WriteLine(string.Format("New active window (h {0}). ", activeWindow));
-
//Seek window in tracked handles
WindowHandleWrapper activated = null;
foreach (var i in _lruHandles) {
@@ -82,7 +80,6 @@ namespace OnTopReplica.MessagePumpProcessors {
if (activated == null) {
//New foreground window is not tracked
- System.Diagnostics.Trace.WriteLine("Active window is not tracked.");
return;
}
@@ -93,7 +90,7 @@ namespace OnTopReplica.MessagePumpProcessors {
//Get least recently used
var next = _lruHandles[0];
- System.Diagnostics.Trace.WriteLine(string.Format("Tracked. Switching to {0} (last use: {1}).", next.WindowHandle.Title, next.LastTimeUsed));
+ Log.Write("Switched to tracked window: switching to {0} (last use: {1})", next.WindowHandle.Title, next.LastTimeUsed);
Form.SetThumbnail(next.WindowHandle, null);
}
diff --git a/OnTopReplica/MessagePumpProcessors/HotKeyManager.cs b/OnTopReplica/MessagePumpProcessors/HotKeyManager.cs
index ab68d38..7717b2a 100644
--- a/OnTopReplica/MessagePumpProcessors/HotKeyManager.cs
+++ b/OnTopReplica/MessagePumpProcessors/HotKeyManager.cs
@@ -46,7 +46,7 @@ namespace OnTopReplica.MessagePumpProcessors {
var key = ++_lastUsedKey;
if (!HotKeyMethods.RegisterHotKey(owner.Handle, key, modifiers, keyCode)) {
- Console.Error.WriteLine("Failed to create hotkey on keys {0}.", keyCode);
+ Log.Write("Failed to create hotkey on key {0} with modifiers {1}", keyCode, modifiers);
return null;
}
@@ -59,7 +59,7 @@ namespace OnTopReplica.MessagePumpProcessors {
public void Dispose() {
if (!HotKeyMethods.UnregisterHotKey(_hwnd, RegistrationKey)) {
- Console.Error.WriteLine("Failed to unregister hotkey #{0}.", RegistrationKey);
+ Log.Write("Failed to unregister hotkey #{0}", RegistrationKey);
}
}
}
diff --git a/OnTopReplica/MessagePumpProcessors/ShellInterceptProcessor.cs b/OnTopReplica/MessagePumpProcessors/ShellInterceptProcessor.cs
index ca95066..fc13b6c 100644
--- a/OnTopReplica/MessagePumpProcessors/ShellInterceptProcessor.cs
+++ b/OnTopReplica/MessagePumpProcessors/ShellInterceptProcessor.cs
@@ -17,7 +17,7 @@ namespace OnTopReplica.MessagePumpProcessors {
if (msg.Msg == HookMethods.WM_SHELLHOOKMESSAGE) {
int hookCode = msg.WParam.ToInt32();
- System.Diagnostics.Trace.WriteLine(string.Format("Hook msg #{0}: {1}", hookCode, msg.LParam));
+ Log.Write("Hook msg #{0}: {1}", hookCode, msg.LParam);
}
return false;
diff --git a/OnTopReplica/Native/HookMethods.cs b/OnTopReplica/Native/HookMethods.cs
index ea24f29..273bb7f 100644
--- a/OnTopReplica/Native/HookMethods.cs
+++ b/OnTopReplica/Native/HookMethods.cs
@@ -12,8 +12,9 @@ namespace OnTopReplica.Native {
static HookMethods() {
WM_SHELLHOOKMESSAGE = RegisterWindowMessage("SHELLHOOK");
- if (WM_SHELLHOOKMESSAGE == 0)
- Console.Error.WriteLine("Failed to register SHELLHOOK Windows message.");
+ if (WM_SHELLHOOKMESSAGE == 0) {
+ Log.Write("Failed to register SHELLHOOK window message");
+ }
}
public static int WM_SHELLHOOKMESSAGE {
diff --git a/OnTopReplica/Native/Rectangle.cs b/OnTopReplica/Native/Rectangle.cs
index cb138c8..1166648 100644
--- a/OnTopReplica/Native/Rectangle.cs
+++ b/OnTopReplica/Native/Rectangle.cs
@@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
+using System.Drawing;
namespace OnTopReplica.Native {
+
/// A native Rectangle Structure.
[StructLayout(LayoutKind.Sequential)]
struct NRectangle {
@@ -52,4 +54,13 @@ namespace OnTopReplica.Native {
}
}
+
+ static class NRectangleExtensions {
+
+ public static NRectangle ToNativeRectangle(this Size size) {
+ return new NRectangle(0, 0, size.Width, size.Height);
+ }
+
+ }
+
}
diff --git a/OnTopReplica/Native/WM.cs b/OnTopReplica/Native/WM.cs
index eddcd01..dceaced 100644
--- a/OnTopReplica/Native/WM.cs
+++ b/OnTopReplica/Native/WM.cs
@@ -21,6 +21,7 @@ namespace OnTopReplica.Native {
public const int NCLBUTTONDOWN = 0x00A1;
public const int NCLBUTTONDBLCLK = 0x00A3;
public const int NCRBUTTONUP = 0x00A5;
+ public const int NCMOUSELEAVE = 0x02A2;
public const int SYSCOMMAND = 0x0112;
public const int GETTEXT = 0x000D;
public const int GETTEXTLENGTH = 0x000E;
diff --git a/OnTopReplica/Native/WindowMethods.cs b/OnTopReplica/Native/WindowMethods.cs
index 06caede..7345218 100644
--- a/OnTopReplica/Native/WindowMethods.cs
+++ b/OnTopReplica/Native/WindowMethods.cs
@@ -9,6 +9,22 @@ namespace OnTopReplica.Native {
///
static class WindowMethods {
+ public static System.Drawing.Point GetCursorPos() {
+ NPoint ret;
+ if (GetCursorPosInternal(out ret))
+ return ret.ToPoint();
+ else {
+#if DEBUG
+ throw new InvalidOperationException("Unable to GetCursorPos");
+#else
+ return default(System.Drawing.Point);
+#endif
+ }
+ }
+
+ [DllImport("user32.dll", EntryPoint="GetCursorPos")]
+ private static extern bool GetCursorPosInternal(out NPoint point);
+
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetClientRect(IntPtr handle, out NRectangle rect);
@@ -40,17 +56,6 @@ namespace OnTopReplica.Native {
return String.Empty;
}
- const int MaxClassLength = 255;
-
- public static string GetWindowClass(IntPtr hwnd) {
- var sb = new StringBuilder(MaxClassLength + 1);
- RealGetWindowClass(hwnd, sb, MaxClassLength);
- return sb.ToString();
- }
-
- [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
- static extern uint RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder lpString, uint maxCount);
-
public enum WindowLong {
WndProc = (-4),
HInstance = (-6),
@@ -61,11 +66,6 @@ namespace OnTopReplica.Native {
Id = (-12)
}
- public enum ClassLong {
- Icon = -14,
- IconSmall = -34
- }
-
[Flags]
public enum WindowStyles : long {
None = 0,
@@ -115,7 +115,23 @@ namespace OnTopReplica.Native {
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLong nIndex, IntPtr dwNewLong);
- #region Class styles
+ #region Window class
+
+ const int MaxClassLength = 255;
+
+ public static string GetWindowClass(IntPtr hwnd) {
+ var sb = new StringBuilder(MaxClassLength + 1);
+ RealGetWindowClass(hwnd, sb, MaxClassLength);
+ return sb.ToString();
+ }
+
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ static extern uint RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder lpString, uint maxCount);
+
+ public enum ClassLong {
+ Icon = -14,
+ IconSmall = -34
+ }
[DllImport("user32.dll", EntryPoint = "GetClassLongPtrW")]
static extern IntPtr GetClassLong64(IntPtr hWnd, int nIndex);
@@ -137,5 +153,28 @@ namespace OnTopReplica.Native {
[DllImport("user32.dll")]
public static extern IntPtr GetMenu(IntPtr hwnd);
+ ///
+ /// Converts client size rectangle to window rectangle, according to window styles.
+ ///
+ /// Client area bounding box.
+ /// Style of window to compute.
+ /// Extended style of window to compute.
+ public static NRectangle ConvertClientToWindowRect(NRectangle clientRectangle, long windowStyle, long extendedWindowStyle) {
+ NRectangle tmp = clientRectangle;
+ if (AdjustWindowRectEx(ref tmp, windowStyle, false, extendedWindowStyle)) {
+ return tmp;
+ }
+ else {
+#if DEBUG
+ throw new InvalidOperationException("Failed to convert client rectangle to window rectangle");
+#else
+ return clientRectangle;
+#endif
+ }
+ }
+
+ [DllImport("user32.dll")]
+ private static extern bool AdjustWindowRectEx(ref NRectangle clientToWindowRect, long windowStyle, bool hasMenu, long extendedWindowStyle);
+
}
}
diff --git a/OnTopReplica/OnTopReplica.csproj b/OnTopReplica/OnTopReplica.csproj
index 0f3f4cf..16aad2e 100644
--- a/OnTopReplica/OnTopReplica.csproj
+++ b/OnTopReplica/OnTopReplica.csproj
@@ -101,6 +101,7 @@
+
Form
@@ -115,6 +116,7 @@
Component
+
Form
diff --git a/OnTopReplica/PlatformSupport.cs b/OnTopReplica/PlatformSupport.cs
index 5ecb432..ef81bdb 100644
--- a/OnTopReplica/PlatformSupport.cs
+++ b/OnTopReplica/PlatformSupport.cs
@@ -13,7 +13,15 @@ namespace OnTopReplica {
///
public static PlatformSupport Create() {
var os = Environment.OSVersion;
+ var platform = CreateFromOperatingSystem(os);
+ Log.Write("{0} detected, using support class {1}",
+ os.VersionString, platform.GetType().FullName);
+
+ return platform;
+ }
+
+ private static PlatformSupport CreateFromOperatingSystem(OperatingSystem os) {
if (os.Platform != PlatformID.Win32NT)
return new Other();
diff --git a/OnTopReplica/Platforms/WindowsSeven.cs b/OnTopReplica/Platforms/WindowsSeven.cs
index c24ee7c..4114f3b 100644
--- a/OnTopReplica/Platforms/WindowsSeven.cs
+++ b/OnTopReplica/Platforms/WindowsSeven.cs
@@ -33,29 +33,6 @@ namespace OnTopReplica.Platforms {
form.Show();
}
- /*public override void OnFormStateChange(MainForm form) {
- //SetWindowStyle(form);
- }*/
-
- ///
- /// Used to alter the window style. Not used anymore.
- ///
- private void SetWindowStyle(MainForm form) {
- if (!form.FullscreenManager.IsFullscreen) {
- //This hides the app from ALT+TAB
- //Note that when minimized, it will be shown as an (ugly) minimized tool window
- //thus we do not minimize, but set to transparent when hiding
- long exStyle = WindowMethods.GetWindowLong(form.Handle, WindowMethods.WindowLong.ExStyle).ToInt64();
-
- exStyle |= (long)(WindowMethods.WindowExStyles.ToolWindow);
- //exStyle &= ~(long)(WindowMethods.WindowExStyles.AppWindow);
-
- WindowMethods.SetWindowLong(form.Handle, WindowMethods.WindowLong.ExStyle, new IntPtr(exStyle));
-
- //WindowMethods.SetWindowLong(form.Handle, WindowMethods.WindowLong.HwndParent, WindowManagerMethods.GetDesktopWindow());
- }
- }
-
}
}
diff --git a/OnTopReplica/Program.cs b/OnTopReplica/Program.cs
index 12971ee..b03599a 100644
--- a/OnTopReplica/Program.cs
+++ b/OnTopReplica/Program.cs
@@ -22,6 +22,15 @@ namespace OnTopReplica {
///
[STAThread]
static void Main(string[] args) {
+ try {
+ AppPaths.SetupPaths();
+ }
+ catch (Exception ex) {
+ MessageBox.Show(string.Format("Unable to setup application folders: {0}", ex), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+
+ Log.Write("Launching OnTopReplica v.{0}", Application.ProductVersion);
+
//Hook fatal abort handler
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
@@ -31,6 +40,8 @@ namespace OnTopReplica {
return;
Platform.PreHandleFormInit();
+ Log.Write("Platform support initialized");
+
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
@@ -65,7 +76,7 @@ namespace OnTopReplica {
_mainForm.IsChromeVisible = true;
//Persist settings
- System.Diagnostics.Trace.WriteLine(string.Format("Persisting {0} size {1} to settings.", _mainForm.Location, _mainForm.ClientSize));
+ Log.Write("Last position before shutdown: {0}, size: {1}", _mainForm.Location, _mainForm.Size);
Settings.Default.RestoreLastPosition = _mainForm.Location;
Settings.Default.RestoreLastSize = _mainForm.ClientSize;
Settings.Default.Save();
@@ -90,20 +101,21 @@ namespace OnTopReplica {
///
static void UpdateManager_CheckCompleted(object sender, UpdateCheckCompletedEventArgs e) {
if (e.Success && e.Information != null) {
+ Log.Write("Updated check successful (latest version is {0})", e.Information.LatestVersion);
+
if (e.Information.IsNewVersion) {
Update.ConfirmAndInstall();
}
}
else {
- System.Diagnostics.Trace.WriteLine(string.Format("Failed to check updates. {0}", e.Error));
+ Log.WriteException("Unable to check for updates", e.Error);
}
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
- string dump = string.Format("OnTopReplica-dump-{0}{1}{2}-{3}{4}.txt",
- DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
- DateTime.Now.Hour, DateTime.Now.Minute);
- string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), dump);
+ Log.WriteException("Unhandled exception", e.ExceptionObject as Exception);
+
+ string path = AppPaths.GenerateCrashDumpPath();
using (var s = new FileStream(path, FileMode.Create)) {
using (var sw = new StreamWriter(s)) {
@@ -114,14 +126,16 @@ namespace OnTopReplica {
sw.WriteLine("Last exception:");
sw.WriteLine(e.ExceptionObject.ToString());
sw.WriteLine();
- sw.WriteLine("OnTopReplica v.{0}", Assembly.GetEntryAssembly().GetName().Version);
+ sw.WriteLine("OnTopReplica v.{0}", Application.ProductVersion);
sw.WriteLine("OS: {0}", Environment.OSVersion.ToString());
sw.WriteLine(".NET: {0}", Environment.Version.ToString());
- sw.WriteLine("Aero DWM: {0}", WindowsFormsAero.OsSupport.IsCompositionEnabled);
+ sw.WriteLine("DWM: {0}", WindowsFormsAero.OsSupport.IsCompositionEnabled);
sw.WriteLine("Launch command: {0}", Environment.CommandLine);
sw.WriteLine("UTC time: {0} {1}", DateTime.UtcNow.ToShortDateString(), DateTime.UtcNow.ToShortTimeString());
}
}
+
+ Log.Write("Crash dump written to {0}", path);
}
}
diff --git a/OnTopReplica/ScreenPosition.cs b/OnTopReplica/ScreenPosition.cs
index 0a99d6c..b792b0b 100644
--- a/OnTopReplica/ScreenPosition.cs
+++ b/OnTopReplica/ScreenPosition.cs
@@ -76,7 +76,7 @@ namespace OnTopReplica {
var move = end.Difference(start);
- System.Diagnostics.Trace.WriteLine(string.Format("From {0} to {1} => {2}.", start, end, move));
+ //System.Diagnostics.Trace.WriteLine(string.Format("From {0} to {1} => {2}.", start, end, move));
var original = form.Location;
form.Location = new Point(original.X + move.X, original.Y + move.Y);
diff --git a/OnTopReplica/StartupOptions/Factory.cs b/OnTopReplica/StartupOptions/Factory.cs
index ee44ca2..f62ad6c 100644
--- a/OnTopReplica/StartupOptions/Factory.cs
+++ b/OnTopReplica/StartupOptions/Factory.cs
@@ -34,7 +34,7 @@ namespace OnTopReplica.StartupOptions {
options.StartLocation = Settings.Default.RestoreLastPosition;
options.StartSize = Settings.Default.RestoreLastSize;
- System.Diagnostics.Trace.WriteLine(string.Format("Restoring window at {0} size {1}.", Settings.Default.RestoreLastPosition, Settings.Default.RestoreLastSize));
+ Log.Write("Restoring window at {0} size {1}", Settings.Default.RestoreLastPosition, Settings.Default.RestoreLastSize);
}
if (Settings.Default.RestoreLastWindow) {
@@ -45,14 +45,20 @@ namespace OnTopReplica.StartupOptions {
var seeker = new RestoreWindowSeeker(new IntPtr(handle), title, className);
seeker.SkipNotVisibleWindows = true;
seeker.Refresh();
+
var resultHandle = seeker.Windows.FirstOrDefault();
if (resultHandle != null) {
- //Load window
+ //Found a window: load it!
options.WindowId = resultHandle.Handle;
}
else {
- System.Diagnostics.Trace.WriteLine("Couldn't find window to restore.");
+ Log.WriteDetails("Failed to find window to restore from last use",
+ "HWND {0}, Title '{1}', Class '{2}'",
+ Settings.Default.RestoreLastWindowHwnd,
+ Settings.Default.RestoreLastWindowTitle,
+ Settings.Default.RestoreLastWindowClass
+ );
}
}
}
@@ -61,11 +67,17 @@ namespace OnTopReplica.StartupOptions {
var cmdOptions = new NDesk.Options.OptionSet()
.Add("windowId=", "Window handle ({HWND}) to be cloned.", id => {
options.WindowId = new IntPtr(id);
+ options.WindowTitle = null;
+ options.WindowClass = null;
})
.Add("windowTitle=", "Partial {TITLE} of the window to be cloned.", s => {
+ options.WindowId = null;
options.WindowTitle = s;
+ options.WindowClass = null;
})
.Add("windowClass=", "{CLASS} of the window to be cloned.", s => {
+ options.WindowId = null;
+ options.WindowTitle = null;
options.WindowClass = s;
})
.Add("v|visible", "If set, only clones windows that are visible.", s => {
diff --git a/OnTopReplica/StartupOptions/Options.cs b/OnTopReplica/StartupOptions/Options.cs
index 35f1b4e..39ef5bf 100644
--- a/OnTopReplica/StartupOptions/Options.cs
+++ b/OnTopReplica/StartupOptions/Options.cs
@@ -92,8 +92,8 @@ namespace OnTopReplica.StartupOptions {
#region Application
public void Apply(MainForm form) {
- //GUI
- form.IsChromeVisible = !DisableChrome;
+ Log.Write("Applying command line launch parameters");
+
form.Opacity = (double)Opacity / 255.0;
//Seek handle for thumbnail cloning
@@ -120,7 +120,6 @@ namespace OnTopReplica.StartupOptions {
handle = seeker.Windows.FirstOrDefault();
}
- //Position lock
if (StartPositionLock.HasValue) {
form.PositionLock = StartPositionLock.Value;
}
@@ -150,6 +149,8 @@ namespace OnTopReplica.StartupOptions {
form.ClickForwardingEnabled = true;
}
+ form.IsChromeVisible = !DisableChrome;
+
//Fullscreen
if (Fullscreen) {
form.FullscreenManager.SwitchFullscreen();
diff --git a/OnTopReplica/ThumbnailPanel.cs b/OnTopReplica/ThumbnailPanel.cs
index bd0b81d..f6135f3 100644
--- a/OnTopReplica/ThumbnailPanel.cs
+++ b/OnTopReplica/ThumbnailPanel.cs
@@ -194,7 +194,9 @@ namespace OnTopReplica {
/// Handle of the window to clone.
/// Optional region.
public void SetThumbnailHandle(WindowHandle handle, ThumbnailRegion region) {
- System.Diagnostics.Trace.WriteLine(string.Format("Setting thumbnail to handle {0}, with region {1}.", handle, region), "ThumbnailPanel");
+ Log.WriteDetails("Setting new thumbnail",
+ "HWND {0}, region {1}", handle, region
+ );
if (_thumbnail != null && !_thumbnail.IsInvalid) {
_thumbnail.Close();
@@ -203,8 +205,8 @@ namespace OnTopReplica {
//Get form and register thumbnail on it
Form owner = this.TopLevelControl as Form;
- if(owner == null)
- throw new Exception("Internal error: ThumbnailPanel.TopLevelControl is not a Form.");
+ if (owner == null)
+ throw new Exception("Internal error: ThumbnailPanel.TopLevelControl is not a Form.");
_labelGlass.Visible = false;
@@ -219,7 +221,7 @@ namespace OnTopReplica {
/// Disposes current thumbnail and enters stand-by mode.
///
public void UnsetThumbnail() {
- System.Diagnostics.Trace.WriteLine("Unsetting thumbnail.");
+ Log.Write("Unsetting thumbnail");
if (_thumbnail != null && !_thumbnail.IsInvalid) {
_thumbnail.Close();
@@ -327,7 +329,7 @@ namespace OnTopReplica {
endPoint.Y - startPoint.Y
);
- System.Diagnostics.Trace.WriteLine(string.Format("Drawn from {0} to {1}, as region {2}.", start, end, final));
+ //System.Diagnostics.Trace.WriteLine(string.Format("Drawn from {0} to {1}, as region {2}.", start, end, final));
//Signal
OnRegionDrawn(final);
diff --git a/OnTopReplica/WindowSeekers/ByTitleWindowSeeker.cs b/OnTopReplica/WindowSeekers/ByTitleWindowSeeker.cs
index c551392..5850335 100644
--- a/OnTopReplica/WindowSeekers/ByTitleWindowSeeker.cs
+++ b/OnTopReplica/WindowSeekers/ByTitleWindowSeeker.cs
@@ -16,7 +16,7 @@ namespace OnTopReplica.WindowSeekers {
if (titleSeekString == null)
throw new ArgumentNullException();
- TitleMatch = titleSeekString.Trim();
+ TitleMatch = titleSeekString.Trim().ToLowerInvariant();
}
public string TitleMatch { get; private set; }
@@ -30,15 +30,19 @@ namespace OnTopReplica.WindowSeekers {
if (!WindowManagerMethods.IsTopLevel(handle.Handle))
return -1;
+ string handleTitle = handle.Title.ToLowerInvariant();
int points = 0;
//Give points for partial match
- if (handle.Title.StartsWith(TitleMatch, StringComparison.InvariantCultureIgnoreCase))
- points += 10;
-
- //Give points for exact match
- if (handle.Title.Equals(TitleMatch, StringComparison.InvariantCultureIgnoreCase))
+ if (handleTitle.Equals(TitleMatch)) {
+ points += 20;
+ }
+ else if (handleTitle.StartsWith(TitleMatch)) {
+ points += 15;
+ }
+ else if (handleTitle.Contains(TitleMatch)) {
points += 10;
+ }
return points;
}
diff --git a/OnTopReplica/WindowsFormsExtensions.cs b/OnTopReplica/WindowsFormsExtensions.cs
index d664dc1..aa089e4 100644
--- a/OnTopReplica/WindowsFormsExtensions.cs
+++ b/OnTopReplica/WindowsFormsExtensions.cs
@@ -46,5 +46,15 @@ namespace OnTopReplica {
}
}
+ ///
+ /// Checks whether a control contains a mouse pointer position in screen coordinates.
+ ///
+ /// Mouse pointer position in screen coordinates.
+ public static bool ContainsMousePointer(this Control ctrl, System.Drawing.Point screenCoordinates) {
+ var bb = new System.Drawing.Rectangle(ctrl.Location, ctrl.Size);
+
+ return bb.Contains(screenCoordinates);
+ }
+
}
}