From ebc4ce5433d74a61bcba7a2b0bb90d923d94a90a Mon Sep 17 00:00:00 2001 From: Dan Clark Date: Sat, 2 Nov 2024 19:56:57 +0000 Subject: [PATCH] WIP --- Toolkit.Foundation/ConfigurationWriter.cs | 2 +- Toolkit.Foundation/IDispatcherTimer.cs | 8 + Toolkit.Foundation/IDispatcherTimerFactory.cs | 6 + Toolkit.Foundation/Toolkit.Foundation.csproj | 1 + .../Toolkit.UI.Avalonia.csproj | 1 + .../Toolkit.UI.Controls.Avalonia.csproj | 1 + Toolkit.Windows/IPointerMonitor.cs | 5 + Toolkit.Windows/ITaskbarList.cs | 6 + Toolkit.Windows/NativeMethods.json | 4 + Toolkit.Windows/NativeMethods.txt | 17 ++ Toolkit.Windows/PInvoke.cs | 62 ++++++ Toolkit.Windows/Rect.cs | 22 ++ Toolkit.Windows/TaskbarButtonMonitor.cs | 189 ++++++++++++++++++ Toolkit.Windows/Toolkit.Windows.csproj | 25 +++ Toolkit.Windows/WindowHelper.cs | 49 +++++ Toolkit.sln | 48 ++++- 16 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 Toolkit.Foundation/IDispatcherTimer.cs create mode 100644 Toolkit.Foundation/IDispatcherTimerFactory.cs create mode 100644 Toolkit.Windows/IPointerMonitor.cs create mode 100644 Toolkit.Windows/ITaskbarList.cs create mode 100644 Toolkit.Windows/NativeMethods.json create mode 100644 Toolkit.Windows/NativeMethods.txt create mode 100644 Toolkit.Windows/PInvoke.cs create mode 100644 Toolkit.Windows/Rect.cs create mode 100644 Toolkit.Windows/TaskbarButtonMonitor.cs create mode 100644 Toolkit.Windows/Toolkit.Windows.csproj create mode 100644 Toolkit.Windows/WindowHelper.cs diff --git a/Toolkit.Foundation/ConfigurationWriter.cs b/Toolkit.Foundation/ConfigurationWriter.cs index 41544be..4c9d277 100644 --- a/Toolkit.Foundation/ConfigurationWriter.cs +++ b/Toolkit.Foundation/ConfigurationWriter.cs @@ -20,4 +20,4 @@ public class ConfigurationWriter(IConfigurationSource source.Set(value); public void Write(TConfiguration value) => source.Set(value); -} \ No newline at end of file +} diff --git a/Toolkit.Foundation/IDispatcherTimer.cs b/Toolkit.Foundation/IDispatcherTimer.cs new file mode 100644 index 0000000..91b6a5f --- /dev/null +++ b/Toolkit.Foundation/IDispatcherTimer.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public interface IDispatcherTimer +{ + void Start(); + + void Stop(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IDispatcherTimerFactory.cs b/Toolkit.Foundation/IDispatcherTimerFactory.cs new file mode 100644 index 0000000..5d0d17b --- /dev/null +++ b/Toolkit.Foundation/IDispatcherTimerFactory.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IDispatcherTimerFactory +{ + IDispatcherTimer Create(Action actionDelegate, TimeSpan interval); +} \ No newline at end of file diff --git a/Toolkit.Foundation/Toolkit.Foundation.csproj b/Toolkit.Foundation/Toolkit.Foundation.csproj index e27f1b3..ef89a92 100644 --- a/Toolkit.Foundation/Toolkit.Foundation.csproj +++ b/Toolkit.Foundation/Toolkit.Foundation.csproj @@ -3,6 +3,7 @@ net8.0 enable enable + AnyCPU;x64;x86 diff --git a/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj b/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj index e80fef1..97ea892 100644 --- a/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj +++ b/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj @@ -3,6 +3,7 @@ net8.0 enable enable + AnyCPU;x64;x86 diff --git a/Toolkit.UI.Controls.Avalonia/Toolkit.UI.Controls.Avalonia.csproj b/Toolkit.UI.Controls.Avalonia/Toolkit.UI.Controls.Avalonia.csproj index 18560b4..98c865e 100644 --- a/Toolkit.UI.Controls.Avalonia/Toolkit.UI.Controls.Avalonia.csproj +++ b/Toolkit.UI.Controls.Avalonia/Toolkit.UI.Controls.Avalonia.csproj @@ -4,6 +4,7 @@ enable enable true + AnyCPU;x64;x86 diff --git a/Toolkit.Windows/IPointerMonitor.cs b/Toolkit.Windows/IPointerMonitor.cs new file mode 100644 index 0000000..76502a0 --- /dev/null +++ b/Toolkit.Windows/IPointerMonitor.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Windows; + +public interface IPointerMonitor : + IInitializer, + IDisposable; diff --git a/Toolkit.Windows/ITaskbarList.cs b/Toolkit.Windows/ITaskbarList.cs new file mode 100644 index 0000000..1ecbb6b --- /dev/null +++ b/Toolkit.Windows/ITaskbarList.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Windows; + +public interface ITaskbarList +{ + IntPtr GetHandle(); +} diff --git a/Toolkit.Windows/NativeMethods.json b/Toolkit.Windows/NativeMethods.json new file mode 100644 index 0000000..3001057 --- /dev/null +++ b/Toolkit.Windows/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "public": true +} \ No newline at end of file diff --git a/Toolkit.Windows/NativeMethods.txt b/Toolkit.Windows/NativeMethods.txt new file mode 100644 index 0000000..c41c717 --- /dev/null +++ b/Toolkit.Windows/NativeMethods.txt @@ -0,0 +1,17 @@ +SetWindowsHookEx +GetModuleHandle +CallNextHookEx +GetPhysicalCursorPos +FindWindowEx +FindWindow +GetWindowRect +DestroyWindow +DefWindowProcW +CreateWindowExW +RegisterClassW +GetSystemMetrics +MonitorFromWindow +RegisterWindowMessage +GetDpiForWindow +SetWindowPos +SHCreateShellItemArrayFromDataObject \ No newline at end of file diff --git a/Toolkit.Windows/PInvoke.cs b/Toolkit.Windows/PInvoke.cs new file mode 100644 index 0000000..aef9535 --- /dev/null +++ b/Toolkit.Windows/PInvoke.cs @@ -0,0 +1,62 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Windows.Win32; + +public static partial class PInvoke +{ + public static readonly int SPI_SETWORKAREA = 0x002F; + public static readonly uint WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated"); + + [StructLayout(LayoutKind.Sequential)] + public struct AppBarData + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public AppBarEdge uEdge; + public RECT rect; + public int lParam; + } + + public enum AppBarMessage : uint + { + New = 0x00000000, + Remove = 0x00000001, + QueryPos = 0x00000002, + SetPos = 0x00000003, + GetState = 0x00000004, + GetTaskbarPos = 0x00000005, + Activate = 0x00000006, + GetAutoHideBar = 0x00000007, + SetAutoHideBar = 0x00000008, + WindowPosChanged = 0x00000009, + SetState = 0x0000000A, + } + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("shell32.dll", SetLastError = true)] + public static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, ref AppBarData pData); + + public static AppBarData GetAppBarData(IntPtr handle) + { + return new AppBarData + { + cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)), + hWnd = handle + }; + } + + public enum AppBarEdge : uint + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } + + public static void GetAppBarPosition(ref AppBarData appBarData) => + SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData); +} diff --git a/Toolkit.Windows/Rect.cs b/Toolkit.Windows/Rect.cs new file mode 100644 index 0000000..d25bae6 --- /dev/null +++ b/Toolkit.Windows/Rect.cs @@ -0,0 +1,22 @@ +namespace Toolkit.Windows; + +public record Rect +{ + public Rect() + { + + } + + public Rect(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + public int X { get; } + public int Y { get; } + public int Width { get; } + public int Height { get; } +} \ No newline at end of file diff --git a/Toolkit.Windows/TaskbarButtonMonitor.cs b/Toolkit.Windows/TaskbarButtonMonitor.cs new file mode 100644 index 0000000..b8d8486 --- /dev/null +++ b/Toolkit.Windows/TaskbarButtonMonitor.cs @@ -0,0 +1,189 @@ +using Windows.Win32; +using Windows.Win32.UI.WindowsAndMessaging; +using Windows.Win32.Foundation; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; + +namespace Toolkit.Windows; + +public record PointerPressed(PointerLocation Location, PointerButton Button = PointerButton.Left); + +public record PointerDragReleased(PointerLocation Location, PointerButton Button = PointerButton.Left); + +public record PointerReleased(PointerLocation Location, PointerButton Button = PointerButton.Left); + +public record PointerLocation(int X, int Y); + +public record PointerMoved(PointerLocation Location); + +internal enum WndProcMessages +{ + WM_LBUTTONUP = 0x0202, + WM_MBUTTONUP = 0x0208, + WM_RBUTTONUP = 0x0205, + WM_MOUSEMOVE = 0x0200, + WM_SETTINGCHANGE = 0x001A, + WM_MBUTTONDOWN = 0x0207, + WM_LBUTTONDOWN = 0x0201, + WM_RBUTTONDOWN = 0x0204 +} + +public enum PointerButton +{ + Left, + Middle, + Right +} + +public record PointerDrag(PointerLocation Location); + + +public class PointerMonitor : IPointerMonitor +{ + private readonly IMessenger messenger; + private bool isDisposed; + private bool isPointerPressed; + private HOOKPROC? mouseEventDelegate; + private UnhookWindowsHookExSafeHandle? mouseHandle; + private bool isPointerDrag; + + public PointerMonitor(IMessenger messenger) + { + this.messenger = messenger; + } + + ~PointerMonitor() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public unsafe Task Initialize() + { + InitializeHook(); + return Task.CompletedTask; + } + + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + RemoveHook(); + isDisposed = true; + } + } + + private unsafe void InitializeHook() + { + mouseEventDelegate = new HOOKPROC(MouseProc); + mouseHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_MOUSE_LL, mouseEventDelegate, + PInvoke.GetModuleHandle("user32.dll"), 0); + } + + private unsafe bool TryGetPointer(out Point point) + { + fixed (Point* lpPointLocal = &point) + { + return PInvoke.GetPhysicalCursorPos(lpPointLocal); + } + } + + private bool TryGetPointerLocation([MaybeNullWhen(false)] out PointerLocation location) + { + if (TryGetPointer(out Point point)) + { + location = new PointerLocation(point.X, point.Y); + return true; + + } + + location = null; + return false; + } + + private LRESULT MouseProc(int nCode, WPARAM wParam, LPARAM lParam) + { + if (nCode >= 0) + { + + if (TryGetPointerLocation(out var location)) + { + switch ((uint)wParam.Value) + { + case (uint)WndProcMessages.WM_MOUSEMOVE: + SendPointerMoved(location); + break; + case (uint)WndProcMessages.WM_LBUTTONUP: + SendPointerReleased(location, PointerButton.Left); + break; + case (uint)WndProcMessages.WM_MBUTTONUP: + SendPointerReleased(location, PointerButton.Middle); + break; + case (uint)WndProcMessages.WM_RBUTTONUP: + SendPointerReleased(location, PointerButton.Right); + break; + case (uint)WndProcMessages.WM_LBUTTONDOWN: + SendPointerPressed(location, PointerButton.Left); + break; + case (uint)WndProcMessages.WM_MBUTTONDOWN: + SendPointerPressed(location, PointerButton.Middle); + break; + case (uint)WndProcMessages.WM_RBUTTONDOWN: + SendPointerPressed(location, PointerButton.Right); + break; + } + } + } + + return PInvoke.CallNextHookEx(mouseHandle, nCode, wParam, lParam); + } + + private unsafe void RemoveHook() + { + if (mouseHandle is not null && mouseHandle.DangerousGetHandle() != nint.Zero) + { + PInvoke.UnhookWindowsHookEx((HHOOK)mouseHandle.DangerousGetHandle()); + } + } + + private void SendPointerMoved(PointerLocation location) + { + if (isPointerPressed) + { + if (!isPointerDrag) + { + isPointerDrag = true; + } + + messenger.Send(new PointerDrag(location)); + } + + messenger.Send(new PointerMoved(location)); + } + + private void SendPointerPressed(PointerLocation location, PointerButton button) + { + isPointerPressed = true; + messenger.Send(new PointerPressed(location, button)); + } + + private void SendPointerReleased(PointerLocation location, PointerButton button) + { + if (isPointerPressed) + { + if (isPointerDrag) + { + isPointerDrag = false; + messenger.Send(new PointerDragReleased(location, button)); + } + + isPointerPressed = false; + messenger.Send(new PointerReleased(location, button)); + } + } +} \ No newline at end of file diff --git a/Toolkit.Windows/Toolkit.Windows.csproj b/Toolkit.Windows/Toolkit.Windows.csproj new file mode 100644 index 0000000..093ec63 --- /dev/null +++ b/Toolkit.Windows/Toolkit.Windows.csproj @@ -0,0 +1,25 @@ + + + + net8.0-windows + enable + enable + True + True + x64;x86 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/Toolkit.Windows/WindowHelper.cs b/Toolkit.Windows/WindowHelper.cs new file mode 100644 index 0000000..af20301 --- /dev/null +++ b/Toolkit.Windows/WindowHelper.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Toolkit.Windows; + +public class WindowHelper +{ + public static void BringToForeground(HWND handle) + { + if (TryGetBoundsUnsafe(handle, out var bounds)) + { + PInvoke.SetWindowPos(handle, new HWND(), bounds.left, bounds.top, bounds.right - + bounds.left, bounds.bottom - bounds.top, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); + } + } + + public static IntPtr Find(string windowName) => + PInvoke.FindWindow(windowName, null); + + public static IntPtr Find(string windowName, IntPtr parentHandle) => + PInvoke.FindWindowEx(new HWND(parentHandle), new HWND(), windowName, null); + + public static void MoveAndResize(HWND handle, int x, int y, int width, int height) => + PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0); + + public static bool TryGetBounds(IntPtr handle, + [MaybeNullWhen(false)] out Rect rect) + { + if (TryGetBoundsUnsafe(handle, out var unsafeRect)) + { + rect = new Rect(unsafeRect.left, unsafeRect.top, unsafeRect.right - unsafeRect.left, unsafeRect.bottom - unsafeRect.top); + + return true; + } + + rect = null; + return false; + } + + private static unsafe bool TryGetBoundsUnsafe(IntPtr handle, out RECT rect) + { + fixed (RECT* lpRectLocal = &rect) + { + return PInvoke.GetWindowRect(new HWND(handle), lpRectLocal); + } + } +} diff --git a/Toolkit.sln b/Toolkit.sln index 8c89dac..a42f5d7 100644 --- a/Toolkit.sln +++ b/Toolkit.sln @@ -3,30 +3,72 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33110.190 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.Foundation", "Toolkit.Foundation\Toolkit.Foundation.csproj", "{66968F8D-689E-49D8-9370-DFF099C56202}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Foundation", "Toolkit.Foundation\Toolkit.Foundation.csproj", "{66968F8D-689E-49D8-9370-DFF099C56202}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.UI.Avalonia", "Toolkit.UI.Avalonia\Toolkit.UI.Avalonia.csproj", "{E091FA94-2F15-403A-98D1-4557C2FF9A02}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.UI.Avalonia", "Toolkit.UI.Avalonia\Toolkit.UI.Avalonia.csproj", "{E091FA94-2F15-403A-98D1-4557C2FF9A02}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.UI.Controls.Avalonia", "Toolkit.UI.Controls.Avalonia\Toolkit.UI.Controls.Avalonia.csproj", "{8841990D-A246-495D-9A40-C39BA4347505}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.UI.Controls.Avalonia", "Toolkit.UI.Controls.Avalonia\Toolkit.UI.Controls.Avalonia.csproj", "{8841990D-A246-495D-9A40-C39BA4347505}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Windows", "Toolkit.Windows\Toolkit.Windows.csproj", "{6F60DA3B-420F-46D6-A882-119A1E00BB50}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|x64.ActiveCfg = Debug|x64 + {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|x64.Build.0 = Debug|x64 + {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|x86.ActiveCfg = Debug|x86 + {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|x86.Build.0 = Debug|x86 {66968F8D-689E-49D8-9370-DFF099C56202}.Release|Any CPU.ActiveCfg = Release|Any CPU {66968F8D-689E-49D8-9370-DFF099C56202}.Release|Any CPU.Build.0 = Release|Any CPU + {66968F8D-689E-49D8-9370-DFF099C56202}.Release|x64.ActiveCfg = Release|x64 + {66968F8D-689E-49D8-9370-DFF099C56202}.Release|x64.Build.0 = Release|x64 + {66968F8D-689E-49D8-9370-DFF099C56202}.Release|x86.ActiveCfg = Release|x86 + {66968F8D-689E-49D8-9370-DFF099C56202}.Release|x86.Build.0 = Release|x86 {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|x64.ActiveCfg = Debug|x64 + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|x64.Build.0 = Debug|x64 + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|x86.ActiveCfg = Debug|x86 + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|x86.Build.0 = Debug|x86 {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|Any CPU.ActiveCfg = Release|Any CPU {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|Any CPU.Build.0 = Release|Any CPU + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|x64.ActiveCfg = Release|x64 + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|x64.Build.0 = Release|x64 + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|x86.ActiveCfg = Release|x86 + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|x86.Build.0 = Release|x86 {8841990D-A246-495D-9A40-C39BA4347505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8841990D-A246-495D-9A40-C39BA4347505}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8841990D-A246-495D-9A40-C39BA4347505}.Debug|x64.ActiveCfg = Debug|x64 + {8841990D-A246-495D-9A40-C39BA4347505}.Debug|x64.Build.0 = Debug|x64 + {8841990D-A246-495D-9A40-C39BA4347505}.Debug|x86.ActiveCfg = Debug|x86 + {8841990D-A246-495D-9A40-C39BA4347505}.Debug|x86.Build.0 = Debug|x86 {8841990D-A246-495D-9A40-C39BA4347505}.Release|Any CPU.ActiveCfg = Release|Any CPU {8841990D-A246-495D-9A40-C39BA4347505}.Release|Any CPU.Build.0 = Release|Any CPU + {8841990D-A246-495D-9A40-C39BA4347505}.Release|x64.ActiveCfg = Release|x64 + {8841990D-A246-495D-9A40-C39BA4347505}.Release|x64.Build.0 = Release|x64 + {8841990D-A246-495D-9A40-C39BA4347505}.Release|x86.ActiveCfg = Release|x86 + {8841990D-A246-495D-9A40-C39BA4347505}.Release|x86.Build.0 = Release|x86 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Debug|Any CPU.ActiveCfg = Debug|x64 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Debug|Any CPU.Build.0 = Debug|x64 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Debug|x64.ActiveCfg = Debug|x64 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Debug|x64.Build.0 = Debug|x64 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Debug|x86.ActiveCfg = Debug|x86 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Debug|x86.Build.0 = Debug|x86 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Release|Any CPU.Build.0 = Release|Any CPU + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Release|x64.ActiveCfg = Release|x64 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Release|x64.Build.0 = Release|x64 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Release|x86.ActiveCfg = Release|x86 + {6F60DA3B-420F-46D6-A882-119A1E00BB50}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE