From 945c286fb29192271404b879655d9f6e94506e16 Mon Sep 17 00:00:00 2001 From: jrhodnik Date: Sat, 25 May 2019 15:39:18 -0400 Subject: [PATCH] Added Monitor overlay. --- VDash/MainWindow.xaml.cs | 29 +- VDash/Overlay/MonitorOverlay.cs | 560 ++++++++++++++++++++++++++++++++ VDash/Overlay/WindowOverlay.cs | 2 +- VDash/Utilities/User32.cs | 145 +++++++++ VDash/VDash.csproj | 1 + 5 files changed, 726 insertions(+), 11 deletions(-) create mode 100644 VDash/Overlay/MonitorOverlay.cs diff --git a/VDash/MainWindow.xaml.cs b/VDash/MainWindow.xaml.cs index a8c507f..43cd4d4 100644 --- a/VDash/MainWindow.xaml.cs +++ b/VDash/MainWindow.xaml.cs @@ -14,6 +14,7 @@ using System.Windows.Navigation; using System.Windows.Shapes; using Valve.VR; using VDash.Overlay; +using VDash.Utilities; namespace VDash { @@ -26,7 +27,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); @@ -37,21 +38,29 @@ namespace VDash } - StartOverlayWindow("Discord"); + //StartOverlayWindow("Discord"); - StartOverlayWindow("Microsoft Visual Studio"); + //StartOverlayWindow("Microsoft Visual Studio"); - StartOverlayWindow("Notepad++"); + //StartOverlayWindow("Notepad++"); //StartOverlayWindow("Putty"); - StartOverlayWindow("Paint"); + //StartOverlayWindow("Paint"); - 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); + //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); + + var displays = User32.GetDisplays(); + MonitorOverlay mo0 = new MonitorOverlay("left", "Left Monitor", displays[0]); + mo0.Create(); + MonitorOverlay mo1 = new MonitorOverlay("center", "Center Monitor", displays[1]); + mo1.Create(); + MonitorOverlay mo2= new MonitorOverlay("right", "Right Monitor", displays[2]); + mo2.Create(); } diff --git a/VDash/Overlay/MonitorOverlay.cs b/VDash/Overlay/MonitorOverlay.cs new file mode 100644 index 0000000..5f120a3 --- /dev/null +++ b/VDash/Overlay/MonitorOverlay.cs @@ -0,0 +1,560 @@ +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +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; +using static VDash.Utilities.User32; + +namespace VDash.Overlay +{ + public class MonitorOverlay : IOverlay + { + + public event EventHandler OverlayShown; + public event EventHandler OverlayHidden; + + 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 DisplayInfo DisplayInfo { get; private set; } + + 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; } } + + internal string m_Name; + public string Name { get { return m_Name; } } + + + public List OverlayShownActions { get; private set; } = new List(); + public List OverlayHiddenActions { get; private set; } = new List(); + + char[] specialKeys = { '+', '^', '%', '~', '(', ')', '{', '}', '[', ']' }; + + public MonitorOverlay(string key, string name, DisplayInfo display) + { + m_Key = key; + m_Name = name; + DisplayInfo = display; + } + + + public void Create() + { + createInternal(); + } + + private void createInternal() + { + if (m_Created) { throw new Exception("This overlay has already been created."); } + + + //Actually create the overlay + var overlay = OpenVR.Overlay; + 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_OverlayHandle, VROverlayInputMethod.Mouse); + + m_Running = true; + m_EventPumpTask = Task.Factory.StartNew(() => { EventPump(); }); + m_Created = true; + } + + public bool IsCreated() { return m_Created; } + + + public void Destroy() + { + destroyInternal(); + } + + private void destroyInternal() + { + if (!m_Created) { throw new Exception("This object is not created."); } + m_Created = false; + + //Signal render thread to exit + m_Running = false; + + //Wait for render thread to exit + m_EventPumpTask.Wait(); + + 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() + { + 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. + + var factory = new Factory1(); + //Get first adapter + var adapter = factory.GetAdapter1(0); + //Get device from adapter + var device = new SharpDX.Direct3D11.Device(adapter); + + int lastWidth = 0; + int lastHeight = 0; + + + // Create Staging texture CPU-accessible + //Actual screen texture. + //var screenTexture = new Texture2D(device, textureDesc); + Texture2D screenTexture = null; + DataStream stream = null; + #endregion + + //For FPS counter. + int frames = 0; + DateTime lastFps = DateTime.MinValue; + DateTime lastNameCheck = DateTime.MinValue; + + while (m_Running) + { + //TODO: implement thumbnail rendering... + //#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_OverlayHandle == OpenVR.k_ulOverlayHandleInvalid) || overlay == null || (!overlay.IsOverlayVisible(m_OverlayHandle))) + { + Thread.Sleep(100); + continue; + } +#endif + + + + #region VR Events + VREvent_t vrEvent = new VREvent_t(); + + 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 + var vrX = vrEvent.data.mouse.x; + var vrY = vrEvent.data.mouse.y; + + var point = new Point() { X = (int)vrX + DisplayInfo.MonitorArea.left, Y = DisplayInfo.ScreenHeight - (int)vrY + DisplayInfo.MonitorArea.top }; + + + bool succ = User32.SetCursorPos(point.X, point.Y); + + if (!succ) + { + Console.WriteLine("SetCursorPos failed."); + } + + break; + } + + case EVREventType.VREvent_MouseButtonDown: + { + var clickType = (EVRMouseButton)vrEvent.data.mouse.button; + + var winClickType = User32.MouseEventFlags.MOUSEEVENTF_LEFTDOWN; + + switch (clickType) + { + case EVRMouseButton.Left: + { + winClickType = User32.MouseEventFlags.MOUSEEVENTF_LEFTDOWN; + break; + } + case EVRMouseButton.Middle: + { + winClickType = User32.MouseEventFlags.MOUSEEVENTF_MIDDLEDOWN; + break; + } + case EVRMouseButton.Right: + { + winClickType = User32.MouseEventFlags.MOUSEEVENTF_RIGHTDOWN; + break; + } + } + + User32.mouse_event(winClickType, 0, 0, 0, UIntPtr.Zero); + lastClick = DateTime.Now; + break; + } + + case EVREventType.VREvent_MouseButtonUp: + { + var clickType = (EVRMouseButton)vrEvent.data.mouse.button; + var winClickType = User32.MouseEventFlags.MOUSEEVENTF_LEFTUP; + + switch (clickType) + { + case EVRMouseButton.Left: + { + winClickType = User32.MouseEventFlags.MOUSEEVENTF_LEFTUP; + break; + } + case EVRMouseButton.Middle: + { + winClickType = User32.MouseEventFlags.MOUSEEVENTF_MIDDLEUP; + break; + } + case EVRMouseButton.Right: + { + winClickType = User32.MouseEventFlags.MOUSEEVENTF_RIGHTUP; + break; + } + } + + User32.mouse_event(winClickType, 0, 0, 0, UIntPtr.Zero); + lastClick = DateTime.MinValue; + break; + } + case EVREventType.VREvent_OverlayShown: + { + OverlayShown?.Invoke(this, new EventArgs()); + + foreach (var action in OverlayShownActions) + { + action.PreformAction(this); + } + break; + } + case EVREventType.VREvent_OverlayHidden: + { + OverlayHidden?.Invoke(this, new EventArgs()); + + 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; + } + } + + } + #endregion + + #region Acutally Render + + var rect = DisplayInfo.MonitorArea; + Point pnt = new Point { X = 0, Y = 0 }; + pnt.X = DisplayInfo.MonitorArea.left; + pnt.Y = DisplayInfo.MonitorArea.top; + + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + //Make sure the coords are actually valid. Could be minimized or something. + if (width > 0 && height > 0) + { + using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb)) + { + using (Graphics graphics = Graphics.FromImage(bmp)) + { + graphics.CopyFromScreen((int)pnt.X, (int)pnt.Y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); + + #region Cursor drawing + User32.CURSORINFO pci; + pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(User32.CURSORINFO)); + + if (User32.GetCursorInfo(out pci)) + { + + Point cursorPoint = new Point() { X = pci.ptScreenPos.X, Y = pci.ptScreenPos.Y }; + cursorPoint.X -= DisplayInfo.MonitorArea.left; + cursorPoint.Y -= DisplayInfo.MonitorArea.top; + + 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 + { + 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); + + if (screenTexture == null || (lastWidth != data.Width || lastHeight != data.Height)) + { + lastWidth = data.Width; + lastHeight = data.Height; + + if (screenTexture != null) + { + 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(); + } + + 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_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, 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); + } + } + } + + + 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) + { + + Console.WriteLine($"Error rendering: {result}"); + } + + device.ImmediateContext.UnmapSubresource(screenTexture, 0); + + + + frames++; + + bmp.UnlockBits(data); + } + } + } + + } +#endregion + + Thread.Sleep(20); + + 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(); + } + + public void Dispose() + { + Destroy(); + } + + public string GetOverlayName() + { + EVROverlayError err = EVROverlayError.None; + StringBuilder sb = new StringBuilder(255); + uint res = OpenVR.Overlay.GetOverlayName(m_OverlayHandle, sb, 255, ref err); + return sb.ToString(); + } + + public void SetOverlayName(string 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/Overlay/WindowOverlay.cs b/VDash/Overlay/WindowOverlay.cs index 6f3ca76..c668fad 100644 --- a/VDash/Overlay/WindowOverlay.cs +++ b/VDash/Overlay/WindowOverlay.cs @@ -405,7 +405,7 @@ namespace VDash.Overlay case EVRButtonId.k_EButton_Knuckles_A: { overlay.ShowKeyboardForOverlay(m_OverlayHandle, (int)EGamepadTextInputMode.k_EGamepadTextInputModeNormal, - (int)EGamepadTextInputLineMode.k_EGamepadTextInputLineModeSingleLine, "desc?", 1024, "", true, 0); + (int)EGamepadTextInputLineMode.k_EGamepadTextInputLineModeSingleLine, "desc", 1024, "", true, 0); break; } } diff --git a/VDash/Utilities/User32.cs b/VDash/Utilities/User32.cs index 40a78a3..dfb0fba 100644 --- a/VDash/Utilities/User32.cs +++ b/VDash/Utilities/User32.cs @@ -11,12 +11,38 @@ namespace VDash.Utilities public class User32 { + + /// + /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle. + /// + /// + /// + /// By convention, the right and bottom edges of the rectangle are normally considered exclusive. + /// In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the the rectangle. + /// For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including, + /// the right column and bottom row of pixels. This structure is identical to the RECTL structure. + /// [StructLayout(LayoutKind.Sequential)] public struct Rect { + /// + /// The x-coordinate of the upper-left corner of the rectangle. + /// public int left; + + /// + /// The y-coordinate of the upper-left corner of the rectangle. + /// public int top; + + /// + /// The x-coordinate of the lower-right corner of the rectangle. + /// public int right; + + /// + /// The y-coordinate of the lower-right corner of the rectangle. + /// public int bottom; } @@ -592,5 +618,124 @@ namespace VDash.Utilities [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsWindow(IntPtr hWnd); + + #region Monitor Tools + [DllImport("user32.dll")] + static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi); + + //[DllImport("user32.dll")] + //static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi); + + delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); + + /// + /// The struct that contains the display information + /// + public class DisplayInfo + { + public bool Primary { get; set; } + public int ScreenHeight { get; set; } + public int ScreenWidth { get; set; } + public Rect MonitorArea { get; set; } + public Rect WorkArea { get; set; } + public MonitorInfoEx MonitorInfo { get; set; } + } + // size of a device name string + private const int CCHDEVICENAME = 32; + + + + /// + /// The MONITORINFOEX structure contains information about a display monitor. + /// The GetMonitorInfo function stores information into a MONITORINFOEX structure or a MONITORINFO structure. + /// The MONITORINFOEX structure is a superset of the MONITORINFO structure. The MONITORINFOEX structure adds a string member to contain a name + /// for the display monitor. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct MonitorInfoEx + { + /// + /// The size, in bytes, of the structure. Set this member to sizeof(MONITORINFOEX) (72) before calling the GetMonitorInfo function. + /// Doing so lets the function determine the type of structure you are passing to it. + /// + public int Size; + + /// + /// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates. + /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public Rect Monitor; + + /// + /// A RECT structure that specifies the work area rectangle of the display monitor that can be used by applications, + /// expressed in virtual-screen coordinates. Windows uses this rectangle to maximize an application on the monitor. + /// The rest of the area in rcMonitor contains system windows such as the task bar and side bars. + /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public Rect WorkArea; + + /// + /// The attributes of the display monitor. + /// + /// This member can be the following value: + /// 1 : MONITORINFOF_PRIMARY + /// + public uint Flags; + + /// + /// A string that specifies the device name of the monitor being used. Most applications have no use for a display monitor name, + /// and so can save some bytes by using a MONITORINFO structure. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string DeviceName; + + public void Init() + { + this.Size = 40 + 2 * CCHDEVICENAME; + this.DeviceName = string.Empty; + } + } + + /// + /// Collection of display information + /// + public class DisplayInfoCollection : List + { + } + + /// + /// Returns the number of Displays using the Win32 functions + /// + /// Collection of Display Info + public static DisplayInfoCollection GetDisplays() + { + DisplayInfoCollection col = new DisplayInfoCollection(); + + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, + delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) + { + MonitorInfoEx mi = new MonitorInfoEx(); + mi.Init(); + bool success = GetMonitorInfo(hMonitor, ref mi); + if (success) + { + DisplayInfo di = new DisplayInfo(); + di.ScreenWidth = (mi.Monitor.right - mi.Monitor.left); + di.ScreenHeight = (mi.Monitor.bottom - mi.Monitor.top); + di.MonitorArea = mi.Monitor; + di.WorkArea = mi.WorkArea; + di.Primary = mi.Flags == 1; + di.MonitorInfo = mi; + col.Add(di); + } + return true; + }, IntPtr.Zero); + return col; + } + #endregion + } } diff --git a/VDash/VDash.csproj b/VDash/VDash.csproj index ba4a362..32f7105 100644 --- a/VDash/VDash.csproj +++ b/VDash/VDash.csproj @@ -91,6 +91,7 @@ +