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); + } + } }