From bc28da6c93c0c34397897359ce602792acfccb44 Mon Sep 17 00:00:00 2001 From: jrhodnik Date: Sat, 25 May 2019 14:41:43 -0400 Subject: [PATCH] Window actions implemented. Keyboard controls implemented. --- VDash/MainWindow.xaml.cs | 40 +- .../Actions/BringWindowToFrontAction.cs | 54 ++ VDash/Overlay/Actions/IOverlayAction.cs | 14 + VDash/Overlay/Actions/MaximizeWindowAction.cs | 34 + VDash/Overlay/Actions/ResizeWindowAction.cs | 47 ++ VDash/Overlay/Actions/RestoreWindowAction.cs | 35 + VDash/Overlay/IOverlay.cs | 9 +- VDash/Overlay/WindowOverlay.cs | 568 ++++++++++------- VDash/Utilities/ConsoleManager.cs | 98 +++ VDash/Utilities/User32.cs | 596 ++++++++++++++++++ VDash/VDash.csproj | 9 +- VDash/WindowSelectors/FromProcessName.cs | 14 +- VDash/WindowSelectors/FromWindowName.cs | 13 + VDash/WindowSelectors/IWindowSelector.cs | 5 + 14 files changed, 1290 insertions(+), 246 deletions(-) create mode 100644 VDash/Overlay/Actions/BringWindowToFrontAction.cs create mode 100644 VDash/Overlay/Actions/IOverlayAction.cs create mode 100644 VDash/Overlay/Actions/MaximizeWindowAction.cs create mode 100644 VDash/Overlay/Actions/ResizeWindowAction.cs create mode 100644 VDash/Overlay/Actions/RestoreWindowAction.cs create mode 100644 VDash/Utilities/ConsoleManager.cs create mode 100644 VDash/Utilities/User32.cs diff --git a/VDash/MainWindow.xaml.cs b/VDash/MainWindow.xaml.cs index 76a4100..a8c507f 100644 --- a/VDash/MainWindow.xaml.cs +++ b/VDash/MainWindow.xaml.cs @@ -13,6 +13,7 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Valve.VR; +using VDash.Overlay; namespace VDash { @@ -25,7 +26,7 @@ namespace VDash { InitializeComponent(); - //Connect to VR runtime... + ///Connect to VR runtime... EVRInitError initError = EVRInitError.None; var ret = OpenVR.Init(ref initError, EVRApplicationType.VRApplication_Overlay); @@ -35,25 +36,38 @@ namespace VDash throw new Exception($"Error connecting to VR: {initError.ToString()}"); } - //ulong desktopOverlay = 0; - //var err = OpenVR.Overlay.FindOverlay("valve.steam.desktop", ref desktopOverlay); + StartOverlayWindow("Discord"); - //HmdVector2_t vec = new HmdVector2_t(); + StartOverlayWindow("Microsoft Visual Studio"); - //OpenVR.Overlay.GetOverlayMouseScale(desktopOverlay, ref vec); + StartOverlayWindow("Notepad++"); - //var discordSelector = new WindowSelectors.FromWindowTitle("Discord", WindowSelectors.FromWindowTitle.TitleSearchType.Contains); - //Overlay.WindowOverlay discordOverlay = new Overlay.WindowOverlay("discord", "Discord", discordSelector, true); - //discordOverlay.Create(); + //StartOverlayWindow("Putty"); - var vsSelector = new WindowSelectors.FromWindowTitle("Microsoft Visual Studio", WindowSelectors.FromWindowTitle.TitleSearchType.Contains); - Overlay.WindowOverlay vsOverlay = new Overlay.WindowOverlay("vs", "Visual Studio", vsSelector, true); - vsOverlay.Create(); + StartOverlayWindow("Paint"); - //Overlay.WindowOverlay obsOverlay = new Overlay.WindowOverlay("obs", "OBS", new WindowSelectors.FromProcessName("obs64"), false); - //obsOverlay.Create(); + Overlay.WindowOverlay obsoverlay = new Overlay.WindowOverlay("obs", "obs", new WindowSelectors.FromProcessName("obs64"), false); + obsoverlay.Create(); + obsoverlay.DestroyOnNoWindowFound = true; + var bringWindow = new Overlay.Actions.BringWindowToFrontAction(true); + obsoverlay.OverlayShownActions.Add(bringWindow); + } + + + //This is just for debugging I swear + int count = 0; + IOverlay StartOverlayWindow(string windowFilter) + { + var ws = new WindowSelectors.FromWindowTitle(windowFilter, WindowSelectors.FromWindowTitle.TitleSearchType.Contains); + Overlay.WindowOverlay overlay = new Overlay.WindowOverlay($"ws{count}", "Loading...", ws, true); + overlay.Create(); + + var bringWindow = new Overlay.Actions.BringWindowToFrontAction(true); + overlay.OverlayShownActions.Add(bringWindow); + count++; + return overlay; } } } diff --git a/VDash/Overlay/Actions/BringWindowToFrontAction.cs b/VDash/Overlay/Actions/BringWindowToFrontAction.cs new file mode 100644 index 0000000..8907c6b --- /dev/null +++ b/VDash/Overlay/Actions/BringWindowToFrontAction.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using VDash.Utilities; + +namespace VDash.Overlay.Actions +{ + public class BringWindowToFrontAction : IOverlayAction + { + /// + /// Unminimize the window if it's minimized. + /// + public bool UnminimizeIfMinimized { get; set; } + public bool Enabled { get; set; } = true; + + public BringWindowToFrontAction(bool unminimizeIfMinimized) + { + UnminimizeIfMinimized = unminimizeIfMinimized; + } + + public void PreformAction(IOverlay intOverlay) + { + if (!Enabled) { return; } + if (!intOverlay.GetType().IsAssignableFrom(typeof(WindowOverlay))) + { + Console.WriteLine("This action cannot be performed on a non-WindowOverlay overlay."); + return; + } + + WindowOverlay overlay = (WindowOverlay)intOverlay; + IntPtr handle = overlay.Selector.GetWindowHandle(); + PreformAction(handle); + } + + public void PreformAction(IntPtr handle) + { + //Make sure window isn't minimized + User32.WINDOWPLACEMENT placement = new User32.WINDOWPLACEMENT(); + placement.Length = Marshal.SizeOf(placement); + User32.GetWindowPlacement(handle, ref placement); + + if (placement.ShowCmd == User32.ShowWindowCommands.ShowMinimized) + { + User32.ShowWindow(handle, User32.ShowWindowCommands.Normal); + } + + User32.SetForegroundWindow(handle); + } + } +} diff --git a/VDash/Overlay/Actions/IOverlayAction.cs b/VDash/Overlay/Actions/IOverlayAction.cs new file mode 100644 index 0000000..44b5d0a --- /dev/null +++ b/VDash/Overlay/Actions/IOverlayAction.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VDash.Overlay.Actions +{ + public interface IOverlayAction + { + void PreformAction(IOverlay overlay); + bool Enabled { get; set; } + } +} diff --git a/VDash/Overlay/Actions/MaximizeWindowAction.cs b/VDash/Overlay/Actions/MaximizeWindowAction.cs new file mode 100644 index 0000000..507cde2 --- /dev/null +++ b/VDash/Overlay/Actions/MaximizeWindowAction.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using VDash.Utilities; + +namespace VDash.Overlay.Actions +{ + public class MaximizeWindowAction : IOverlayAction + { + public bool Enabled { get; set; } + + public void PreformAction(IOverlay intOverlay) + { + if (!Enabled) { return; } + if (!intOverlay.GetType().IsAssignableFrom(typeof(WindowOverlay))) + { + Console.WriteLine("This action cannot be performed on a non-WindowOverlay overlay."); + return; + } + + WindowOverlay overlay = (WindowOverlay)intOverlay; + IntPtr handle = overlay.Selector.GetWindowHandle(); + PreformAction(handle); + } + + public void PreformAction(IntPtr handle) + { + User32.ShowWindow(handle, User32.ShowWindowCommands.Maximize); + } + } +} diff --git a/VDash/Overlay/Actions/ResizeWindowAction.cs b/VDash/Overlay/Actions/ResizeWindowAction.cs new file mode 100644 index 0000000..444023f --- /dev/null +++ b/VDash/Overlay/Actions/ResizeWindowAction.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VDash.Utilities; + +namespace VDash.Overlay.Actions +{ + public class ResizeWindowAction : IOverlayAction + { + public int NewWidth { get; set; } + public int NewHeight { get; set; } + public bool Enabled { get; set; } + + public ResizeWindowAction(int newWidth, int newHeight) + { + NewWidth = newWidth; + NewHeight = newHeight; + } + + public void PreformAction(IOverlay intOverlay) + { + if (!Enabled) { return; } + if (!intOverlay.GetType().IsAssignableFrom(typeof(WindowOverlay))) + { + Console.WriteLine("This action cannot be performed on a non-WindowOverlay overlay."); + return; + } + + WindowOverlay overlay = (WindowOverlay)intOverlay; + IntPtr handle = overlay.Selector.GetWindowHandle(); + PreformAction(handle); + } + + public void PreformAction(IntPtr handle) + { + //Retain old rect + User32.Rect rect = new User32.Rect(); + User32.GetWindowRect(handle, ref rect); + + User32.SetWindowPos(handle, IntPtr.Zero, rect.left, rect.bottom, NewWidth, NewHeight, User32.SetWindowPosFlags.DoNotActivate | + User32.SetWindowPosFlags.IgnoreMove | User32.SetWindowPosFlags.IgnoreZOrder | + User32.SetWindowPosFlags.DoNotChangeOwnerZOrder); + } + } +} diff --git a/VDash/Overlay/Actions/RestoreWindowAction.cs b/VDash/Overlay/Actions/RestoreWindowAction.cs new file mode 100644 index 0000000..7e9e2e8 --- /dev/null +++ b/VDash/Overlay/Actions/RestoreWindowAction.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VDash.Utilities; + +namespace VDash.Overlay.Actions +{ + public class RestoreWindowAction : IOverlayAction + { + + public bool Enabled { get; set; } + + public void PreformAction(IOverlay intOverlay) + { + if (!Enabled) { return; } + if (!intOverlay.GetType().IsAssignableFrom(typeof(WindowOverlay))) + { + Console.WriteLine("This action cannot be performed on a non-WindowOverlay overlay."); + return; + } + + WindowOverlay overlay = (WindowOverlay)intOverlay; + IntPtr handle = overlay.Selector.GetWindowHandle(); + PreformAction(handle); + } + + public void PreformAction(IntPtr handle) + { + User32.ShowWindow(handle, User32.ShowWindowCommands.Restore); + } + } +} + diff --git a/VDash/Overlay/IOverlay.cs b/VDash/Overlay/IOverlay.cs index 74ddff0..4a3a34a 100644 --- a/VDash/Overlay/IOverlay.cs +++ b/VDash/Overlay/IOverlay.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using VDash.Overlay.Actions; namespace VDash.Overlay { @@ -7,13 +9,18 @@ namespace VDash.Overlay event EventHandler OverlayShown; event EventHandler OverlayHidden; - ulong Handle { get; } + List OverlayShownActions { get; } + List OverlayHiddenActions { get; } + + ulong OverlayHandle { get; } string Name { get; } string Key { get; } void Create(); void Destroy(); string GetOverlayName(); + float GetOverlayWidth(); + void SetOverlayWidth(float width); void SetOverlayName(string newName); } } diff --git a/VDash/Overlay/WindowOverlay.cs b/VDash/Overlay/WindowOverlay.cs index 7f12b94..6f3ca76 100644 --- a/VDash/Overlay/WindowOverlay.cs +++ b/VDash/Overlay/WindowOverlay.cs @@ -2,6 +2,7 @@ using SharpDX.Direct3D11; using SharpDX.DXGI; using System; +using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; @@ -9,8 +10,12 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms; using Valve.VR; +using VDash.Overlay.Actions; +using VDash.Utilities; using VDash.WindowSelectors; +using System.Linq; namespace VDash.Overlay { @@ -20,20 +25,21 @@ namespace VDash.Overlay public event EventHandler OverlayShown; public event EventHandler OverlayHidden; - public CVRSystem VRSystem { get; private set; } + public bool UpdateDashboardName { get; set; } = false; + public bool DestroyOnNoWindowFound { get; set; } = false; + public int CurrentFPS { get; private set; } + private Task m_EventPumpTask = null; + /// + /// Tracks whether or not this overlay is actually created. + /// bool m_Created = false; bool m_Running = true; - public bool UpdateDashboardName { get; set; } = false; - internal ulong m_Handle; - public ulong Handle - { - get - { - return m_Handle; - } - } + internal ulong m_OverlayHandle; + public ulong OverlayHandle { get { return m_OverlayHandle; } } + internal ulong m_ThumbnailIcon; + public ulong ThumbnailIcon { get { return m_ThumbnailIcon; } } internal string m_Key; public string Key { get { return m_Key; } } @@ -41,38 +47,64 @@ namespace VDash.Overlay internal string m_Name; public string Name { get { return m_Name; } } - IWindowSelector m_Selector = null; + + public IWindowSelector Selector { get; private set; } = null; + + public List OverlayShownActions { get; private set; } = new List(); + public List OverlayHiddenActions { get; private set; } = new List(); + + char[] specialKeys = { '+', '^', '%', '~', '(', ')', '{', '}', '[', ']' }; public WindowOverlay(string key, string name, IWindowSelector selector, bool updateDashboardName) { m_Key = key; m_Name = name; - m_Selector = selector; + Selector = selector; UpdateDashboardName = updateDashboardName; } + /// + /// Represents whether or not an external caller wants this to stay destroyed, really. + /// To keep the DestroyOnNoWindowFound stuff in check. Don't want it creating when it shouldn't. + /// + bool m_Destroyed = true; + public void Create() + { + m_Destroyed = false; + createInternal(); + } + + private void createInternal() { if (m_Created) { throw new Exception("This overlay has already been created."); } - //ConsoleManager.Show(); - ulong thumbHandle = OpenVR.k_ulOverlayHandleInvalid; + //Actually create the overlay var overlay = OpenVR.Overlay; - EVROverlayError err = overlay.CreateDashboardOverlay(m_Key, m_Name, ref m_Handle, ref thumbHandle); + EVROverlayError err = overlay.CreateDashboardOverlay(m_Key, m_Name, ref m_OverlayHandle, ref m_ThumbnailIcon); if (err != EVROverlayError.None) { throw new Exception($"Error creating overlay: {err}"); } - overlay.SetOverlayInputMethod(m_Handle, VROverlayInputMethod.Mouse); + overlay.SetOverlayInputMethod(m_OverlayHandle, VROverlayInputMethod.Mouse); m_Running = true; m_EventPumpTask = Task.Factory.StartNew(() => { EventPump(); }); m_Created = true; } + public bool IsCreated() { return m_Created; } + + public void Destroy() + { + m_Destroyed = true; + destroyInternal(); + } + + private void destroyInternal() { if (!m_Created) { throw new Exception("This object is not created."); } m_Created = false; @@ -83,19 +115,25 @@ namespace VDash.Overlay //Wait for render thread to exit m_EventPumpTask.Wait(); - var err = OpenVR.Overlay.DestroyOverlay(m_Handle); + var err = OpenVR.Overlay.DestroyOverlay(m_OverlayHandle); if( err != EVROverlayError.None) { throw new Exception($"Error destroying overlay: {err}"); } + + m_OverlayHandle = OpenVR.k_ulOverlayHandleInvalid; + m_ThumbnailIcon = OpenVR.k_ulOverlayHandleInvalid; } private void EventPump() { - #region Rendering setup var overlay = OpenVR.Overlay; + DateTime lastClick = DateTime.MinValue; + + #region Rendering setup + //Partially taken from here: https://stackoverflow.com/questions/6812068/c-sharp-which-is-the-fastest-way-to-take-a-screen-shot //Desktop Duplication didn't work well, hence is why we're sticking with GDI. Still this is faster than using SetTextureRaw. @@ -120,60 +158,107 @@ namespace VDash.Overlay int frames = 0; DateTime lastFps = DateTime.MinValue; DateTime lastNameCheck = DateTime.MinValue; + while (m_Running) { - //Update the SteamVR dash name if necessary, only once a second. - if (UpdateDashboardName && DateTime.Now.Subtract(lastNameCheck).TotalMilliseconds >= 1000) + if (DestroyOnNoWindowFound) { - var window = m_Selector.GetWindowInfo(); + //Check to see if the window actually exists. Act accordingly. + if (!Selector.WindowExists()) + { + if (IsCreated()) + { + destroyInternal(); + Console.WriteLine($"Destroyed {m_Key} because it's window does not exist."); + } + + Thread.Sleep(100); + continue; + } + else if(Selector.WindowExists() && !m_Created && !m_Destroyed) + { + createInternal(); + } + } + + //This technically should never happen, buuuut... + IntPtr windowHandle = IntPtr.Zero; + try + { + windowHandle = Selector.GetWindowHandle(); + } + catch (WindowNotFoundException) + { + } + + //Update the SteamVR dash name if necessary, only once a second. + if (Selector.WindowExists() && UpdateDashboardName && DateTime.Now.Subtract(lastNameCheck).TotalMilliseconds >= 1000) + { + var window = Selector.GetWindowInfo(); if(GetOverlayName() != window.Title) { SetOverlayName(window.Title); } lastNameCheck = DateTime.Now; + + + #region Thumbnail rendering + using (var icon = User32.GetAppIcon(windowHandle)) + { + using (var iconBmp = icon.ToBitmap()) + { + var data = iconBmp.LockBits(new Rectangle(0, 0, iconBmp.Width, iconBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + + overlay.SetOverlayRaw(m_ThumbnailIcon, data.Scan0, (uint)data.Width, (uint)data.Height, 4); + + iconBmp.UnlockBits(data); + } + } + #endregion } //Skip rendering if overlay isn't visible #if !DEBUG - if ((m_Handle == Openvr.k_uloverlayhandleinvalid) || overlay == null || (!overlay.isoverlayvisible(m_handle))) + if ((m_OverlayHandle == OpenVR.k_ulOverlayHandleInvalid) || overlay == null || (!overlay.IsOverlayVisible(m_OverlayHandle))) { - thread.sleep(100); + Thread.Sleep(100); continue; } #endif - IntPtr windowHandle = IntPtr.Zero; - try - { - windowHandle = m_Selector.GetWindowHandle(); - } - catch (WindowNotFoundException) - { - //Console.WriteLine($"[{m_Key}] {m_Name}: Window not found."); - } + #region VR Events VREvent_t vrEvent = new VREvent_t(); - while (OpenVR.Overlay.PollNextOverlayEvent(m_Handle, ref vrEvent, (uint)Marshal.SizeOf(vrEvent))) + while (OpenVR.Overlay.PollNextOverlayEvent(m_OverlayHandle, ref vrEvent, (uint)Marshal.SizeOf(vrEvent))) { switch ((EVREventType)vrEvent.eventType) { case EVREventType.VREvent_MouseMove: { + //Prevent the cursor from moving while we click. Super fkn annoying. + if(DateTime.Now.Subtract(lastClick).TotalMilliseconds < 400) + { + break; + } + //Make sure the window still exists if (windowHandle != IntPtr.Zero) { var vrX = vrEvent.data.mouse.x; var vrY = vrEvent.data.mouse.y; var rect = new User32.Rect(); - User32.GetWindowRect(windowHandle, ref rect); + + User32.GetClientRect(windowHandle, ref rect); int width = rect.right - rect.left; int height = rect.bottom - rect.top; + + var point = new System.Drawing.Point() { X = (int)vrX, Y = height - (int)vrY }; + - var point = new User32.POINT() { x = (int)vrX, y = height - (int)vrY }; var succ = User32.ClientToScreen(windowHandle, ref point); if (!succ) @@ -182,7 +267,7 @@ namespace VDash.Overlay } - succ = User32.SetCursorPos((int)point.x, (int)point.y); + succ = User32.SetCursorPos((int)point.X, (int)point.Y); if (!succ) { @@ -195,7 +280,6 @@ namespace VDash.Overlay case EVREventType.VREvent_MouseButtonDown: { var clickType = (EVRMouseButton)vrEvent.data.mouse.button; - Console.WriteLine($"Mouse button down: {clickType}"); var winClickType = User32.MouseEventFlags.MOUSEEVENTF_LEFTDOWN; @@ -219,13 +303,13 @@ namespace VDash.Overlay } User32.mouse_event(winClickType, 0, 0, 0, UIntPtr.Zero); + lastClick = DateTime.Now; break; } case EVREventType.VREvent_MouseButtonUp: { var clickType = (EVRMouseButton)vrEvent.data.mouse.button; - Console.WriteLine($"Mouse button up: {clickType}"); var winClickType = User32.MouseEventFlags.MOUSEEVENTF_LEFTUP; switch (clickType) @@ -248,16 +332,83 @@ namespace VDash.Overlay } User32.mouse_event(winClickType, 0, 0, 0, UIntPtr.Zero); + lastClick = DateTime.MinValue; break; } case EVREventType.VREvent_OverlayShown: { OverlayShown?.Invoke(this, new EventArgs()); + + if (Selector.WindowExists()) + { + foreach (var action in OverlayShownActions) + { + action.PreformAction(this); + } + } break; } case EVREventType.VREvent_OverlayHidden: { OverlayHidden?.Invoke(this, new EventArgs()); + + if (Selector.WindowExists()) + { + foreach (var action in OverlayHiddenActions) + { + action.PreformAction(this); + } + } + break; + } + case EVREventType.VREvent_KeyboardCharInput: + { + //Console.WriteLine($"Got char input: {vrEvent.data.keyboard.cNewInput}"); + var input = vrEvent.data.keyboard.cNewInput; + + //We should only be getting one keypress at a time. + switch (input) + { + case "\b": + { + SendKeys.SendWait("{BACKSPACE}"); + break; + } + case "\n": + { + SendKeys.SendWait("{ENTER}"); + break; + } + default: + { + if (specialKeys.Contains(input[0])) + { + SendKeys.SendWait("{" + input + "}"); + } + else + { + SendKeys.SendWait(input); + } + break; + } + } + + break; + } + case EVREventType.VREvent_ButtonPress: + { + EVRButtonId button = (EVRButtonId)vrEvent.data.controller.button; + //Console.WriteLine($"Button pressed: {button}"); + + switch (button) + { + case EVRButtonId.k_EButton_Knuckles_A: + { + overlay.ShowKeyboardForOverlay(m_OverlayHandle, (int)EGamepadTextInputMode.k_EGamepadTextInputModeNormal, + (int)EGamepadTextInputLineMode.k_EGamepadTextInputLineModeSingleLine, "desc?", 1024, "", true, 0); + break; + } + } break; } } @@ -265,162 +416,166 @@ namespace VDash.Overlay } #endregion -#region Acutally Render - - if (windowHandle != IntPtr.Zero) + #region Acutally Render + //Redundant check, but w/e + if (Selector.WindowExists() && windowHandle != IntPtr.Zero) { var rect = new User32.Rect(); - User32.GetWindowRect(windowHandle, ref rect); + User32.GetClientRect(windowHandle, ref rect); + System.Drawing.Point pnt = new Point { X = 0, Y = 0 }; + + User32.ClientToScreen(windowHandle, ref pnt); int width = rect.right - rect.left; int height = rect.bottom - rect.top; - - - using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb)) + //Make sure the coords are actually valid. Could be minimized or something. + if (width > 0 && height > 0) { - using (Graphics graphics = Graphics.FromImage(bmp)) + using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb)) { - graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); - - //Draw cursor - User32.CURSORINFO pci; - pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(User32.CURSORINFO)); - - if (User32.GetCursorInfo(out pci)) + using (Graphics graphics = Graphics.FromImage(bmp)) { + graphics.CopyFromScreen((int)pnt.X, (int)pnt.Y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); - User32.POINT cursorPoint = new User32.POINT() { x = pci.ptScreenPos.X, y = pci.ptScreenPos.Y}; + #region Cursor drawing + User32.CURSORINFO pci; + pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(User32.CURSORINFO)); - if (User32.ScreenToClient(windowHandle, ref cursorPoint)) + if (User32.GetCursorInfo(out pci)) { - if (pci.flags == User32.CURSOR_SHOWING) + System.Drawing.Point cursorPoint = new System.Drawing.Point() { X = pci.ptScreenPos.X, Y = pci.ptScreenPos.Y }; + + if (User32.ScreenToClient(windowHandle, ref cursorPoint)) { - User32.DrawIcon(graphics.GetHdc(), (int)cursorPoint.x, (int)cursorPoint.y, pci.hCursor); - graphics.ReleaseHdc(); + + if (pci.flags == User32.CURSOR_SHOWING) + { + User32.DrawIcon(graphics.GetHdc(), (int)cursorPoint.X, (int)cursorPoint.Y, pci.hCursor); + graphics.ReleaseHdc(); + } + } + else + { + Console.WriteLine("Error getting coords to draw cursor."); } } else { - Console.WriteLine("Error getting coords to draw cursor."); + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + Console.WriteLine($"Error getting cursor info: {errorMessage}"); } - } - else - { - string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; - Console.WriteLine($"Error getting cursor info: {errorMessage}"); - } + #endregion + var data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); - var data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); - - - if (screenTexture == null || (lastWidth != data.Width || lastHeight != data.Height)) - { - lastWidth = data.Width; - lastHeight = data.Height; - - if (screenTexture != null) + if (screenTexture == null || (lastWidth != data.Width || lastHeight != data.Height)) { - screenTexture.Dispose(); + lastWidth = data.Width; + lastHeight = data.Height; - //Dispose not freeing screenTexture: https://gamedev.stackexchange.com/questions/93311/freeing-texture-memory-in-sharpdx-what-am-i-missing - device.ImmediateContext.ClearState(); - device.ImmediateContext.Flush(); - } - - var textureDesc = new Texture2DDescription - { - CpuAccessFlags = CpuAccessFlags.Write, - BindFlags = BindFlags.ShaderResource, - Format = Format.B8G8R8A8_UNorm, - //Format = Format.B8G8R8A8_UNorm_SRgb, - Width = data.Width, - Height = data.Height, - OptionFlags = ResourceOptionFlags.None, - MipLevels = 1, - ArraySize = 1, - SampleDescription = { Count = 1, Quality = 0 }, - Usage = ResourceUsage.Dynamic - }; - screenTexture = new Texture2D(device, textureDesc); - stream = new DataStream(4 * width * height, true, true); - - overlay.SetOverlayWidthInMeters(m_Handle, 2.5f); - - HmdVector2_t vecWindowSize = new HmdVector2_t() { v0 = width, v1 = height }; - OpenVR.Overlay.SetOverlayMouseScale(m_Handle, ref vecWindowSize); - Console.WriteLine("Updated!"); - - - } - //Render using graphics card, in DirectX context. - - //using () - { - var screenView = new ShaderResourceView(device, screenTexture); - device.ImmediateContext.PixelShader.SetShaderResource(0, screenView); - - var dr = new SharpDX.DataRectangle(data.Scan0, data.Stride); - - DataBox databox = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.WriteDiscard, - SharpDX.Direct3D11.MapFlags.None, out stream); - - - - if (!databox.IsEmpty) - { - int count = 4 * data.Width * data.Height; - - //Stride compensation. Sometimes the BMP and DX texture strides are different. This fixes it. - if (databox.RowPitch == data.Stride) + if (screenTexture != null) { - stream.Write(data.Scan0, 0, count); + screenTexture.Dispose(); + + //Dispose not freeing screenTexture: https://gamedev.stackexchange.com/questions/93311/freeing-texture-memory-in-sharpdx-what-am-i-missing + device.ImmediateContext.ClearState(); + device.ImmediateContext.Flush(); } - //Truncate the extra BMP bytes. - else if (databox.RowPitch < data.Stride) + + var textureDesc = new Texture2DDescription { - for (int i = 0; i < data.Height; i++) + CpuAccessFlags = CpuAccessFlags.Write, + BindFlags = BindFlags.ShaderResource, + Format = Format.B8G8R8A8_UNorm, + //Format = Format.B8G8R8A8_UNorm_SRgb, + Width = data.Width, + Height = data.Height, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Dynamic + }; + screenTexture = new Texture2D(device, textureDesc); + stream = new DataStream(4 * width * height, true, true); + + overlay.SetOverlayWidthInMeters(m_OverlayHandle, 2.5f); + + HmdVector2_t vecWindowSize = new HmdVector2_t() { v0 = data.Width, v1 = data.Height }; + OpenVR.Overlay.SetOverlayMouseScale(m_OverlayHandle, ref vecWindowSize); + Console.WriteLine("Updated!"); + + + } + //Render using graphics card, in DirectX context. + + //using () + { + var screenView = new ShaderResourceView(device, screenTexture); + device.ImmediateContext.PixelShader.SetShaderResource(0, screenView); + + var dr = new SharpDX.DataRectangle(data.Scan0, data.Stride); + + DataBox databox = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.WriteDiscard, + SharpDX.Direct3D11.MapFlags.None, out stream); + + + + if (!databox.IsEmpty) + { + int count = 4 * data.Width * data.Height; + + //Stride compensation. Sometimes the BMP and DX texture strides are different. This fixes it. + if (databox.RowPitch == data.Stride) { - stream.Write(data.Scan0, (data.Stride * i), databox.RowPitch); + stream.Write(data.Scan0, 0, count); + } + //Truncate the extra BMP bytes. + else if (databox.RowPitch < data.Stride) + { + for (int i = 0; i < data.Height; i++) + { + stream.Write(data.Scan0, (data.Stride * i), databox.RowPitch); + } + } + //Add some junk on empty bytes on the end to make everyone happy. + else if (databox.RowPitch > data.Stride) + { + int difference = databox.RowPitch - data.Stride; + byte[] junk = new byte[difference]; + for (int i = 0; i < data.Height; i++) + { + stream.Write(data.Scan0, (data.Stride * i), data.Stride); + stream.Write(junk, 0, difference); + } } } - //Add some junk on empty bytes on the end to make everyone happy. - else if(databox.RowPitch > data.Stride) + + + VRTextureBounds_t bounds = new VRTextureBounds_t(); + OpenVR.Overlay.GetOverlayTextureBounds(m_OverlayHandle, ref bounds); + + var texture = new Texture_t(); + texture.handle = screenTexture.NativePointer; + texture.eType = ETextureType.DirectX; + texture.eColorSpace = EColorSpace.Auto; + EVROverlayError result = overlay.SetOverlayTexture(m_OverlayHandle, ref texture); + if (result != EVROverlayError.None) { - int difference = databox.RowPitch - data.Stride; - byte[] junk = new byte[difference]; - for (int i = 0; i < data.Height; i++) - { - stream.Write(data.Scan0, (data.Stride * i), data.Stride); - stream.Write(junk, 0, difference); - } + + Console.WriteLine($"Error rendering: {result}"); } + + device.ImmediateContext.UnmapSubresource(screenTexture, 0); + + + + frames++; + + bmp.UnlockBits(data); } - - - VRTextureBounds_t bounds = new VRTextureBounds_t(); - OpenVR.Overlay.GetOverlayTextureBounds(m_Handle, ref bounds); - - var texture = new Texture_t(); - texture.handle = screenTexture.NativePointer; - texture.eType = ETextureType.DirectX; - texture.eColorSpace = EColorSpace.Auto; - EVROverlayError result = overlay.SetOverlayTexture(m_Handle, ref texture); - if (result != EVROverlayError.None) - { - - Console.WriteLine($"Error rendering: {result}"); - } - - device.ImmediateContext.UnmapSubresource(screenTexture, 0); - - - - frames++; - - bmp.UnlockBits(data); } } } @@ -432,83 +587,24 @@ namespace VDash.Overlay if (DateTime.Now.Subtract(lastFps).TotalMilliseconds > 1000) { //Console.WriteLine($"{frames} fps."); + CurrentFPS = frames; frames = 0; lastFps = DateTime.Now; } } //Clean up after we exit while loop. + screenTexture.Dispose(); + device.ImmediateContext.ClearState(); + device.ImmediateContext.Flush(); + + CurrentFPS = 0; + device.Dispose(); adapter.Dispose(); factory.Dispose(); } - private class User32 - { - [StructLayout(LayoutKind.Sequential)] - public struct Rect - { - public int left; - public int top; - public int right; - public int bottom; - } - - [StructLayout(LayoutKind.Sequential)] - public struct POINT - { - public long x; - public long y; - } - - [StructLayout(LayoutKind.Sequential)] - public struct CURSORINFO - { - public Int32 cbSize; - public Int32 flags; - public IntPtr hCursor; - public Point ptScreenPos; - } - public const Int32 CURSOR_SHOWING = 0x00000001; - - [DllImport("user32.dll")] - public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetCursorPos(int x, int y); - - [DllImport("user32.dll")] - public static extern bool ClientToScreen(IntPtr hwnd, ref POINT lpPoint); - - [DllImport("user32.dll")] - public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); - - [DllImport("user32.dll")] - public static extern bool GetCursorInfo(out CURSORINFO pci); - - [DllImport("user32.dll")] - public static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); - - [DllImport("user32.dll")] - public static extern void mouse_event(MouseEventFlags dwFlags, int dx, int dy, uint dwData, UIntPtr dwExtraInfo); - public enum MouseEventFlags - { - MOUSEEVENTF_ABSOLUTE = 0x8000, - MOUSEEVENTF_LEFTDOWN = 0x0002, - MOUSEEVENTF_LEFTUP = 0x0004, - MOUSEEVENTF_MIDDLEDOWN = 0x0020, - MOUSEEVENTF_MIDDLEUP = 0x0040, - MOUSEEVENTF_MOVE = 0x0001, - MOUSEEVENTF_RIGHTDOWN = 0x0008, - MOUSEEVENTF_RIGHTUP = 0x0010, - MOUSEEVENTF_XDOWN = 0x0080, - MOUSEEVENTF_XUP = 0x0100, - MOUSEEVENTF_WHEEL = 0x0800, - MOUSEEVENTF_HWHEEL = 0x01000 - } - } - public void Dispose() { Destroy(); @@ -518,14 +614,26 @@ namespace VDash.Overlay { EVROverlayError err = EVROverlayError.None; StringBuilder sb = new StringBuilder(255); - uint res = OpenVR.Overlay.GetOverlayName(m_Handle, sb, 255, ref err); + uint res = OpenVR.Overlay.GetOverlayName(m_OverlayHandle, sb, 255, ref err); return sb.ToString(); } public void SetOverlayName(string newName) { - OpenVR.Overlay.SetOverlayName(m_Handle, newName); + OpenVR.Overlay.SetOverlayName(m_OverlayHandle, newName); m_Name = newName; } + + public float GetOverlayWidth() + { + float width = 0f; + OpenVR.Overlay.GetOverlayWidthInMeters(m_OverlayHandle, ref width); + return width; + } + + public void SetOverlayWidth(float width) + { + OpenVR.Overlay.SetOverlayWidthInMeters(m_OverlayHandle, width); + } } } diff --git a/VDash/Utilities/ConsoleManager.cs b/VDash/Utilities/ConsoleManager.cs new file mode 100644 index 0000000..c68bc99 --- /dev/null +++ b/VDash/Utilities/ConsoleManager.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; + +[SuppressUnmanagedCodeSecurity] +public static class ConsoleManager +{ + private const string Kernel32_DllName = "kernel32.dll"; + + [DllImport(Kernel32_DllName)] + private static extern bool AllocConsole(); + + [DllImport(Kernel32_DllName)] + private static extern bool FreeConsole(); + + [DllImport(Kernel32_DllName)] + private static extern IntPtr GetConsoleWindow(); + + [DllImport(Kernel32_DllName)] + private static extern int GetConsoleOutputCP(); + + public static bool HasConsole + { + get { return GetConsoleWindow() != IntPtr.Zero; } + } + + /// + /// Creates a new console instance if the process is not attached to a console already. + /// + public static void Show() + { + //#if DEBUG + if (!HasConsole) + { + AllocConsole(); + InvalidateOutAndError(); + } + //#endif + } + + /// + /// If the process has a console attached to it, it will be detached and no longer visible. Writing to the System.Console is still possible, but no output will be shown. + /// + public static void Hide() + { + //#if DEBUG + if (HasConsole) + { + SetOutAndErrorNull(); + FreeConsole(); + } + //#endif + } + + public static void Toggle() + { + if (HasConsole) + { + Hide(); + } + else + { + Show(); + } + } + + static void InvalidateOutAndError() + { + Type type = typeof(System.Console); + + System.Reflection.FieldInfo _out = type.GetField("_out", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + System.Reflection.FieldInfo _error = type.GetField("_error", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + System.Reflection.MethodInfo _InitializeStdOutError = type.GetMethod("InitializeStdOutError", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + Debug.Assert(_out != null); + Debug.Assert(_error != null); + + Debug.Assert(_InitializeStdOutError != null); + + _out.SetValue(null, null); + _error.SetValue(null, null); + + _InitializeStdOutError.Invoke(null, new object[] { true }); + } + + static void SetOutAndErrorNull() + { + Console.SetOut(TextWriter.Null); + Console.SetError(TextWriter.Null); + } +} \ No newline at end of file diff --git a/VDash/Utilities/User32.cs b/VDash/Utilities/User32.cs new file mode 100644 index 0000000..40a78a3 --- /dev/null +++ b/VDash/Utilities/User32.cs @@ -0,0 +1,596 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace VDash.Utilities +{ + + public class User32 + { + [StructLayout(LayoutKind.Sequential)] + public struct Rect + { + public int left; + public int top; + public int right; + public int bottom; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public Point ptScreenPos; + } + public const Int32 CURSOR_SHOWING = 0x00000001; + + [DllImport("user32.dll")] + public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect); + + [DllImport("user32.dll")] + public static extern bool GetClientRect(IntPtr hWnd, ref Rect lpRect); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetCursorPos(int x, int y); + + [DllImport("user32.dll")] + public static extern bool ClientToScreen(IntPtr hwnd, ref System.Drawing.Point lpPoint); + + [DllImport("user32.dll")] + public static extern bool ScreenToClient(IntPtr hWnd, ref System.Drawing.Point lpPoint); + + [DllImport("user32.dll")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + public static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + [DllImport("user32.dll")] + public static extern void mouse_event(MouseEventFlags dwFlags, int dx, int dy, uint dwData, UIntPtr dwExtraInfo); + public enum MouseEventFlags + { + MOUSEEVENTF_ABSOLUTE = 0x8000, + MOUSEEVENTF_LEFTDOWN = 0x0002, + MOUSEEVENTF_LEFTUP = 0x0004, + MOUSEEVENTF_MIDDLEDOWN = 0x0020, + MOUSEEVENTF_MIDDLEUP = 0x0040, + MOUSEEVENTF_MOVE = 0x0001, + MOUSEEVENTF_RIGHTDOWN = 0x0008, + MOUSEEVENTF_RIGHTUP = 0x0010, + MOUSEEVENTF_XDOWN = 0x0080, + MOUSEEVENTF_XUP = 0x0100, + MOUSEEVENTF_WHEEL = 0x0800, + MOUSEEVENTF_HWHEEL = 0x01000 + } + + public const int GCL_HICONSM = -34; + public const int GCL_HICON = -14; + + public const int ICON_SMALL = 0; + public const int ICON_BIG = 1; + public const int ICON_SMALL2 = 2; + + public const int WM_GETICON = 0x7F; + + public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) + { + if (IntPtr.Size > 4) + return GetClassLongPtr64(hWnd, nIndex); + else + return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); + } + + [DllImport("user32.dll", EntryPoint = "GetClassLong")] + public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] + public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + public static Icon GetAppIcon(IntPtr hwnd) + { + IntPtr iconHandle = SendMessage(hwnd, WM_GETICON, ICON_SMALL2, 0); + if (iconHandle == IntPtr.Zero) + iconHandle = SendMessage(hwnd, WM_GETICON, ICON_SMALL, 0); + if (iconHandle == IntPtr.Zero) + iconHandle = SendMessage(hwnd, WM_GETICON, ICON_BIG, 0); + if (iconHandle == IntPtr.Zero) + iconHandle = GetClassLongPtr(hwnd, GCL_HICON); + if (iconHandle == IntPtr.Zero) + iconHandle = GetClassLongPtr(hwnd, GCL_HICONSM); + + if (iconHandle == IntPtr.Zero) + return null; + + Icon icn = Icon.FromHandle(iconHandle); + + return icn; + } + + [Flags()] + public enum SetWindowPosFlags : uint + { + /// If the calling thread and the thread that owns the window are attached to different input queues, + /// the system posts the request to the thread that owns the window. This prevents the calling thread from + /// blocking its execution while other threads process the request. + /// SWP_ASYNCWINDOWPOS + AsynchronousWindowPosition = 0x4000, + /// Prevents generation of the WM_SYNCPAINT message. + /// SWP_DEFERERASE + DeferErase = 0x2000, + /// Draws a frame (defined in the window's class description) around the window. + /// SWP_DRAWFRAME + DrawFrame = 0x0020, + /// Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to + /// the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE + /// is sent only when the window's size is being changed. + /// SWP_FRAMECHANGED + FrameChanged = 0x0020, + /// Hides the window. + /// SWP_HIDEWINDOW + HideWindow = 0x0080, + /// Does not activate the window. If this flag is not set, the window is activated and moved to the + /// top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter + /// parameter). + /// SWP_NOACTIVATE + DoNotActivate = 0x0010, + /// Discards the entire contents of the client area. If this flag is not specified, the valid + /// contents of the client area are saved and copied back into the client area after the window is sized or + /// repositioned. + /// SWP_NOCOPYBITS + DoNotCopyBits = 0x0100, + /// Retains the current position (ignores X and Y parameters). + /// SWP_NOMOVE + IgnoreMove = 0x0002, + /// Does not change the owner window's position in the Z order. + /// SWP_NOOWNERZORDER + DoNotChangeOwnerZOrder = 0x0200, + /// Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to + /// the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent + /// window uncovered as a result of the window being moved. When this flag is set, the application must + /// explicitly invalidate or redraw any parts of the window and parent window that need redrawing. + /// SWP_NOREDRAW + DoNotRedraw = 0x0008, + /// Same as the SWP_NOOWNERZORDER flag. + /// SWP_NOREPOSITION + DoNotReposition = 0x0200, + /// Prevents the window from receiving the WM_WINDOWPOSCHANGING message. + /// SWP_NOSENDCHANGING + DoNotSendChangingEvent = 0x0400, + /// Retains the current size (ignores the cx and cy parameters). + /// SWP_NOSIZE + IgnoreResize = 0x0001, + /// Retains the current Z order (ignores the hWndInsertAfter parameter). + /// SWP_NOZORDER + IgnoreZOrder = 0x0004, + /// Displays the window. + /// SWP_SHOWWINDOW + ShowWindow = 0x0040, + } + + /// + /// Changes the size, position, and Z order of a child, pop-up, or top-level window. These windows are ordered + /// according to their appearance on the screen. The topmost window receives the highest rank and is the first window + /// in the Z order. + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545%28v=vs.85%29.aspx for more information. + /// + /// C++ ( hWnd [in]. Type: HWND )
A handle to the window. + /// + /// C++ ( hWndInsertAfter [in, optional]. Type: HWND )
A handle to the window to precede the positioned window in + /// the Z order. This parameter must be a window handle or one of the following values. + /// + /// + /// HWND placementWindow to precede placement + /// + /// + /// HWND_BOTTOM ((HWND)1) + /// + /// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost + /// window, the window loses its topmost status and is placed at the bottom of all other windows. + /// + /// + /// + /// HWND_NOTOPMOST ((HWND)-2) + /// + /// Places the window above all non-topmost windows (that is, behind all topmost windows). This + /// flag has no effect if the window is already a non-topmost window. + /// + /// + /// + /// HWND_TOP ((HWND)0)Places the window at the top of the Z order. + /// + /// + /// HWND_TOPMOST ((HWND)-1) + /// + /// Places the window above all non-topmost windows. The window maintains its topmost position + /// even when it is deactivated. + /// + /// + /// + /// For more information about how this parameter is used, see the following Remarks section. + /// + /// C++ ( X [in]. Type: int )
The new position of the left side of the window, in client coordinates. + /// C++ ( Y [in]. Type: int )
The new position of the top of the window, in client coordinates. + /// C++ ( cx [in]. Type: int )
The new width of the window, in pixels. + /// C++ ( cy [in]. Type: int )
The new height of the window, in pixels. + /// + /// C++ ( uFlags [in]. Type: UINT )
The window sizing and positioning flags. This parameter can be a combination + /// of the following values. + /// + /// + /// HWND sizing and positioning flags + /// Where to place and size window. Can be a combination of any + /// + /// + /// SWP_ASYNCWINDOWPOS (0x4000) + /// + /// If the calling thread and the thread that owns the window are attached to different input + /// queues, the system posts the request to the thread that owns the window. This prevents the calling + /// thread from blocking its execution while other threads process the request. + /// + /// + /// + /// SWP_DEFERERASE (0x2000) + /// Prevents generation of the WM_SYNCPAINT message. + /// + /// + /// SWP_DRAWFRAME (0x0020) + /// Draws a frame (defined in the window's class description) around the window. + /// + /// + /// SWP_FRAMECHANGED (0x0020) + /// + /// Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message + /// to the window, even if the window's size is not being changed. If this flag is not specified, + /// WM_NCCALCSIZE is sent only when the window's size is being changed + /// + /// + /// + /// SWP_HIDEWINDOW (0x0080)Hides the window. + /// + /// + /// SWP_NOACTIVATE (0x0010) + /// + /// Does not activate the window. If this flag is not set, the window is activated and moved to + /// the top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter + /// parameter). + /// + /// + /// + /// SWP_NOCOPYBITS (0x0100) + /// + /// Discards the entire contents of the client area. If this flag is not specified, the valid + /// contents of the client area are saved and copied back into the client area after the window is sized or + /// repositioned. + /// + /// + /// + /// SWP_NOMOVE (0x0002) + /// Retains the current position (ignores X and Y parameters). + /// + /// + /// SWP_NOOWNERZORDER (0x0200) + /// Does not change the owner window's position in the Z order. + /// + /// + /// SWP_NOREDRAW (0x0008) + /// + /// Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies + /// to the client area, the nonclient area (including the title bar and scroll bars), and any part of the + /// parent window uncovered as a result of the window being moved. When this flag is set, the application + /// must explicitly invalidate or redraw any parts of the window and parent window that need redrawing. + /// + /// + /// + /// SWP_NOREPOSITION (0x0200)Same as the SWP_NOOWNERZORDER flag. + /// + /// + /// SWP_NOSENDCHANGING (0x0400) + /// Prevents the window from receiving the WM_WINDOWPOSCHANGING message. + /// + /// + /// SWP_NOSIZE (0x0001) + /// Retains the current size (ignores the cx and cy parameters). + /// + /// + /// SWP_NOZORDER (0x0004) + /// Retains the current Z order (ignores the hWndInsertAfter parameter). + /// + /// + /// SWP_SHOWWINDOW (0x0040)Displays the window. + /// + /// + /// + /// true or nonzero if the function succeeds, false or zero otherwise or if function fails. + /// + /// + /// As part of the Vista re-architecture, all services were moved off the interactive desktop into Session 0. + /// hwnd and window manager operations are only effective inside a session and cross-session attempts to manipulate + /// the hwnd will fail. For more information, see The Windows Vista Developer Story: Application Compatibility + /// Cookbook. + /// + /// + /// If you have changed certain window data using SetWindowLong, you must call SetWindowPos for the changes to + /// take effect. Use the following combination for uFlags: SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + /// SWP_FRAMECHANGED. + /// + /// + /// A window can be made a topmost window either by setting the hWndInsertAfter parameter to HWND_TOPMOST and + /// ensuring that the SWP_NOZORDER flag is not set, or by setting a window's position in the Z order so that it is + /// above any existing topmost windows. When a non-topmost window is made topmost, its owned windows are also made + /// topmost. Its owners, however, are not changed. + /// + /// + /// If neither the SWP_NOACTIVATE nor SWP_NOZORDER flag is specified (that is, when the application requests that + /// a window be simultaneously activated and its position in the Z order changed), the value specified in + /// hWndInsertAfter is used only in the following circumstances. + /// + /// + /// Neither the HWND_TOPMOST nor HWND_NOTOPMOST flag is specified in hWndInsertAfter. + /// The window identified by hWnd is not the active window. + /// + /// + /// An application cannot activate an inactive window without also bringing it to the top of the Z order. + /// Applications can change an activated window's position in the Z order without restrictions, or it can activate + /// a window and then move it to the top of the topmost or non-topmost windows. + /// + /// + /// If a topmost window is repositioned to the bottom (HWND_BOTTOM) of the Z order or after any non-topmost + /// window, it is no longer topmost. When a topmost window is made non-topmost, its owners and its owned windows + /// are also made non-topmost windows. + /// + /// + /// A non-topmost window can own a topmost window, but the reverse cannot occur. Any window (for example, a + /// dialog box) owned by a topmost window is itself made a topmost window, to ensure that all owned windows stay + /// above their owner. + /// + /// + /// If an application is not in the foreground, and should be in the foreground, it must call the + /// SetForegroundWindow function. + /// + /// + /// To use SetWindowPos to bring a window to the top, the process that owns the window must have + /// SetForegroundWindow permission. + /// + /// + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags); + + /// + /// Brings the thread that created the specified window into the foreground and activates the window. Keyboard input is + /// directed to the window, and various visual cues are changed for the user. The system assigns a slightly higher + /// priority to the thread that created the foreground window than it does to other threads. + /// See for https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539%28v=vs.85%29.aspx more information. + /// + /// + /// C++ ( hWnd [in]. Type: HWND )
A handle to the window that should be activated and brought to the foreground. + /// + /// + /// true or nonzero if the window was brought to the foreground, false or zero If the window was not + /// brought to the foreground. + /// + /// + /// The system restricts which processes can set the foreground window. A process can set the foreground window only if + /// one of the following conditions is true: + /// + /// + /// Conditions + /// + /// The process is the foreground process. + /// The process was started by the foreground process. + /// The process received the last input event. + /// There is no foreground process. + /// The process is being debugged. + /// The foreground process is not a Modern Application or the Start Screen. + /// The foreground is not locked (see LockSetForegroundWindow). + /// The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo). + /// No menus are active. + /// + /// + /// An application cannot force a window to the foreground while the user is working with another window. + /// Instead, Windows flashes the taskbar button of the window to notify the user. + /// + /// + /// A process that can set the foreground window can enable another process to set the foreground window by + /// calling the AllowSetForegroundWindow function. The process specified by dwProcessId loses the ability to set + /// the foreground window the next time the user generates input, unless the input is directed at that process, or + /// the next time a process calls AllowSetForegroundWindow, unless that process is specified. + /// + /// + /// The foreground process can disable calls to SetForegroundWindow by calling the LockSetForegroundWindow + /// function. + /// + /// + // For Windows Mobile, replace user32.dll with coredll.dll + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + /// + /// Retrieves the show state and the restored, minimized, and maximized positions of the specified window. + /// + /// + /// A handle to the window. + /// + /// + /// A pointer to the WINDOWPLACEMENT structure that receives the show state and position information. + /// + /// Before calling GetWindowPlacement, set the length member to sizeof(WINDOWPLACEMENT). GetWindowPlacement fails if lpwndpl-> length is not set correctly. + /// + /// + /// + /// If the function succeeds, the return value is nonzero. + /// + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); + + /// + /// Sets the show state and the restored, minimized, and maximized positions of the specified window. + /// + /// + /// A handle to the window. + /// + /// + /// A pointer to a WINDOWPLACEMENT structure that specifies the new show state and window positions. + /// + /// Before calling SetWindowPlacement, set the length member of the WINDOWPLACEMENT structure to sizeof(WINDOWPLACEMENT). SetWindowPlacement fails if the length member is not set correctly. + /// + /// + /// + /// If the function succeeds, the return value is nonzero. + /// + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl); + + /// + /// Contains information about the placement of a window on the screen. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPLACEMENT + { + /// + /// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT). + /// + /// GetWindowPlacement and SetWindowPlacement fail if this member is not set correctly. + /// + /// + public int Length; + + /// + /// Specifies flags that control the position of the minimized window and the method by which the window is restored. + /// + public int Flags; + + /// + /// The current show state of the window. + /// + public ShowWindowCommands ShowCmd; + + /// + /// The coordinates of the window's upper-left corner when the window is minimized. + /// + public Point MinPosition; + + /// + /// The coordinates of the window's upper-left corner when the window is maximized. + /// + public Point MaxPosition; + + /// + /// The window's coordinates when the window is in the restored position. + /// + public Rect NormalPosition; + + /// + /// Gets the default (empty) value. + /// + public static WINDOWPLACEMENT Default + { + get + { + WINDOWPLACEMENT result = new WINDOWPLACEMENT(); + result.Length = Marshal.SizeOf(result); + return result; + } + } + } + + public enum ShowWindowCommands + { + /// + /// Hides the window and activates another window. + /// + Hide = 0, + /// + /// Activates and displays a window. If the window is minimized or + /// maximized, the system restores it to its original size and position. + /// An application should specify this flag when displaying the window + /// for the first time. + /// + Normal = 1, + /// + /// Activates the window and displays it as a minimized window. + /// + ShowMinimized = 2, + /// + /// Maximizes the specified window. + /// + Maximize = 3, // is this the right value? + /// + /// Activates the window and displays it as a maximized window. + /// + ShowMaximized = 3, + /// + /// Displays a window in its most recent size and position. This value + /// is similar to , except + /// the window is not activated. + /// + ShowNoActivate = 4, + /// + /// Activates the window and displays it in its current size and position. + /// + Show = 5, + /// + /// Minimizes the specified window and activates the next top-level + /// window in the Z order. + /// + Minimize = 6, + /// + /// Displays the window as a minimized window. This value is similar to + /// , except the + /// window is not activated. + /// + ShowMinNoActive = 7, + /// + /// Displays the window in its current size and position. This value is + /// similar to , except the + /// window is not activated. + /// + ShowNA = 8, + /// + /// Activates and displays the window. If the window is minimized or + /// maximized, the system restores it to its original size and position. + /// An application should specify this flag when restoring a minimized window. + /// + Restore = 9, + /// + /// Sets the show state based on the SW_* value specified in the + /// STARTUPINFO structure passed to the CreateProcess function by the + /// program that started the application. + /// + ShowDefault = 10, + /// + /// Windows 2000/XP: Minimizes a window, even if the thread + /// that owns the window is not responding. This flag should only be + /// used when minimizing windows from a different thread. + /// + ForceMinimize = 11 + } + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsWindow(IntPtr hWnd); + } +} diff --git a/VDash/VDash.csproj b/VDash/VDash.csproj index e222d45..ba4a362 100644 --- a/VDash/VDash.csproj +++ b/VDash/VDash.csproj @@ -66,6 +66,7 @@ + @@ -84,7 +85,13 @@ MSBuild:Compile Designer + + + + + + @@ -96,7 +103,7 @@ App.xaml Code - + MainWindow.xaml Code diff --git a/VDash/WindowSelectors/FromProcessName.cs b/VDash/WindowSelectors/FromProcessName.cs index 1685e90..abc0290 100644 --- a/VDash/WindowSelectors/FromProcessName.cs +++ b/VDash/WindowSelectors/FromProcessName.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Linq; +using VDash.Utilities; namespace VDash.WindowSelectors { @@ -17,9 +18,15 @@ namespace VDash.WindowSelectors m_ProcName = procName; } + Process[] processes = null; public Process[] GetProcesses() { - return Process.GetProcessesByName(m_ProcName); + if(processes == null) + { + processes = Process.GetProcessesByName(m_ProcName); + } + + return processes; } public Process GetProcess() @@ -46,5 +53,10 @@ namespace VDash.WindowSelectors } return ret; } + + public bool WindowExists() + { + return User32.IsWindow(GetWindowHandle()); + } } } diff --git a/VDash/WindowSelectors/FromWindowName.cs b/VDash/WindowSelectors/FromWindowName.cs index 39af695..dba5b31 100644 --- a/VDash/WindowSelectors/FromWindowName.cs +++ b/VDash/WindowSelectors/FromWindowName.cs @@ -80,6 +80,19 @@ namespace VDash.WindowSelectors return Window; } + public bool WindowExists() + { + try + { + var window = Window; + } + catch (WindowNotFoundException) + { + return false; + } + return true; + } + public enum TitleSearchType { Contains = 0, diff --git a/VDash/WindowSelectors/IWindowSelector.cs b/VDash/WindowSelectors/IWindowSelector.cs index b38dc8e..31cb161 100644 --- a/VDash/WindowSelectors/IWindowSelector.cs +++ b/VDash/WindowSelectors/IWindowSelector.cs @@ -9,6 +9,7 @@ namespace VDash.WindowSelectors { IntPtr GetWindowHandle(); WindowInfo GetWindowInfo(); + bool WindowExists(); } public class WindowNotFoundException : Exception @@ -53,6 +54,10 @@ namespace VDash.WindowSelectors return Title; } + /// + /// Gets a list of all windows in the current desktop session. + /// + /// public static WindowInfo[] GetWindowList() { List windows = new List();