diff --git a/Source/OpenTK/Graphics/GraphicsContext.cs b/Source/OpenTK/Graphics/GraphicsContext.cs index 1d6eb6bd..12a605f1 100644 --- a/Source/OpenTK/Graphics/GraphicsContext.cs +++ b/Source/OpenTK/Graphics/GraphicsContext.cs @@ -150,6 +150,7 @@ namespace OpenTK.Graphics implementation = factory.CreateGLContext(mode, window, shareContext, direct_rendering, major, minor, flags); handle_cached = ((IGraphicsContextInternal)implementation).Context; + factory.RegisterResource(this); } AddContext(this); diff --git a/Source/OpenTK/Graphics/GraphicsContextBase.cs b/Source/OpenTK/Graphics/GraphicsContextBase.cs index 686704ca..d6537f53 100644 --- a/Source/OpenTK/Graphics/GraphicsContextBase.cs +++ b/Source/OpenTK/Graphics/GraphicsContextBase.cs @@ -1,44 +1,47 @@ #region License // -// The Open Toolkit Library License +// GraphicsContextBase.cs // -// Copyright (c) 2006 - 2009 the Open Toolkit library. +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos // // 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: +// 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 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. +// 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.Collections.Generic; +using System.Diagnostics; using System.Text; using OpenTK.Platform; namespace OpenTK.Graphics { // Provides the foundation for all IGraphicsContext implementations. - abstract class GraphicsContextBase : IGraphicsContext, IGraphicsContextInternal + abstract class GraphicsContextBase : IGraphicsContext, IGraphicsContextInternal, IEquatable { #region Fields bool disposed; - + protected ContextHandle Handle; protected GraphicsMode Mode; @@ -106,7 +109,53 @@ namespace OpenTK.Graphics #region IDisposable Members - public abstract void Dispose(); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected abstract void Dispose(bool disposing); + + #if DEBUG + ~GraphicsContextBase() + { + Dispose(false); + Debug.Print("[Warning] {0}:{1} leaked. Did you forget to call Dispose()?", + GetType().FullName, Handle); + } + #endif + + #endregion + + #region IEquatable Members + + public bool Equals(IGraphicsContextInternal other) + { + return Context.Equals(other.Context); + } + + #endregion + + #region Public Members + + public override string ToString() + { + return string.Format("[{0}: IsCurrent={1}, IsDisposed={2}, VSync={3}, SwapInterval={4}, GraphicsMode={5}, ErrorChecking={6}, Implementation={7}, Context={8}]", + GetType().Name, IsCurrent, IsDisposed, VSync, SwapInterval, GraphicsMode, ErrorChecking, Implementation, Context); + } + + public override int GetHashCode() + { + return Handle.GetHashCode(); + } + + public override bool Equals(object obj) + { + return + obj is IGraphicsContextInternal && + Equals((IGraphicsContextInternal)obj); + } #endregion } diff --git a/Source/OpenTK/NativeWindow.cs b/Source/OpenTK/NativeWindow.cs index 9b731f53..6e892db9 100644 --- a/Source/OpenTK/NativeWindow.cs +++ b/Source/OpenTK/NativeWindow.cs @@ -102,7 +102,9 @@ namespace OpenTK this.options = options; this.device = device; - implementation = Factory.Default.CreateNativeWindow(x, y, width, height, title, mode, options, this.device); + IPlatformFactory factory = Factory.Default; + implementation = factory.CreateNativeWindow(x, y, width, height, title, mode, options, this.device); + factory.RegisterResource(this); if ((options & GameWindowFlags.Fullscreen) != 0) { diff --git a/Source/OpenTK/Platform/Dummy/DummyGLContext.cs b/Source/OpenTK/Platform/Dummy/DummyGLContext.cs index e2f06d2c..ab6cead8 100644 --- a/Source/OpenTK/Platform/Dummy/DummyGLContext.cs +++ b/Source/OpenTK/Platform/Dummy/DummyGLContext.cs @@ -113,7 +113,7 @@ namespace OpenTK.Platform.Dummy #region --- IDisposable Members --- - public override void Dispose() { IsDisposed = true; } + protected override void Dispose(bool disposing) { IsDisposed = true; } #endregion } diff --git a/Source/OpenTK/Platform/Egl/EglContext.cs b/Source/OpenTK/Platform/Egl/EglContext.cs index 575174ff..13f8ec86 100644 --- a/Source/OpenTK/Platform/Egl/EglContext.cs +++ b/Source/OpenTK/Platform/Egl/EglContext.cs @@ -180,15 +180,9 @@ namespace OpenTK.Platform.Egl #region IDisposable Members - public override void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - // Todo: cross-reference the specs. What should happen if the context is destroyed from a different // thread? - protected virtual void Dispose(bool manual) + protected override void Dispose(bool manual) { if (!IsDisposed) { @@ -197,19 +191,10 @@ namespace OpenTK.Platform.Egl Egl.MakeCurrent(WindowInfo.Display, WindowInfo.Surface, WindowInfo.Surface, IntPtr.Zero); Egl.DestroyContext(WindowInfo.Display, HandleAsEGLContext); } - else - { - Debug.Print("[Warning] {0}:{1} was not disposed.", this.GetType().Name, HandleAsEGLContext); - } IsDisposed = true; } } - ~EglContext() - { - Dispose(false); - } - #endregion } } diff --git a/Source/OpenTK/Platform/Factory.cs b/Source/OpenTK/Platform/Factory.cs index a195429f..dcf4c97f 100644 --- a/Source/OpenTK/Platform/Factory.cs +++ b/Source/OpenTK/Platform/Factory.cs @@ -47,6 +47,11 @@ namespace OpenTK.Platform #region Constructors static Factory() + { + Toolkit.Init(); + } + + public Factory() { // Ensure we are correctly initialized. Toolkit.Init(); @@ -160,6 +165,11 @@ namespace OpenTK.Platform #pragma warning restore 612,618 } + public void RegisterResource(IDisposable resource) + { + default_implementation.RegisterResource(resource); + } + class UnsupportedPlatform : PlatformFactoryBase { #region Fields diff --git a/Source/OpenTK/Platform/IPlatformFactory.cs b/Source/OpenTK/Platform/IPlatformFactory.cs index 34effb7f..a126c13d 100644 --- a/Source/OpenTK/Platform/IPlatformFactory.cs +++ b/Source/OpenTK/Platform/IPlatformFactory.cs @@ -55,5 +55,7 @@ namespace OpenTK.Platform [Obsolete] Input.IJoystickDriver CreateLegacyJoystickDriver(); + + void RegisterResource(IDisposable resource); } } diff --git a/Source/OpenTK/Platform/MacOS/Cocoa/Class.cs b/Source/OpenTK/Platform/MacOS/Cocoa/Class.cs index 6c29bdcf..b7cd602f 100644 --- a/Source/OpenTK/Platform/MacOS/Cocoa/Class.cs +++ b/Source/OpenTK/Platform/MacOS/Cocoa/Class.cs @@ -55,6 +55,9 @@ namespace OpenTK.Platform.MacOS [DllImport (Cocoa.LibObjC)] extern static void objc_registerClassPair(IntPtr classToRegister); + [DllImport (Cocoa.LibObjC)] + extern static void objc_disposeClassPair(IntPtr cls); + public static IntPtr Get(string name) { var id = objc_getClass(name); @@ -75,7 +78,10 @@ namespace OpenTK.Platform.MacOS objc_registerClassPair(handle); } - static List storedDelegates = new List(); + public static void DisposeClass(IntPtr handle) + { + objc_disposeClassPair(handle); + } public static void RegisterMethod(IntPtr handle, Delegate d, string selector, string typeString) { @@ -89,8 +95,6 @@ namespace OpenTK.Platform.MacOS { throw new ArgumentException("Could not register method " + d + " in class + " + class_getName(handle)); } - - storedDelegates.Add(d); // Don't let the garbage collector eat our delegates. } } } diff --git a/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs b/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs index cc2d98d1..8357de3d 100644 --- a/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs +++ b/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs @@ -209,14 +209,13 @@ namespace OpenTK.Platform.MacOS fixed (byte* pBytes = b) { - IntPtr nsData = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSData"), Selector.Alloc), - Selector.Get("initWithBytes:length:"), (IntPtr)pBytes, b.Length), - Selector.Autorelease); + IntPtr nsData = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSData"), Selector.Alloc), + Selector.Get("initWithBytes:length:"), (IntPtr)pBytes, b.Length); - IntPtr nsImage = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSImage"), Selector.Alloc), - Selector.Get("initWithData:"), nsData), - Selector.Autorelease); + IntPtr nsImage = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSImage"), Selector.Alloc), + Selector.Get("initWithData:"), nsData); + Cocoa.SendVoid(nsData, Selector.Release); return nsImage; } } diff --git a/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs b/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs index 0670d43c..6011f05e 100644 --- a/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs +++ b/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs @@ -29,7 +29,9 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; using OpenTK.Platform.MacOS; namespace OpenTK.Platform.MacOS @@ -41,14 +43,21 @@ namespace OpenTK.Platform.MacOS static readonly IntPtr selQuit = Selector.Get("quit"); - internal static void Initialize() + static readonly int ThreadId = + System.Threading.Thread.CurrentThread.ManagedThreadId; + + internal static void Initialize() { } + + static NSApplication() { + Cocoa.Initialize(); + // Create the NSAutoreleasePool AutoreleasePool = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.NSAutoreleasePool, Selector.Alloc), Selector.Init); // Register a Quit method to be called on cmd-q IntPtr nsapp = Class.Get("NSApplication"); - Class.RegisterMethod(nsapp, new OnQuitDelegate(OnQuit), "quit", "v@:"); + Class.RegisterMethod(nsapp, OnQuitHandler, "quit", "v@:"); // Fetch the application handle Handle = Cocoa.SendIntPtr(nsapp, Selector.Get("sharedApplication")); @@ -58,22 +67,17 @@ namespace OpenTK.Platform.MacOS Cocoa.SendVoid(Handle, Selector.Get("activateIgnoringOtherApps:"), true); // Create the menu bar - var menubar = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc), - Selector.Autorelease); - - var menuItem = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc), - Selector.Autorelease); + var menubar = Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc); + var menuItem = Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc); // Add menu item to bar, and bar to application Cocoa.SendIntPtr(menubar, Selector.Get("addItem:"), menuItem); Cocoa.SendIntPtr(Handle, Selector.Get("setMainMenu:"), menubar); // Add a "Quit" menu item and bind the button. - var appMenu = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc), - Selector.Autorelease); - var quitMenuItem = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc), - Selector.Get("initWithTitle:action:keyEquivalent:"), Cocoa.ToNSString("Quit"), selQuit, Cocoa.ToNSString("q")), - Selector.Autorelease); + var appMenu = Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc); + var quitMenuItem = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc), + Selector.Get("initWithTitle:action:keyEquivalent:"), Cocoa.ToNSString("Quit"), selQuit, Cocoa.ToNSString("q")); Cocoa.SendIntPtr(appMenu, Selector.Get("addItem:"), quitMenuItem); Cocoa.SendIntPtr(menuItem, Selector.Get("setSubmenu:"), appMenu); @@ -99,9 +103,27 @@ namespace OpenTK.Platform.MacOS Cocoa.SendVoid(settings, Selector.Release); } + internal static bool IsUIThread + { + get + { + int thread_id = Thread.CurrentThread.ManagedThreadId; + bool is_ui_thread = thread_id == NSApplication.ThreadId; + if (!is_ui_thread) + { + Debug.Print("[Warning] UI resources must be disposed in the UI thread #{0}, not #{1}.", + NSApplication.ThreadId, thread_id); + } + return is_ui_thread; + } + } + internal static event EventHandler Quit = delegate { }; + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void OnQuitDelegate(IntPtr self, IntPtr cmd); + + static OnQuitDelegate OnQuitHandler = OnQuit; static void OnQuit(IntPtr self, IntPtr cmd) { var e = new CancelEventArgs(); diff --git a/Source/OpenTK/Platform/MacOS/CocoaContext.cs b/Source/OpenTK/Platform/MacOS/CocoaContext.cs index 1d6fc341..58dcec2d 100644 --- a/Source/OpenTK/Platform/MacOS/CocoaContext.cs +++ b/Source/OpenTK/Platform/MacOS/CocoaContext.cs @@ -326,23 +326,16 @@ namespace OpenTK #region IDisposable Members - ~CocoaContext() - { - Dispose(false); - } - - public override void Dispose() - { - Dispose(true); - } - - void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (IsDisposed || Handle.Handle == IntPtr.Zero) return; Debug.Print("Disposing of Cocoa context."); + if (!NSApplication.IsUIThread) + return; + Cocoa.SendVoid(NSOpenGLContext, Selector.Get("clearCurrentContext")); Cocoa.SendVoid(Handle.Handle, Selector.Get("clearDrawable")); Cocoa.SendVoid(Handle.Handle, Selector.Get("release")); diff --git a/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs b/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs index 834e0483..15856d9a 100644 --- a/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs +++ b/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs @@ -128,6 +128,7 @@ namespace OpenTK.Platform.MacOS private CocoaWindowInfo windowInfo; private IntPtr windowClass; private IntPtr trackingArea; + private IntPtr current_icon_handle; private bool disposed = false; private bool exists; private bool cursorVisible = true; @@ -145,26 +146,44 @@ namespace OpenTK.Platform.MacOS public CocoaNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice device) { + // Create callback methods. We need to store those, + // otherwise the GC may collect them while they are + // still active. + WindowKeyDownHandler = WindowKeyDown; + WindowDidResizeHandler = WindowDidResize; + WindowDidMoveHandler = WindowDidMove; + WindowDidBecomeKeyHandler = WindowDidBecomeKey; + WindowDidResignKeyHandler = WindowDidResignKey; + WindowWillMiniaturizeHandler = WindowWillMiniaturize; + WindowDidMiniaturizeHandler = WindowDidMiniaturize; + WindowDidDeminiaturizeHandler = WindowDidDeminiaturize; + WindowShouldZoomToFrameHandler = WindowShouldZoomToFrame; + WindowShouldCloseHandler = WindowShouldClose; + AcceptsFirstResponderHandler = AcceptsFirstResponder; + CanBecomeKeyWindowHandler = CanBecomeKeyWindow; + CanBecomeMainWindowHandler = CanBecomeMainWindow; + ResetCursorRectsHandler = ResetCursorRects; + // Create the window class - Interlocked.Increment(ref UniqueId); - windowClass = Class.AllocateClass("OpenTK_GameWindow" + UniqueId, "NSWindow"); - Class.RegisterMethod(windowClass, new WindowKeyDownDelegate(WindowKeyDown), "keyDown:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowDidResizeDelegate(WindowDidResize), "windowDidResize:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowDidMoveDelegate(WindowDidMove), "windowDidMove:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowDidBecomeKeyDelegate(WindowDidBecomeKey), "windowDidBecomeKey:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowDidResignKeyDelegate(WindowDidResignKey), "windowDidResignKey:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowWillMiniaturizeDelegate(WindowWillMiniaturize), "windowWillMiniaturize:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowDidMiniaturizeDelegate(WindowDidMiniaturize), "windowDidMiniaturize:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowDidDeminiaturizeDelegate(WindowDidDeminiaturize), "windowDidDeminiaturize:", "v@:@"); - Class.RegisterMethod(windowClass, new WindowShouldZoomToFrameDelegate(WindowShouldZoomToFrame), "windowShouldZoom:toFrame:", "b@:@{NSRect={NSPoint=ff}{NSSize=ff}}"); - Class.RegisterMethod(windowClass, new WindowShouldCloseDelegate(WindowShouldClose), "windowShouldClose:", "b@:@"); - Class.RegisterMethod(windowClass, new AcceptsFirstResponderDelegate(AcceptsFirstResponder), "acceptsFirstResponder", "b@:"); - Class.RegisterMethod(windowClass, new CanBecomeKeyWindowDelegate(CanBecomeKeyWindow), "canBecomeKeyWindow", "b@:"); - Class.RegisterMethod(windowClass, new CanBecomeMainWindowDelegate(CanBecomeMainWindow), "canBecomeMainWindow", "b@:"); + int unique_id = Interlocked.Increment(ref UniqueId); + windowClass = Class.AllocateClass("OpenTK_GameWindow" + unique_id, "NSWindow"); + Class.RegisterMethod(windowClass, WindowKeyDownHandler, "keyDown:", "v@:@"); + Class.RegisterMethod(windowClass, WindowDidResizeHandler, "windowDidResize:", "v@:@"); + Class.RegisterMethod(windowClass, WindowDidMoveHandler, "windowDidMove:", "v@:@"); + Class.RegisterMethod(windowClass, WindowDidBecomeKeyHandler, "windowDidBecomeKey:", "v@:@"); + Class.RegisterMethod(windowClass, WindowDidResignKeyHandler, "windowDidResignKey:", "v@:@"); + Class.RegisterMethod(windowClass, WindowWillMiniaturizeHandler, "windowWillMiniaturize:", "v@:@"); + Class.RegisterMethod(windowClass, WindowDidMiniaturizeHandler, "windowDidMiniaturize:", "v@:@"); + Class.RegisterMethod(windowClass, WindowDidDeminiaturizeHandler, "windowDidDeminiaturize:", "v@:@"); + Class.RegisterMethod(windowClass, WindowShouldZoomToFrameHandler, "windowShouldZoom:toFrame:", "b@:@{NSRect={NSPoint=ff}{NSSize=ff}}"); + Class.RegisterMethod(windowClass, WindowShouldCloseHandler, "windowShouldClose:", "b@:@"); + Class.RegisterMethod(windowClass, AcceptsFirstResponderHandler, "acceptsFirstResponder", "b@:"); + Class.RegisterMethod(windowClass, CanBecomeKeyWindowHandler, "canBecomeKeyWindow", "b@:"); + Class.RegisterMethod(windowClass, CanBecomeMainWindowHandler, "canBecomeMainWindow", "b@:"); Class.RegisterClass(windowClass); - IntPtr viewClass = Class.AllocateClass("OpenTK_NSView" + UniqueId, "NSView"); - Class.RegisterMethod(viewClass, new ResetCursorRectsDelegate(ResetCursorRects), "resetCursorRects", "v@:"); + IntPtr viewClass = Class.AllocateClass("OpenTK_NSView" + unique_id, "NSView"); + Class.RegisterMethod(viewClass, ResetCursorRectsHandler, "resetCursorRects", "v@:"); Class.RegisterClass(viewClass); // Create window instance @@ -182,15 +201,34 @@ namespace OpenTK.Platform.MacOS var style = GetStyleMask(windowBorder); var bufferingType = NSBackingStore.Buffered; - IntPtr windowPtr; - windowPtr = Cocoa.SendIntPtr(windowClass, Selector.Alloc); - windowPtr = Cocoa.SendIntPtr(windowPtr, Selector.Get("initWithContentRect:styleMask:backing:defer:"), contentRect, (int)style, (int)bufferingType, false); + IntPtr classPtr; + classPtr = Cocoa.SendIntPtr(windowClass, Selector.Alloc); + if (classPtr == IntPtr.Zero) + { + Debug.Print("[Error] Failed to allocate window class."); + throw new PlatformException(); + } + + bool defer = false; + IntPtr windowPtr = Cocoa.SendIntPtr(classPtr, Selector.Get("initWithContentRect:styleMask:backing:defer:"), contentRect, (int)style, (int)bufferingType, defer); + if (windowPtr == IntPtr.Zero) + { + Debug.Print("[Error] Failed to initialize window with ({0}, {1}, {2}, {3}).", + contentRect, style, bufferingType, defer); + throw new PlatformException(); + } // Replace view with our custom implementation // that overrides resetCursorRects (maybe there is // a better way to implement this override?) // Existing view: IntPtr viewPtr = Cocoa.SendIntPtr(windowPtr, Selector.Get("contentView")); + if (viewPtr == IntPtr.Zero) + { + Debug.Print("[Error] Failed to retrieve content view for window {0}.", windowPtr); + throw new PlatformException(); + } + // Our custom view with the same bounds: viewPtr = Cocoa.SendIntPtr( Cocoa.SendIntPtr(viewClass, Selector.Alloc), @@ -200,6 +238,11 @@ namespace OpenTK.Platform.MacOS { Cocoa.SendVoid(windowPtr, Selector.Get("setContentView:"), viewPtr); } + else + { + Debug.Print("[Error] Failed to initialize content view with frame {0}.", selBounds); + throw new PlatformException(); + } windowInfo = new CocoaWindowInfo(windowPtr); @@ -214,21 +257,50 @@ namespace OpenTK.Platform.MacOS NSApplication.Quit += ApplicationQuit; } + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowKeyDownDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowDidResizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowDidMoveDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowDidBecomeKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowDidResignKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowWillMiniaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowDidMiniaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void WindowDidDeminiaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate bool WindowShouldZoomToFrameDelegate(IntPtr self, IntPtr cmd, IntPtr nsWindow, RectangleF toFrame); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate bool WindowShouldCloseDelegate(IntPtr self, IntPtr cmd, IntPtr sender); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate bool AcceptsFirstResponderDelegate(IntPtr self, IntPtr cmd); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate bool CanBecomeKeyWindowDelegate(IntPtr self, IntPtr cmd); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate bool CanBecomeMainWindowDelegate(IntPtr self, IntPtr cmd); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate void ResetCursorRectsDelegate(IntPtr self, IntPtr cmd); + WindowKeyDownDelegate WindowKeyDownHandler; + WindowDidResizeDelegate WindowDidResizeHandler; + WindowDidMoveDelegate WindowDidMoveHandler; + WindowDidBecomeKeyDelegate WindowDidBecomeKeyHandler; + WindowDidResignKeyDelegate WindowDidResignKeyHandler; + WindowWillMiniaturizeDelegate WindowWillMiniaturizeHandler; + WindowDidMiniaturizeDelegate WindowDidMiniaturizeHandler; + WindowDidDeminiaturizeDelegate WindowDidDeminiaturizeHandler; + WindowShouldZoomToFrameDelegate WindowShouldZoomToFrameHandler; + WindowShouldCloseDelegate WindowShouldCloseHandler; + AcceptsFirstResponderDelegate AcceptsFirstResponderHandler; + CanBecomeKeyWindowDelegate CanBecomeKeyWindowHandler; + CanBecomeMainWindowDelegate CanBecomeMainWindowHandler; + ResetCursorRectsDelegate ResetCursorRectsHandler; + private void WindowKeyDown(IntPtr self, IntPtr cmd, IntPtr notification) { // Steal the event to remove the "beep" sound that is normally played for unhandled key events. @@ -236,7 +308,14 @@ namespace OpenTK.Platform.MacOS private void WindowDidResize(IntPtr self, IntPtr cmd, IntPtr notification) { - OnResize(true); + try + { + OnResize(true); + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private void OnResize(bool resetTracking) @@ -258,77 +337,141 @@ namespace OpenTK.Platform.MacOS private void ApplicationQuit(object sender, CancelEventArgs e) { - bool close = WindowShouldClose(windowInfo.Handle, IntPtr.Zero, IntPtr.Zero); - e.Cancel |= !close; + try + { + bool close = WindowShouldClose(windowInfo.Handle, IntPtr.Zero, IntPtr.Zero); + e.Cancel |= !close; + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + } } private void WindowDidMove(IntPtr self, IntPtr cmd, IntPtr notification) { - // Problem: Called only when you stop moving for a brief moment, - // not each frame as it is on PC. - OnMove(EventArgs.Empty); + try + { + // Problem: Called only when you stop moving for a brief moment, + // not each frame as it is on PC. + OnMove(EventArgs.Empty); + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private void WindowDidBecomeKey(IntPtr self, IntPtr cmd, IntPtr notification) { - OnFocusedChanged(EventArgs.Empty); + try + { + OnFocusedChanged(EventArgs.Empty); + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private void WindowDidResignKey(IntPtr self, IntPtr cmd, IntPtr notification) { - OnFocusedChanged(EventArgs.Empty); + try + { + OnFocusedChanged(EventArgs.Empty); + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private void WindowWillMiniaturize(IntPtr self, IntPtr cmd, IntPtr notification) { - // Can get stuck in weird states if we maximize, then minimize; - // restoring to the old state would override the normalBounds. - // To avoid this without adding complexity, just restore state here. - RestoreWindowState(); // Avoid getting in weird states + try + { + // Can get stuck in weird states if we maximize, then minimize; + // restoring to the old state would override the normalBounds. + // To avoid this without adding complexity, just restore state here. + RestoreWindowState(); // Avoid getting in weird states + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private void WindowDidMiniaturize(IntPtr self, IntPtr cmd, IntPtr notification) { - windowState = WindowState.Minimized; - OnWindowStateChanged(EventArgs.Empty); - OnResize(false); // Don't set tracking area when we minimize + try + { + windowState = WindowState.Minimized; + OnWindowStateChanged(EventArgs.Empty); + OnResize(false); // Don't set tracking area when we minimize + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private void WindowDidDeminiaturize(IntPtr self, IntPtr cmd, IntPtr notification) { - windowState = WindowState.Normal; - OnWindowStateChanged(EventArgs.Empty); - OnResize(true); + try + { + windowState = WindowState.Normal; + OnWindowStateChanged(EventArgs.Empty); + OnResize(true); + } + catch (Exception e) + { + Debug.Print(e.ToString()); + } } private bool WindowShouldZoomToFrame(IntPtr self, IntPtr cmd, IntPtr nsWindow, RectangleF toFrame) { - if (windowState == WindowState.Maximized) + try { - WindowState = WindowState.Normal; + if (windowState == WindowState.Maximized) + { + WindowState = WindowState.Normal; + } + else + { + previousBounds = InternalBounds; + previousWindowBorder = WindowBorder; + + InternalBounds = toFrame; + windowState = WindowState.Maximized; + + OnWindowStateChanged(EventArgs.Empty); + } } - else + catch (Exception e) { - previousBounds = InternalBounds; - previousWindowBorder = WindowBorder; - - InternalBounds = toFrame; - windowState = WindowState.Maximized; - - OnWindowStateChanged(EventArgs.Empty); + Debug.Print(e.ToString()); } + return false; } private bool WindowShouldClose(IntPtr self, IntPtr cmd, IntPtr sender) { - var cancelArgs = new CancelEventArgs(); - OnClosing(cancelArgs); - - if (!cancelArgs.Cancel) + try { - OnClosed(EventArgs.Empty); - return true; + var cancelArgs = new CancelEventArgs(); + OnClosing(cancelArgs); + + if (!cancelArgs.Cancel) + { + OnClosed(EventArgs.Empty); + return true; + } + } + catch (Exception e) + { + Debug.Print(e.ToString()); } return false; @@ -351,24 +494,31 @@ namespace OpenTK.Platform.MacOS private void ResetTrackingArea() { - var owner = windowInfo.ViewHandle; - if (trackingArea != IntPtr.Zero) + try { - Cocoa.SendVoid(owner, selRemoveTrackingArea, trackingArea); - Cocoa.SendVoid(trackingArea, Selector.Release); + var owner = windowInfo.ViewHandle; + if (trackingArea != IntPtr.Zero) + { + Cocoa.SendVoid(owner, selRemoveTrackingArea, trackingArea); + Cocoa.SendVoid(trackingArea, Selector.Release); + } + + var ownerBounds = Cocoa.SendRect(owner, selBounds); + var options = (int)( + NSTrackingAreaOptions.MouseEnteredAndExited | + NSTrackingAreaOptions.ActiveInKeyWindow | + NSTrackingAreaOptions.MouseMoved | + NSTrackingAreaOptions.CursorUpdate); + + trackingArea = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSTrackingArea"), Selector.Alloc), + selInitWithRect, ownerBounds, options, owner, IntPtr.Zero); + + Cocoa.SendVoid(owner, selAddTrackingArea, trackingArea); + } + catch (Exception e) + { + Debug.Print(e.ToString()); } - - var ownerBounds = Cocoa.SendRect(owner, selBounds); - var options = (int)( - NSTrackingAreaOptions.MouseEnteredAndExited | - NSTrackingAreaOptions.ActiveInKeyWindow | - NSTrackingAreaOptions.MouseMoved | - NSTrackingAreaOptions.CursorUpdate); - - trackingArea = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSTrackingArea"), Selector.Alloc), - selInitWithRect, ownerBounds, options, owner, IntPtr.Zero); - - Cocoa.SendVoid(owner, selAddTrackingArea, trackingArea); } public override void Close() @@ -573,20 +723,7 @@ namespace OpenTK.Platform.MacOS if (shouldClose) { shouldClose = false; - - // PerformClose is equivalent to pressing the close-button, which - // does not work in a borderless window. Handle this special case. - if (GetStyleMask() == NSWindowStyle.Borderless) - { - if (WindowShouldClose(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) - { - Cocoa.SendVoid(windowInfo.Handle, selClose); - } - } - else - { - Cocoa.SendVoid(windowInfo.Handle, selPerformClose, windowInfo.Handle); - } + CloseWindow(false); } } @@ -613,13 +750,35 @@ namespace OpenTK.Platform.MacOS get { return icon; } set { - icon = value; - using (Image img = icon.ToBitmap()) + if (value != null && value != icon) { - IntPtr nsimg = Cocoa.ToNSImage(img); - Cocoa.SendVoid(NSApplication.Handle, selSetApplicationIconImage, nsimg); + // Create and set new icon + IntPtr nsimg = IntPtr.Zero; + using (Image img = value.ToBitmap()) + { + nsimg = Cocoa.ToNSImage(img); + if (nsimg != IntPtr.Zero) + { + Cocoa.SendVoid(NSApplication.Handle, selSetApplicationIconImage, nsimg); + } + else + { + Debug.Print("[Mac] Failed to create NSImage for {0}", value); + return; + } + } + + // Release previous icon + if (current_icon_handle != IntPtr.Zero) + { + Cocoa.SendVoid(current_icon_handle, Selector.Release); + } + + // Raise IconChanged event + current_icon_handle = nsimg; + icon = value; + OnIconChanged(EventArgs.Empty); } - OnIconChanged(EventArgs.Empty); } } @@ -889,21 +1048,19 @@ namespace OpenTK.Platform.MacOS // effect on output quality." IntPtr imgdata = Cocoa.SendIntPtr( - Cocoa.SendIntPtr( - Cocoa.SendIntPtr(NSBitmapImageRep, Selector.Alloc), - selInitWithBitmapDataPlanes, - IntPtr.Zero, - cursor.Width, - cursor.Height, - 8, - 4, - 1, - 0, - NSDeviceRGBColorSpace, - NSBitmapFormat.AlphaFirst, - 4 * cursor.Width, - 32), - Selector.Autorelease); + Cocoa.SendIntPtr(NSBitmapImageRep, Selector.Alloc), + selInitWithBitmapDataPlanes, + IntPtr.Zero, + cursor.Width, + cursor.Height, + 8, + 4, + 1, + 0, + NSDeviceRGBColorSpace, + NSBitmapFormat.AlphaFirst, + 4 * cursor.Width, + 32); if (imgdata == IntPtr.Zero) { Debug.Print("Failed to create NSBitmapImageRep with size ({0},{1]})", @@ -935,14 +1092,13 @@ namespace OpenTK.Platform.MacOS // Construct the actual NSImage IntPtr img = Cocoa.SendIntPtr( - Cocoa.SendIntPtr( - Cocoa.SendIntPtr(NSImage, Selector.Alloc), - selInitWithSize, - new SizeF(cursor.Width, cursor.Height)), - Selector.Autorelease); + Cocoa.SendIntPtr(NSImage, Selector.Alloc), + selInitWithSize, + new SizeF(cursor.Width, cursor.Height)); if (img == IntPtr.Zero) { Debug.Print("Failed to construct NSImage from NSBitmapImageRep"); + Cocoa.SendVoid(imgdata, Selector.Release); return IntPtr.Zero; } Cocoa.SendVoid(img, selAddRepresentation, imgdata); @@ -950,14 +1106,13 @@ namespace OpenTK.Platform.MacOS // Convert the NSImage to a NSCursor IntPtr nscursor = Cocoa.SendIntPtr( - Cocoa.SendIntPtr( - Cocoa.SendIntPtr(NSCursor, Selector.Alloc), - selInitWithImageHotSpot, - img, - new PointF(cursor.X, cursor.Y) - ), - Selector.Autorelease); + Cocoa.SendIntPtr(NSCursor, Selector.Alloc), + selInitWithImageHotSpot, + img, + new PointF(cursor.X, cursor.Y)); + Cocoa.SendVoid(imgdata, Selector.Release); + Cocoa.SendVoid(img, Selector.Release); return nscursor; } @@ -994,15 +1149,15 @@ namespace OpenTK.Platform.MacOS get { return cursorVisible; } set { - cursorVisible = value; - if (value) + if (value && !cursorVisible) { SetCursorVisible(true); } - else + else if (!value && cursorVisible) { SetCursorVisible(false); } + cursorVisible = value; } } @@ -1011,15 +1166,21 @@ namespace OpenTK.Platform.MacOS if (disposed) return; - Debug.Print("Disposing of CocoaNativeWindow."); - NSApplication.Quit -= ApplicationQuit; + Debug.Print("Disposing of CocoaNativeWindow (disposing={0}).", disposing); - CursorVisible = true; - disposed = true; - exists = false; + if (!NSApplication.IsUIThread) + return; + + NSApplication.Quit -= ApplicationQuit; if (disposing) { + CursorVisible = true; + if (exists) + { + CloseWindow(true); + } + if (trackingArea != IntPtr.Zero) { Cocoa.SendVoid(windowInfo.ViewHandle, selRemoveTrackingArea, trackingArea); @@ -1027,9 +1188,15 @@ namespace OpenTK.Platform.MacOS trackingArea = IntPtr.Zero; } + Debug.Print("[Mac] Disposing {0}", windowInfo); windowInfo.Dispose(); } + else + { + Debug.Print("{0} leaked, did you forget to call Dispose()?", GetType().FullName); + } + disposed = true; OnDisposed(EventArgs.Empty); } @@ -1119,5 +1286,28 @@ namespace OpenTK.Platform.MacOS { return (NSWindowStyle)Cocoa.SendUint(windowInfo.Handle, selStyleMask); } + + void CloseWindow(bool shutdown) + { + if (!Exists) + return; + + exists = false; + + // PerformClose is equivalent to pressing the close-button, which + // does not work in a borderless window. Handle this special case. + if (GetStyleMask() == NSWindowStyle.Borderless || shutdown) + { + if (WindowShouldClose(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) + { + Cocoa.SendVoid(windowInfo.Handle, selClose); + } + } + else + { + Cocoa.SendVoid(windowInfo.Handle, selPerformClose, windowInfo.Handle); + } + } + } } diff --git a/Source/OpenTK/Platform/MacOS/HIDInput.cs b/Source/OpenTK/Platform/MacOS/HIDInput.cs index 5bde1be3..4c3e4541 100755 --- a/Source/OpenTK/Platform/MacOS/HIDInput.cs +++ b/Source/OpenTK/Platform/MacOS/HIDInput.cs @@ -77,7 +77,7 @@ namespace OpenTK.Platform.MacOS new Dictionary(new IntPtrEqualityComparer()); } - readonly IOHIDManagerRef hidmanager; + IOHIDManagerRef hidmanager; readonly Dictionary MouseDevices = new Dictionary(new IntPtrEqualityComparer()); @@ -94,7 +94,7 @@ namespace OpenTK.Platform.MacOS readonly Dictionary JoystickIndexToDevice = new Dictionary(); - readonly CFRunLoop RunLoop = CF.CFRunLoopGetMain(); + readonly CFRunLoop RunLoop; readonly CFString InputLoopMode = CF.RunLoopModeDefault; readonly CFDictionary DeviceTypes = new CFDictionary(); @@ -118,12 +118,28 @@ namespace OpenTK.Platform.MacOS { Debug.Print("Using HIDInput."); + RunLoop = CF.CFRunLoopGetMain(); + if (RunLoop == IntPtr.Zero) + RunLoop = CF.CFRunLoopGetCurrent(); + if (RunLoop == IntPtr.Zero) + { + Debug.Print("[Error] No CFRunLoop found for {0}", GetType().FullName); + throw new InvalidOperationException(); + } + CF.CFRetain(RunLoop); + HandleDeviceAdded = DeviceAdded; HandleDeviceRemoved = DeviceRemoved; HandleDeviceValueReceived = DeviceValueReceived; // For retrieving input directly from the hardware hidmanager = CreateHIDManager(); + if (hidmanager == IntPtr.Zero) + { + Debug.Print("[Mac] Failed to create IO HID manager, HIDInput driver not supported."); + throw new NotSupportedException(); + } + RegisterHIDCallbacks(hidmanager); // For retrieving the global cursor position @@ -164,53 +180,61 @@ namespace OpenTK.Platform.MacOS IntPtr @event, IntPtr refcon) { - CursorState.SetIsConnected(true); - - switch (type) + try { - case CGEventType.MouseMoved: - case CGEventType.LeftMouseDragged: - case CGEventType.RightMouseDragged: - case CGEventType.OtherMouseDragged: - { - Carbon.HIPoint p = CG.EventGetLocation(@event); - CursorState.X = (int)Math.Round(p.X); - CursorState.Y = (int)Math.Round(p.Y); - } - break; + CursorState.SetIsConnected(true); - case CGEventType.ScrollWheel: - { - // Note: OpenTK follows the win32 convention, where - // (+h, +v) = (right, up). MacOS reports (+h, +v) = (left, up) - // so we need to flip the horizontal scroll direction. - double h = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis2) * MacOSFactory.ScrollFactor; - double v = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis1) * MacOSFactory.ScrollFactor; - CursorState.SetScrollRelative((float)(-h), (float)v); - } - break; + switch (type) + { + case CGEventType.MouseMoved: + case CGEventType.LeftMouseDragged: + case CGEventType.RightMouseDragged: + case CGEventType.OtherMouseDragged: + { + Carbon.HIPoint p = CG.EventGetLocation(@event); + CursorState.X = (int)Math.Round(p.X); + CursorState.Y = (int)Math.Round(p.Y); + } + break; - case CGEventType.LeftMouseDown: - case CGEventType.RightMouseDown: - case CGEventType.OtherMouseDown: - { - int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber); - n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK - MouseButton b = MouseButton.Left + n; - CursorState[b] = true; - } - break; + case CGEventType.ScrollWheel: + { + // Note: OpenTK follows the win32 convention, where + // (+h, +v) = (right, up). MacOS reports (+h, +v) = (left, up) + // so we need to flip the horizontal scroll direction. + double h = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis2) * MacOSFactory.ScrollFactor; + double v = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis1) * MacOSFactory.ScrollFactor; + CursorState.SetScrollRelative((float)(-h), (float)v); + } + break; - case CGEventType.LeftMouseUp: - case CGEventType.RightMouseUp: - case CGEventType.OtherMouseUp: - { - int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber); - n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK - MouseButton b = MouseButton.Left + n; - CursorState[b] = false; - } - break; + case CGEventType.LeftMouseDown: + case CGEventType.RightMouseDown: + case CGEventType.OtherMouseDown: + { + int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber); + n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK + MouseButton b = MouseButton.Left + n; + CursorState[b] = true; + } + break; + + case CGEventType.LeftMouseUp: + case CGEventType.RightMouseUp: + case CGEventType.OtherMouseUp: + { + int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber); + n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK + MouseButton b = MouseButton.Left + n; + CursorState[b] = false; + } + break; + } + } + catch (Exception e) + { + // Do not let any exceptions escape into unmanaged code! + Debug.Print(e.ToString()); } return @event; @@ -234,8 +258,6 @@ namespace OpenTK.Platform.MacOS NativeMethods.IOHIDManagerSetDeviceMatching(hidmanager, DeviceTypes.Ref); NativeMethods.IOHIDManagerOpen(hidmanager, IOOptionBits.Zero); - - OpenTK.Platform.MacOS.Carbon.CF.CFRunLoopRunInMode(InputLoopMode, 0.0, true); } void DeviceAdded(IntPtr context, IOReturn res, IntPtr sender, IOHIDDeviceRef device) @@ -322,6 +344,7 @@ namespace OpenTK.Platform.MacOS { NativeMethods.IOHIDDeviceRegisterInputValueCallback(device, IntPtr.Zero, IntPtr.Zero); NativeMethods.IOHIDDeviceUnscheduleFromRunLoop(device, RunLoop, InputLoopMode); + NativeMethods.IOHIDDeviceClose(device, IOOptionBits.Zero); } } catch (Exception e) @@ -1032,7 +1055,11 @@ namespace OpenTK.Platform.MacOS [DllImport(hid)] public static extern IOHIDManagerRef IOHIDManagerCreate( - CFAllocatorRef allocator, IOOptionBits options) ; + CFAllocatorRef allocator, IOOptionBits options); + + [DllImport(hid)] + public static extern IOReturn IOHIDManagerClose( + IOHIDManagerRef allocator, IOOptionBits options); // This routine will be called when a new (matching) device is connected. [DllImport(hid)] @@ -1075,7 +1102,7 @@ namespace OpenTK.Platform.MacOS [DllImport(hid)] public static extern void IOHIDManagerSetDeviceMatching( IOHIDManagerRef manager, - CFDictionaryRef matching) ; + CFDictionaryRef matching); [DllImport(hid)] public static extern IOReturn IOHIDManagerOpen( @@ -1087,6 +1114,11 @@ namespace OpenTK.Platform.MacOS IOHIDDeviceRef manager, IOOptionBits opts); + [DllImport(hid)] + public static extern IOReturn IOHIDDeviceClose( + IOHIDDeviceRef device, + IOOptionBits options); + [DllImport(hid)] public static extern CFTypeRef IOHIDDeviceGetProperty( IOHIDDeviceRef device, @@ -1724,6 +1756,15 @@ namespace OpenTK.Platform.MacOS { if (manual) { + if (MouseEventTapSource != IntPtr.Zero) + { + // Note: releasing the mach port (tap source) + // automatically releases the event tap. + CF.RunLoopRemoveSource(RunLoop, MouseEventTapSource, CF.RunLoopModeDefault); + CF.CFRelease(MouseEventTapSource); + MouseEventTapSource = IntPtr.Zero; + } + NativeMethods.IOHIDManagerRegisterDeviceMatchingCallback( hidmanager, IntPtr.Zero, IntPtr.Zero); NativeMethods.IOHIDManagerRegisterDeviceRemovalCallback( @@ -1746,19 +1787,15 @@ namespace OpenTK.Platform.MacOS DeviceRemoved(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, device); } - HandleDeviceAdded = null; - HandleDeviceRemoved = null; - HandleDeviceValueReceived = null; - - if (MouseEventTap != IntPtr.Zero) + if (hidmanager != IntPtr.Zero) { - CF.CFRelease(MouseEventTap); - MouseEventTap = IntPtr.Zero; + NativeMethods.IOHIDManagerClose(hidmanager, IOOptionBits.Zero); + hidmanager = IntPtr.Zero; } - if (MouseEventTapSource != IntPtr.Zero) + + if (RunLoop != IntPtr.Zero) { - CF.CFRelease(MouseEventTapSource); - MouseEventTapSource = IntPtr.Zero; + CF.CFRelease(RunLoop); } } else diff --git a/Source/OpenTK/Platform/MacOS/MacOSFactory.cs b/Source/OpenTK/Platform/MacOS/MacOSFactory.cs index 43e7bd75..9a40ffa3 100644 --- a/Source/OpenTK/Platform/MacOS/MacOSFactory.cs +++ b/Source/OpenTK/Platform/MacOS/MacOSFactory.cs @@ -43,7 +43,13 @@ namespace OpenTK.Platform.MacOS internal const float ScrollFactor = 0.1f; internal static bool ExclusiveFullscreen = false; - readonly IInputDriver2 InputDriver = new HIDInput(); + readonly IInputDriver2 InputDriver; + + public MacOSFactory() + { + NSApplication.Initialize(); + InputDriver = new HIDInput(); + } #region IPlatformFactory Members diff --git a/Source/OpenTK/Platform/MacOS/Quartz/CoreFoundation.cs b/Source/OpenTK/Platform/MacOS/Quartz/CoreFoundation.cs index 6b5c8818..a11b9b9b 100644 --- a/Source/OpenTK/Platform/MacOS/Quartz/CoreFoundation.cs +++ b/Source/OpenTK/Platform/MacOS/Quartz/CoreFoundation.cs @@ -112,6 +112,9 @@ namespace OpenTK.Platform.MacOS.Carbon [DllImport(appServices)] internal static extern IntPtr CFDictionaryGetValue(IntPtr theDictionary, IntPtr theKey); + [DllImport(appServices)] + internal static extern IntPtr CFRetain(CFTypeRef cf); + [DllImport(appServices)] internal static extern void CFRelease(CFTypeRef cf); @@ -230,5 +233,11 @@ namespace OpenTK.Platform.MacOS.Carbon CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); + + [DllImport(appServices, EntryPoint = "CFRunLoopRemoveSource")] + internal static extern void RunLoopRemoveSource( + CFRunLoopRef rl, + CFRunLoopSourceRef source, + CFStringRef mode); } } diff --git a/Source/OpenTK/Platform/PlatformException.cs b/Source/OpenTK/Platform/PlatformException.cs index adea690b..37ffe478 100644 --- a/Source/OpenTK/Platform/PlatformException.cs +++ b/Source/OpenTK/Platform/PlatformException.cs @@ -10,10 +10,22 @@ using System; namespace OpenTK { - /// Defines a plaftorm specific exception. + /// + /// Defines a plaftorm-specific exception. + /// public class PlatformException : Exception { - /// Constructs a new PlatformException. - public PlatformException(string s) : base(s) { } + /// + /// Initializes a new instance of the class. + /// + public PlatformException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A message explaining the cause for this exception. + public PlatformException(string message) : base(message) { } } } \ No newline at end of file diff --git a/Source/OpenTK/Platform/PlatformFactoryBase.cs b/Source/OpenTK/Platform/PlatformFactoryBase.cs index 40dc0c1c..69341d7a 100644 --- a/Source/OpenTK/Platform/PlatformFactoryBase.cs +++ b/Source/OpenTK/Platform/PlatformFactoryBase.cs @@ -28,6 +28,7 @@ #endregion using System; +using System.Collections.Generic; using System.Diagnostics; using OpenTK.Graphics; using OpenTK.Input; @@ -42,6 +43,9 @@ namespace OpenTK.Platform /// abstract class PlatformFactoryBase : IPlatformFactory { + static readonly object sync = new object(); + readonly List Resources = new List(); + protected bool IsDisposed; public PlatformFactoryBase() @@ -80,6 +84,14 @@ namespace OpenTK.Platform return new LegacyJoystickDriver(); } + public void RegisterResource(IDisposable resource) + { + lock (sync) + { + Resources.Add(resource); + } + } + #endregion #region IDisposable implementation @@ -96,10 +108,19 @@ namespace OpenTK.Platform { if (manual) { + lock (sync) + { + foreach (var resource in Resources) + { + resource.Dispose(); + } + Resources.Clear(); + } } else { - Debug.Print("[OpenTK] {0} leaked, did you forget to call Dispose()?", GetType()); + Debug.Print("[OpenTK] {0} leaked with {1} live resources, did you forget to call Dispose()?", + GetType().FullName, Resources.Count); } IsDisposed = true; } diff --git a/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs b/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs index 0fd064bd..eb6f8ab7 100644 --- a/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs +++ b/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs @@ -376,7 +376,7 @@ namespace OpenTK.Platform.SDL2 #region IDisposable Members - void Dispose(bool manual) + protected override void Dispose(bool manual) { if (!IsDisposed) { @@ -397,17 +397,6 @@ namespace OpenTK.Platform.SDL2 } } - public override void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~Sdl2GraphicsContext() - { - Dispose(false); - } - #endregion } } diff --git a/Source/OpenTK/Platform/Windows/WinGLContext.cs b/Source/OpenTK/Platform/Windows/WinGLContext.cs index b7da272a..0e5c044f 100644 --- a/Source/OpenTK/Platform/Windows/WinGLContext.cs +++ b/Source/OpenTK/Platform/Windows/WinGLContext.cs @@ -465,13 +465,7 @@ namespace OpenTK.Platform.Windows #region IDisposable Members - public override void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool calledManually) + protected override void Dispose(bool calledManually) { if (!IsDisposed) { @@ -479,20 +473,10 @@ namespace OpenTK.Platform.Windows { DestroyContext(); } - else - { - Debug.Print("[Warning] OpenGL context {0} leaked. Did you forget to call IGraphicsContext.Dispose()?", - Handle.Handle); - } IsDisposed = true; } } - ~WinGLContext() - { - Dispose(false); - } - #region private void DestroyContext() private void DestroyContext() diff --git a/Source/OpenTK/Platform/X11/X11GLContext.cs b/Source/OpenTK/Platform/X11/X11GLContext.cs index d86a3481..1b680d2a 100644 --- a/Source/OpenTK/Platform/X11/X11GLContext.cs +++ b/Source/OpenTK/Platform/X11/X11GLContext.cs @@ -483,13 +483,7 @@ namespace OpenTK.Platform.X11 #region --- IDisposable Members --- - public override void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool manuallyCalled) + protected override void Dispose(bool manuallyCalled) { if (!IsDisposed) { @@ -516,12 +510,6 @@ namespace OpenTK.Platform.X11 } IsDisposed = true; } - - - ~X11GLContext() - { - this.Dispose(false); - } #endregion } diff --git a/Source/OpenTK/Toolkit.cs b/Source/OpenTK/Toolkit.cs index 5839a907..0699d5b8 100644 --- a/Source/OpenTK/Toolkit.cs +++ b/Source/OpenTK/Toolkit.cs @@ -178,22 +178,19 @@ namespace OpenTK } } } - else - { - Debug.Print("OpenTK.Toolkit leaked, did you forget to call Dispose()?"); - platform_factory = null; - toolkit = null; - initialized = false; - } } + #if DEBUG /// /// Finalizes this instance. /// ~Toolkit() { - Dispose(false); + Debug.Print("[Warning] {0} leaked, did you forget to call Dispose()?"); + // We may not Dispose() the toolkit from the finalizer thread, + // as that will crash on many operating systems. } + #endif #endregion }