#region License // // The Open Toolkit Library License // // Copyright (c) 2006 - 2009 the Open Toolkit library. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #endregion using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using OpenTK.Graphics; using OpenTK.Input; using System.Collections.Generic; using System.IO; #if !MINIMAL using System.Drawing; #endif namespace OpenTK.Platform.Windows { /// \internal /// /// Drives GameWindow on Windows. /// This class supports OpenTK, and is not intended for use by OpenTK programs. /// internal sealed class WinGLNative : INativeWindow, IInputDriver { #region Fields const ExtendedWindowStyle ParentStyleEx = ExtendedWindowStyle.WindowEdge | ExtendedWindowStyle.ApplicationWindow; const ExtendedWindowStyle ChildStyleEx = 0; static readonly WinKeyMap KeyMap = new WinKeyMap(); readonly IntPtr Instance = Marshal.GetHINSTANCE(typeof(WinGLNative).Module); readonly IntPtr ClassName = Marshal.StringToHGlobalAuto(Guid.NewGuid().ToString()); readonly WindowProcedure WindowProcedureDelegate; readonly uint ModalLoopTimerPeriod = 1; UIntPtr timer_handle; readonly Functions.TimerProc ModalLoopCallback; bool class_registered; bool disposed; bool exists; WinWindowInfo window, child_window; WindowBorder windowBorder = WindowBorder.Resizable; Nullable previous_window_border; // Set when changing to fullscreen state. Nullable deferred_window_border; // Set to avoid changing borders during fullscreen state. WindowState windowState = WindowState.Normal; bool borderless_maximized_window_state = false; // Hack to get maximized mode with hidden border (not normally possible). bool focused; bool mouse_outside_window = true; bool invisible_since_creation; // Set by WindowsMessage.CREATE and consumed by Visible = true (calls BringWindowToFront). int suppress_resize; // Used in WindowBorder and WindowState in order to avoid rapid, consecutive resize events. Rectangle bounds = new Rectangle(), client_rectangle = new Rectangle(), previous_bounds = new Rectangle(); // Used to restore previous size when leaving fullscreen mode. Icon icon; const ClassStyle DefaultClassStyle = ClassStyle.OwnDC; // Used for IInputDriver implementation WinMMJoystick joystick_driver = new WinMMJoystick(); KeyboardDevice keyboard = new KeyboardDevice(); MouseDevice mouse = new MouseDevice(); IList keyboards = new List(1); IList mice = new List(1); const long ExtendedBit = 1 << 24; // Used to distinguish left and right control, alt and enter keys. public static readonly uint ShiftLeftScanCode = Functions.MapVirtualKey(VirtualKeys.LSHIFT, 0); public static readonly uint ShiftRightScanCode = Functions.MapVirtualKey(VirtualKeys.RSHIFT, 0); public static readonly uint ControlLeftScanCode = Functions.MapVirtualKey(VirtualKeys.LCONTROL, 0); public static readonly uint ControlRightScanCode = Functions.MapVirtualKey(VirtualKeys.RCONTROL, 0); public static readonly uint AltLeftScanCode = Functions.MapVirtualKey(VirtualKeys.LMENU, 0); public static readonly uint AltRightScanCode = Functions.MapVirtualKey(VirtualKeys.RMENU, 0); KeyPressEventArgs key_press = new KeyPressEventArgs((char)0); int cursor_visible_count = 0; static readonly object SyncRoot = new object(); #endregion #region Contructors public WinGLNative(int x, int y, int width, int height, string title, GameWindowFlags options, DisplayDevice device) { lock (SyncRoot) { // This is the main window procedure callback. We need the callback in order to create the window, so // don't move it below the CreateWindow calls. WindowProcedureDelegate = WindowProcedure; //// This timer callback is called periodically when the window enters a sizing / moving modal loop. //ModalLoopCallback = delegate(IntPtr handle, WindowMessage msg, UIntPtr eventId, int time) //{ // // Todo: find a way to notify the frontend that it should process queued up UpdateFrame/RenderFrame events. // if (Move != null) // Move(this, EventArgs.Empty); //}; // To avoid issues with Ati drivers on Windows 6+ with compositing enabled, the context will not be // bound to the top-level window, but rather to a child window docked in the parent. window = new WinWindowInfo( CreateWindow(x, y, width, height, title, options, device, IntPtr.Zero), null); child_window = new WinWindowInfo( CreateWindow(0, 0, ClientSize.Width, ClientSize.Height, title, options, device, window.WindowHandle), window); exists = true; keyboard.Description = "Standard Windows keyboard"; keyboard.NumberOfFunctionKeys = 12; keyboard.NumberOfKeys = 101; keyboard.NumberOfLeds = 3; mouse.Description = "Standard Windows mouse"; mouse.NumberOfButtons = 3; mouse.NumberOfWheels = 1; keyboards.Add(keyboard); mice.Add(mouse); } } #endregion #region Private Members #region WindowProcedure IntPtr WindowProcedure(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam) { switch (message) { #region Size / Move / Style events case WindowMessage.ACTIVATE: // See http://msdn.microsoft.com/en-us/library/ms646274(VS.85).aspx (WM_ACTIVATE notification): // wParam: The low-order word specifies whether the window is being activated or deactivated. bool new_focused_state = Focused; if (IntPtr.Size == 4) focused = (wParam.ToInt32() & 0xFFFF) != 0; else focused = (wParam.ToInt64() & 0xFFFF) != 0; if (new_focused_state != Focused) FocusedChanged(this, EventArgs.Empty); break; case WindowMessage.ENTERMENULOOP: case WindowMessage.ENTERSIZEMOVE: // Entering the modal size/move loop: we don't want rendering to // stop during this time, so we register a timer callback to continue // processing from time to time. StartTimer(handle); break; case WindowMessage.EXITMENULOOP: case WindowMessage.EXITSIZEMOVE: // ExitingmModal size/move loop: the timer callback is no longer // necessary. StopTimer(handle); break; case WindowMessage.ERASEBKGND: return new IntPtr(1); case WindowMessage.WINDOWPOSCHANGED: unsafe { WindowPosition* pos = (WindowPosition*)lParam; if (window != null && pos->hwnd == window.WindowHandle) { Point new_location = new Point(pos->x, pos->y); if (Location != new_location) { bounds.Location = new_location; Move(this, EventArgs.Empty); } Size new_size = new Size(pos->cx, pos->cy); if (Size != new_size) { bounds.Width = pos->cx; bounds.Height = pos->cy; Win32Rectangle rect; Functions.GetClientRect(handle, out rect); client_rectangle = rect.ToRectangle(); Functions.SetWindowPos(child_window.WindowHandle, IntPtr.Zero, 0, 0, ClientRectangle.Width, ClientRectangle.Height, SetWindowPosFlags.NOZORDER | SetWindowPosFlags.NOOWNERZORDER | SetWindowPosFlags.NOACTIVATE | SetWindowPosFlags.NOSENDCHANGING); if (suppress_resize <= 0) Resize(this, EventArgs.Empty); } // Ensure cursor remains grabbed if (!CursorVisible) GrabCursor(); } } break; case WindowMessage.STYLECHANGED: unsafe { if (wParam.ToInt64() == (long)GWL.STYLE) { WindowStyle style = ((StyleStruct*)lParam)->New; if ((style & WindowStyle.Popup) != 0) windowBorder = WindowBorder.Hidden; else if ((style & WindowStyle.ThickFrame) != 0) windowBorder = WindowBorder.Resizable; else if ((style & ~(WindowStyle.ThickFrame | WindowStyle.MaximizeBox)) != 0) windowBorder = WindowBorder.Fixed; } } // Ensure cursor remains grabbed if (!CursorVisible) GrabCursor(); break; case WindowMessage.SIZE: SizeMessage state = (SizeMessage)wParam.ToInt64(); WindowState new_state = windowState; switch (state) { case SizeMessage.RESTORED: new_state = borderless_maximized_window_state ? WindowState.Maximized : WindowState.Normal; break; case SizeMessage.MINIMIZED: new_state = WindowState.Minimized; break; case SizeMessage.MAXIMIZED: new_state = WindowBorder == WindowBorder.Hidden ? WindowState.Fullscreen : WindowState.Maximized; break; } if (new_state != windowState) { windowState = new_state; WindowStateChanged(this, EventArgs.Empty); } // Ensure cursor remains grabbed if (!CursorVisible) GrabCursor(); break; #endregion #region Input events case WindowMessage.CHAR: if (IntPtr.Size == 4) key_press.KeyChar = (char)wParam.ToInt32(); else key_press.KeyChar = (char)wParam.ToInt64(); KeyPress(this, key_press); break; case WindowMessage.MOUSEMOVE: Point point = new Point( (short)((uint)lParam.ToInt32() & 0x0000FFFF), (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16)); mouse.Position = point; if (mouse_outside_window) { // Once we receive a mouse move event, it means that the mouse has // re-entered the window. mouse_outside_window = false; EnableMouseTracking(); MouseEnter(this, EventArgs.Empty); } break; case WindowMessage.MOUSELEAVE: mouse_outside_window = true; // Mouse tracking is disabled automatically by the OS MouseLeave(this, EventArgs.Empty); break; case WindowMessage.MOUSEWHEEL: // This is due to inconsistent behavior of the WParam value on 64bit arch, whese // wparam = 0xffffffffff880000 or wparam = 0x00000000ff100000 mouse.WheelPrecise += ((long)wParam << 32 >> 48) / 120.0f; break; case WindowMessage.LBUTTONDOWN: Functions.SetCapture(window.WindowHandle); mouse[MouseButton.Left] = true; break; case WindowMessage.MBUTTONDOWN: Functions.SetCapture(window.WindowHandle); mouse[MouseButton.Middle] = true; break; case WindowMessage.RBUTTONDOWN: Functions.SetCapture(window.WindowHandle); mouse[MouseButton.Right] = true; break; case WindowMessage.XBUTTONDOWN: Functions.SetCapture(window.WindowHandle); mouse[((wParam.ToInt32() & 0xFFFF0000) >> 16) != (int)MouseKeys.XButton1 ? MouseButton.Button1 : MouseButton.Button2] = true; break; case WindowMessage.LBUTTONUP: Functions.ReleaseCapture(); mouse[MouseButton.Left] = false; break; case WindowMessage.MBUTTONUP: Functions.ReleaseCapture(); mouse[MouseButton.Middle] = false; break; case WindowMessage.RBUTTONUP: Functions.ReleaseCapture(); mouse[MouseButton.Right] = false; break; case WindowMessage.XBUTTONUP: Functions.ReleaseCapture(); mouse[((wParam.ToInt32() & 0xFFFF0000) >> 16) != (int)MouseKeys.XButton1 ? MouseButton.Button1 : MouseButton.Button2] = false; break; // Keyboard events: case WindowMessage.KEYDOWN: case WindowMessage.KEYUP: case WindowMessage.SYSKEYDOWN: case WindowMessage.SYSKEYUP: bool pressed = message == WindowMessage.KEYDOWN || message == WindowMessage.SYSKEYDOWN; // Shift/Control/Alt behave strangely when e.g. ShiftRight is held down and ShiftLeft is pressed // and released. It looks like neither key is released in this case, or that the wrong key is // released in the case of Control and Alt. // To combat this, we are going to release both keys when either is released. Hacky, but should work. // Win95 does not distinguish left/right key constants (GetAsyncKeyState returns 0). // In this case, both keys will be reported as pressed. bool extended = (lParam.ToInt64() & ExtendedBit) != 0; uint scancode = (uint)((lParam.ToInt64() >> 16) & 0xFF); Key key = Key.Unknown; switch ((VirtualKeys)wParam) { case VirtualKeys.SHIFT: // The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit // to distinguish between left and right keys. Moreover, pressing both keys and releasing one // may result in both keys being held down (but not always). // The only reliable way to solve this was reported by BlueMonkMN at the forums: we should // check the scancodes. It looks like GLFW does the same thing, so it should be reliable. // Note: we release both keys when either shift is released. // Otherwise, the state of one key might be stuck to pressed. if (ShiftRightScanCode != 0 && pressed) { if (scancode == ShiftRightScanCode) key = Input.Key.ShiftRight; else key = Input.Key.ShiftLeft; } else { // Windows 9x and NT4.0 or key release event. keyboard.SetKey(Input.Key.ShiftLeft, ShiftLeftScanCode, pressed); keyboard.SetKey(Input.Key.ShiftRight, ShiftRightScanCode, pressed); } break; case VirtualKeys.CONTROL: if (extended) key = Input.Key.ControlRight; else key = Input.Key.ControlLeft; break; case VirtualKeys.MENU: if (extended) key = Input.Key.AltRight; else key = Input.Key.AltLeft; break; case VirtualKeys.RETURN: if (extended) key = Key.KeypadEnter; else key = Key.Enter; break; default: if (!KeyMap.ContainsKey((VirtualKeys)wParam)) Debug.Print("Virtual key {0} ({1}) not mapped.", (VirtualKeys)wParam, (long)lParam); else key = KeyMap[(VirtualKeys)wParam]; break; } keyboard.SetKey(key, scancode, pressed); return IntPtr.Zero; case WindowMessage.SYSCHAR: return IntPtr.Zero; case WindowMessage.KILLFOCUS: keyboard.ClearKeys(); break; #endregion #region Creation / Destruction events case WindowMessage.CREATE: CreateStruct cs = (CreateStruct)Marshal.PtrToStructure(lParam, typeof(CreateStruct)); if (cs.hwndParent == IntPtr.Zero) { bounds.X = cs.x; bounds.Y = cs.y; bounds.Width = cs.cx; bounds.Height = cs.cy; Win32Rectangle rect; Functions.GetClientRect(handle, out rect); client_rectangle = rect.ToRectangle(); invisible_since_creation = true; } break; case WindowMessage.CLOSE: System.ComponentModel.CancelEventArgs e = new System.ComponentModel.CancelEventArgs(); Closing(this, e); if (!e.Cancel) { DestroyWindow(); break; } return IntPtr.Zero; case WindowMessage.DESTROY: exists = false; Functions.UnregisterClass(ClassName, Instance); window.Dispose(); child_window.Dispose(); Closed(this, EventArgs.Empty); break; #endregion } return Functions.DefWindowProc(handle, message, wParam, lParam); } private void EnableMouseTracking() { TrackMouseEventStructure me = new TrackMouseEventStructure(); me.Size = TrackMouseEventStructure.SizeInBytes; me.TrackWindowHandle = child_window.WindowHandle; me.Flags = TrackMouseEventFlags.LEAVE; if (!Functions.TrackMouseEvent(ref me)) Debug.Print("[Warning] Failed to enable mouse tracking, error: {0}.", Marshal.GetLastWin32Error()); } private void StartTimer(IntPtr handle) { if (timer_handle == UIntPtr.Zero) { timer_handle = Functions.SetTimer(handle, new UIntPtr(1), ModalLoopTimerPeriod, ModalLoopCallback); if (timer_handle == UIntPtr.Zero) Debug.Print("[Warning] Failed to set modal loop timer callback ({0}:{1}->{2}).", GetType().Name, handle, Marshal.GetLastWin32Error()); } } private void StopTimer(IntPtr handle) { if (timer_handle != UIntPtr.Zero) { if (!Functions.KillTimer(handle, timer_handle)) Debug.Print("[Warning] Failed to kill modal loop timer callback ({0}:{1}->{2}).", GetType().Name, handle, Marshal.GetLastWin32Error()); timer_handle = UIntPtr.Zero; } } #endregion #region IsIdle bool IsIdle { get { MSG message = new MSG(); return !Functions.PeekMessage(ref message, window.WindowHandle, 0, 0, 0); } } #endregion #region CreateWindow IntPtr CreateWindow(int x, int y, int width, int height, string title, GameWindowFlags options, DisplayDevice device, IntPtr parentHandle) { // Use win32 to create the native window. // Keep in mind that some construction code runs in the WM_CREATE message handler. // The style of a parent window is different than that of a child window. // Note: the child window should always be visible, even if the parent isn't. WindowStyle style = 0; ExtendedWindowStyle ex_style = 0; if (parentHandle == IntPtr.Zero) { style |= WindowStyle.OverlappedWindow | WindowStyle.ClipChildren; ex_style = ParentStyleEx; } else { style |= WindowStyle.Visible | WindowStyle.Child | WindowStyle.ClipSiblings; ex_style = ChildStyleEx; } // Find out the final window rectangle, after the WM has added its chrome (titlebar, sidebars etc). Win32Rectangle rect = new Win32Rectangle(); rect.left = x; rect.top = y; rect.right = x + width; rect.bottom = y + height; Functions.AdjustWindowRectEx(ref rect, style, false, ex_style); // Create the window class that we will use for this window. // The current approach is to register a new class for each top-level WinGLWindow we create. if (!class_registered) { ExtendedWindowClass wc = new ExtendedWindowClass(); wc.Size = ExtendedWindowClass.SizeInBytes; wc.Style = DefaultClassStyle; wc.Instance = Instance; wc.WndProc = WindowProcedureDelegate; wc.ClassName = ClassName; wc.Icon = Icon != null ? Icon.Handle : IntPtr.Zero; #warning "This seems to resize one of the 'large' icons, rather than using a small icon directly (multi-icon files). Investigate!" wc.IconSm = Icon != null ? new Icon(Icon, 16, 16).Handle : IntPtr.Zero; wc.Cursor = Functions.LoadCursor(CursorName.Arrow); ushort atom = Functions.RegisterClassEx(ref wc); if (atom == 0) throw new PlatformException(String.Format("Failed to register window class. Error: {0}", Marshal.GetLastWin32Error())); class_registered = true; } IntPtr window_name = Marshal.StringToHGlobalAuto(title); IntPtr handle = Functions.CreateWindowEx( ex_style, ClassName, window_name, style, rect.left, rect.top, rect.Width, rect.Height, parentHandle, IntPtr.Zero, Instance, IntPtr.Zero); if (handle == IntPtr.Zero) throw new PlatformException(String.Format("Failed to create window. Error: {0}", Marshal.GetLastWin32Error())); return handle; } #endregion #region DestroyWindow /// /// Starts the teardown sequence for the current window. /// void DestroyWindow() { if (Exists) { Debug.Print("Destroying window: {0}", window.ToString()); Functions.DestroyWindow(window.WindowHandle); exists = false; } } #endregion void HideBorder() { suppress_resize++; WindowBorder = WindowBorder.Hidden; ProcessEvents(); suppress_resize--; } void RestoreBorder() { suppress_resize++; WindowBorder = deferred_window_border.HasValue ? deferred_window_border.Value : previous_window_border.HasValue ? previous_window_border.Value : WindowBorder; ProcessEvents(); suppress_resize--; deferred_window_border = previous_window_border = null; } void ResetWindowState() { suppress_resize++; WindowState = WindowState.Normal; ProcessEvents(); suppress_resize--; } void GrabCursor() { Point pos = PointToScreen(new Point(ClientRectangle.X, ClientRectangle.Y)); Win32Rectangle rect = new Win32Rectangle(); rect.left = pos.X; rect.right = pos.X + ClientRectangle.Width; rect.top = pos.Y; rect.bottom = pos.Y + ClientRectangle.Height; if (!Functions.ClipCursor(ref rect)) Debug.WriteLine(String.Format("Failed to grab cursor. Error: {0}", Marshal.GetLastWin32Error())); } void UngrabCursor() { if (!Functions.ClipCursor(IntPtr.Zero)) Debug.WriteLine(String.Format("Failed to ungrab cursor. Error: {0}", Marshal.GetLastWin32Error())); } #endregion #region INativeWindow Members #region Bounds public Rectangle Bounds { get { return bounds; } set { // Note: the bounds variable is updated when the resize/move message arrives. Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, value.X, value.Y, value.Width, value.Height, 0); } } #endregion #region Location public Point Location { get { return Bounds.Location; } set { // Note: the bounds variable is updated when the resize/move message arrives. Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, value.X, value.Y, 0, 0, SetWindowPosFlags.NOSIZE); } } #endregion #region Size public Size Size { get { return Bounds.Size; } set { // Note: the bounds variable is updated when the resize/move message arrives. Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, 0, 0, value.Width, value.Height, SetWindowPosFlags.NOMOVE); } } #endregion #region ClientRectangle public Rectangle ClientRectangle { get { if (client_rectangle.Width == 0) client_rectangle.Width = 1; if (client_rectangle.Height == 0) client_rectangle.Height = 1; return client_rectangle; } set { WindowStyle style = (WindowStyle)Functions.GetWindowLong(window.WindowHandle, GetWindowLongOffsets.STYLE); Win32Rectangle rect = Win32Rectangle.From(value); Functions.AdjustWindowRect(ref rect, style, false); Location = new Point(rect.left, rect.top); Size = new Size(rect.Width, rect.Height); } } #endregion #region ClientSize public Size ClientSize { get { return ClientRectangle.Size; } set { WindowStyle style = (WindowStyle)Functions.GetWindowLong(window.WindowHandle, GetWindowLongOffsets.STYLE); Win32Rectangle rect = Win32Rectangle.From(value); Functions.AdjustWindowRect(ref rect, style, false); Size = new Size(rect.Width, rect.Height); } } #endregion #region Width public int Width { get { return ClientRectangle.Width; } set { ClientRectangle = new Rectangle(0, 0, value, Height); } } #endregion #region Height public int Height { get { return ClientRectangle.Height; } set { ClientRectangle = new Rectangle(0, 0, Width, value); } } #endregion #region X public int X { get { return Location.X; } set { Location = new Point(value, Y); } } #endregion #region Y public int Y { get { return Location.Y; } set { Location = new Point(X, value); } } #endregion #region Icon public Icon Icon { get { return icon; } set { if (value != icon) { icon = value; if (window.WindowHandle != IntPtr.Zero) { Functions.SendMessage(window.WindowHandle, WindowMessage.SETICON, (IntPtr)0, icon == null ? IntPtr.Zero : value.Handle); Functions.SendMessage(window.WindowHandle, WindowMessage.SETICON, (IntPtr)1, icon == null ? IntPtr.Zero : value.Handle); } IconChanged(this, EventArgs.Empty); } } } #endregion #region Focused public bool Focused { get { return focused; } } #endregion #region Title StringBuilder sb_title = new StringBuilder(256); public string Title { get { sb_title.Remove(0, sb_title.Length); if (Functions.GetWindowText(window.WindowHandle, sb_title, sb_title.Capacity) == 0) Debug.Print("Failed to retrieve window title (window:{0}, reason:{1}).", window.WindowHandle, Marshal.GetLastWin32Error()); return sb_title.ToString(); } set { if (Title != value) { if (!Functions.SetWindowText(window.WindowHandle, value)) Debug.Print("Failed to change window title (window:{0}, new title:{1}, reason:{2}).", window.WindowHandle, value, Marshal.GetLastWin32Error()); TitleChanged(this, EventArgs.Empty); } } } #endregion #region Visible public bool Visible { get { return Functions.IsWindowVisible(window.WindowHandle); } set { if (value != Visible) { if (value) { Functions.ShowWindow(window.WindowHandle, ShowWindowCommand.SHOW); if (invisible_since_creation) { Functions.BringWindowToTop(window.WindowHandle); Functions.SetForegroundWindow(window.WindowHandle); } } else if (!value) { Functions.ShowWindow(window.WindowHandle, ShowWindowCommand.HIDE); } VisibleChanged(this, EventArgs.Empty); } } } #endregion #region Exists public bool Exists { get { return exists; } } #endregion #region CursorVisible public bool CursorVisible { get { return cursor_visible_count >= 0; } // Not used set { if (value && cursor_visible_count < 0) { do { cursor_visible_count = Functions.ShowCursor(true); } while (cursor_visible_count < 0); UngrabCursor(); } else if (!value && cursor_visible_count >= 0) { do { cursor_visible_count = Functions.ShowCursor(false); } while (cursor_visible_count >= 0); GrabCursor(); } } } #endregion #region Close public void Close() { Functions.PostMessage(window.WindowHandle, WindowMessage.CLOSE, IntPtr.Zero, IntPtr.Zero); } #endregion #region public WindowState WindowState public WindowState WindowState { get { return windowState; } set { if (WindowState == value) return; ShowWindowCommand command = 0; bool exiting_fullscreen = false; borderless_maximized_window_state = false; switch (value) { case WindowState.Normal: command = ShowWindowCommand.RESTORE; // If we are leaving fullscreen mode we need to restore the border. if (WindowState == WindowState.Fullscreen) exiting_fullscreen = true; break; case WindowState.Maximized: // Note: if we use the MAXIMIZE command and the window border is Hidden (i.e. WS_POPUP), // we will enter fullscreen mode - we don't want that! As a workaround, we'll resize the window // manually to cover the whole working area of the current monitor. // Reset state to avoid strange interactions with fullscreen/minimized windows. ResetWindowState(); if (WindowBorder == WindowBorder.Hidden) { IntPtr current_monitor = Functions.MonitorFromWindow(window.WindowHandle, MonitorFrom.Nearest); MonitorInfo info = new MonitorInfo(); info.Size = MonitorInfo.SizeInBytes; Functions.GetMonitorInfo(current_monitor, ref info); previous_bounds = Bounds; borderless_maximized_window_state = true; Bounds = info.Work.ToRectangle(); } else { command = ShowWindowCommand.MAXIMIZE; } break; case WindowState.Minimized: command = ShowWindowCommand.MINIMIZE; break; case WindowState.Fullscreen: // We achieve fullscreen by hiding the window border and sending the MAXIMIZE command. // We cannot use the WindowState.Maximized directly, as that will not send the MAXIMIZE // command for windows with hidden borders. // Reset state to avoid strange side-effects from maximized/minimized windows. ResetWindowState(); previous_bounds = Bounds; previous_window_border = WindowBorder; HideBorder(); command = ShowWindowCommand.MAXIMIZE; Functions.SetForegroundWindow(window.WindowHandle); break; } if (command != 0) Functions.ShowWindow(window.WindowHandle, command); // Restore previous window border or apply pending border change when leaving fullscreen mode. if (exiting_fullscreen) { RestoreBorder(); } // Restore previous window size/location if necessary if (command == ShowWindowCommand.RESTORE && previous_bounds != Rectangle.Empty) { Bounds = previous_bounds; previous_bounds = Rectangle.Empty; } } } #endregion #region public WindowBorder WindowBorder public WindowBorder WindowBorder { get { return windowBorder; } set { // Do not allow border changes during fullscreen mode. // Defer them for when we leave fullscreen. if (WindowState == WindowState.Fullscreen) { deferred_window_border = value; return; } if (windowBorder == value) return; // We wish to avoid making an invisible window visible just to change the border. // However, it's a good idea to make a visible window invisible temporarily, to // avoid garbage caused by the border change. bool was_visible = Visible; // To ensure maximized/minimized windows work correctly, reset state to normal, // change the border, then go back to maximized/minimized. WindowState state = WindowState; ResetWindowState(); WindowStyle style = WindowStyle.ClipChildren | WindowStyle.ClipSiblings; switch (value) { case WindowBorder.Resizable: style |= WindowStyle.OverlappedWindow; break; case WindowBorder.Fixed: style |= WindowStyle.OverlappedWindow & ~(WindowStyle.ThickFrame | WindowStyle.MaximizeBox | WindowStyle.SizeBox); break; case WindowBorder.Hidden: style |= WindowStyle.Popup; break; } // Make sure client size doesn't change when changing the border style. Size client_size = ClientSize; Win32Rectangle rect = Win32Rectangle.From(client_size); Functions.AdjustWindowRectEx(ref rect, style, false, ParentStyleEx); // This avoids leaving garbage on the background window. if (was_visible) Visible = false; Functions.SetWindowLong(window.WindowHandle, GetWindowLongOffsets.STYLE, (IntPtr)(int)style); Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, 0, 0, rect.Width, rect.Height, SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOZORDER | SetWindowPosFlags.FRAMECHANGED); // Force window to redraw update its borders, but only if it's // already visible (invisible windows will change borders when // they become visible, so no need to make them visiable prematurely). if (was_visible) Visible = true; WindowState = state; WindowBorderChanged(this, EventArgs.Empty); } } #endregion #region PointToClient public Point PointToClient(Point point) { if (!Functions.ScreenToClient(window.WindowHandle, ref point)) throw new InvalidOperationException(String.Format( "Could not convert point {0} from screen to client coordinates. Windows error: {1}", point.ToString(), Marshal.GetLastWin32Error())); return point; } #endregion #region PointToScreen public Point PointToScreen(Point point) { if (!Functions.ClientToScreen(window.WindowHandle, ref point)) throw new InvalidOperationException(String.Format( "Could not convert point {0} from screen to client coordinates. Windows error: {1}", point.ToString(), Marshal.GetLastWin32Error())); return point; } #endregion #region Events public event EventHandler Move = delegate { }; public event EventHandler Resize = delegate { }; public event EventHandler Closing = delegate { }; public event EventHandler Closed = delegate { }; public event EventHandler Disposed = delegate { }; public event EventHandler IconChanged = delegate { }; public event EventHandler TitleChanged = delegate { }; public event EventHandler VisibleChanged = delegate { }; public event EventHandler FocusedChanged = delegate { }; public event EventHandler WindowBorderChanged = delegate { }; public event EventHandler WindowStateChanged = delegate { }; public event EventHandler KeyDown = delegate { }; public event EventHandler KeyPress = delegate { }; public event EventHandler KeyUp = delegate { }; public event EventHandler MouseEnter = delegate { }; public event EventHandler MouseLeave = delegate { }; #endregion #endregion #region INativeGLWindow Members #region public void ProcessEvents() private int ret; MSG msg; public void ProcessEvents() { while (!IsIdle) { ret = Functions.GetMessage(ref msg, window.WindowHandle, 0, 0); if (ret == -1) { throw new PlatformException(String.Format( "An error happened while processing the message queue. Windows error: {0}", Marshal.GetLastWin32Error())); } Functions.TranslateMessage(ref msg); Functions.DispatchMessage(ref msg); } } #endregion #region public IInputDriver InputDriver public IInputDriver InputDriver { get { return this; } } #endregion #region public IWindowInfo WindowInfo public IWindowInfo WindowInfo { get { return child_window; } } #endregion #endregion #region IInputDriver Members public void Poll() { joystick_driver.Poll(); } #endregion #region IKeyboardDriver Members public IList Keyboard { get { return keyboards; } } public KeyboardState GetState() { throw new NotImplementedException(); } public KeyboardState GetState(int index) { throw new NotImplementedException(); } #endregion #region IMouseDriver Members public IList Mouse { get { return mice; } } #endregion #region IJoystickDriver Members public IList Joysticks { get { return joystick_driver.Joysticks; } } #endregion #region IDisposable Members public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } void Dispose(bool calledManually) { if (!disposed) { if (calledManually) { // Safe to clean managed resources DestroyWindow(); if (Icon != null) Icon.Dispose(); } else { Debug.Print("[Warning] INativeWindow leaked ({0}). Did you forget to call INativeWindow.Dispose()?", this); } Disposed(this, EventArgs.Empty); disposed = true; } } ~WinGLNative() { Dispose(false); } #endregion } }