Merge pull request #158 from thefiddler/fuzzfix

[Mac] Improve stability under Cocoa
This commit is contained in:
thefiddler 2014-07-23 23:53:16 +02:00
commit ed7b83178f
21 changed files with 607 additions and 307 deletions

View file

@ -150,6 +150,7 @@ namespace OpenTK.Graphics
implementation = factory.CreateGLContext(mode, window, shareContext, direct_rendering, major, minor, flags); implementation = factory.CreateGLContext(mode, window, shareContext, direct_rendering, major, minor, flags);
handle_cached = ((IGraphicsContextInternal)implementation).Context; handle_cached = ((IGraphicsContextInternal)implementation).Context;
factory.RegisterResource(this);
} }
AddContext(this); AddContext(this);

View file

@ -1,44 +1,47 @@
#region License #region License
// //
// The Open Toolkit Library License // GraphicsContextBase.cs
// //
// Copyright (c) 2006 - 2009 the Open Toolkit library. // Author:
// Stefanos A. <stapostol@gmail.com>
//
// Copyright (c) 2006-2014 Stefanos Apostolopoulos
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to // in the Software without restriction, including without limitation the rights
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// the Software, and to permit persons to whom the Software is furnished to do // copies of the Software, and to permit persons to whom the Software is
// so, subject to the following conditions: // furnished to do so, subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in all // The above copyright notice and this permission notice shall be included in
// copies or substantial portions of the Software. // all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // THE SOFTWARE.
// OTHER DEALINGS IN THE SOFTWARE.
// //
#endregion #endregion
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Text; using System.Text;
using OpenTK.Platform; using OpenTK.Platform;
namespace OpenTK.Graphics namespace OpenTK.Graphics
{ {
// Provides the foundation for all IGraphicsContext implementations. // Provides the foundation for all IGraphicsContext implementations.
abstract class GraphicsContextBase : IGraphicsContext, IGraphicsContextInternal abstract class GraphicsContextBase : IGraphicsContext, IGraphicsContextInternal, IEquatable<IGraphicsContextInternal>
{ {
#region Fields #region Fields
bool disposed; bool disposed;
protected ContextHandle Handle; protected ContextHandle Handle;
protected GraphicsMode Mode; protected GraphicsMode Mode;
@ -106,7 +109,53 @@ namespace OpenTK.Graphics
#region IDisposable Members #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<IGraphicsContextInternal> 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 #endregion
} }

View file

@ -102,7 +102,9 @@ namespace OpenTK
this.options = options; this.options = options;
this.device = device; 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) if ((options & GameWindowFlags.Fullscreen) != 0)
{ {

View file

@ -113,7 +113,7 @@ namespace OpenTK.Platform.Dummy
#region --- IDisposable Members --- #region --- IDisposable Members ---
public override void Dispose() { IsDisposed = true; } protected override void Dispose(bool disposing) { IsDisposed = true; }
#endregion #endregion
} }

View file

@ -180,15 +180,9 @@ namespace OpenTK.Platform.Egl
#region IDisposable Members #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 // Todo: cross-reference the specs. What should happen if the context is destroyed from a different
// thread? // thread?
protected virtual void Dispose(bool manual) protected override void Dispose(bool manual)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
@ -197,19 +191,10 @@ namespace OpenTK.Platform.Egl
Egl.MakeCurrent(WindowInfo.Display, WindowInfo.Surface, WindowInfo.Surface, IntPtr.Zero); Egl.MakeCurrent(WindowInfo.Display, WindowInfo.Surface, WindowInfo.Surface, IntPtr.Zero);
Egl.DestroyContext(WindowInfo.Display, HandleAsEGLContext); Egl.DestroyContext(WindowInfo.Display, HandleAsEGLContext);
} }
else
{
Debug.Print("[Warning] {0}:{1} was not disposed.", this.GetType().Name, HandleAsEGLContext);
}
IsDisposed = true; IsDisposed = true;
} }
} }
~EglContext()
{
Dispose(false);
}
#endregion #endregion
} }
} }

View file

@ -47,6 +47,11 @@ namespace OpenTK.Platform
#region Constructors #region Constructors
static Factory() static Factory()
{
Toolkit.Init();
}
public Factory()
{ {
// Ensure we are correctly initialized. // Ensure we are correctly initialized.
Toolkit.Init(); Toolkit.Init();
@ -160,6 +165,11 @@ namespace OpenTK.Platform
#pragma warning restore 612,618 #pragma warning restore 612,618
} }
public void RegisterResource(IDisposable resource)
{
default_implementation.RegisterResource(resource);
}
class UnsupportedPlatform : PlatformFactoryBase class UnsupportedPlatform : PlatformFactoryBase
{ {
#region Fields #region Fields

View file

@ -55,5 +55,7 @@ namespace OpenTK.Platform
[Obsolete] [Obsolete]
Input.IJoystickDriver CreateLegacyJoystickDriver(); Input.IJoystickDriver CreateLegacyJoystickDriver();
void RegisterResource(IDisposable resource);
} }
} }

View file

@ -55,6 +55,9 @@ namespace OpenTK.Platform.MacOS
[DllImport (Cocoa.LibObjC)] [DllImport (Cocoa.LibObjC)]
extern static void objc_registerClassPair(IntPtr classToRegister); extern static void objc_registerClassPair(IntPtr classToRegister);
[DllImport (Cocoa.LibObjC)]
extern static void objc_disposeClassPair(IntPtr cls);
public static IntPtr Get(string name) public static IntPtr Get(string name)
{ {
var id = objc_getClass(name); var id = objc_getClass(name);
@ -75,7 +78,10 @@ namespace OpenTK.Platform.MacOS
objc_registerClassPair(handle); objc_registerClassPair(handle);
} }
static List<Delegate> storedDelegates = new List<Delegate>(); public static void DisposeClass(IntPtr handle)
{
objc_disposeClassPair(handle);
}
public static void RegisterMethod(IntPtr handle, Delegate d, string selector, string typeString) 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)); 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.
} }
} }
} }

View file

@ -209,14 +209,13 @@ namespace OpenTK.Platform.MacOS
fixed (byte* pBytes = b) fixed (byte* pBytes = b)
{ {
IntPtr nsData = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSData"), Selector.Alloc), IntPtr nsData = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSData"), Selector.Alloc),
Selector.Get("initWithBytes:length:"), (IntPtr)pBytes, b.Length), Selector.Get("initWithBytes:length:"), (IntPtr)pBytes, b.Length);
Selector.Autorelease);
IntPtr nsImage = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSImage"), Selector.Alloc), IntPtr nsImage = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSImage"), Selector.Alloc),
Selector.Get("initWithData:"), nsData), Selector.Get("initWithData:"), nsData);
Selector.Autorelease);
Cocoa.SendVoid(nsData, Selector.Release);
return nsImage; return nsImage;
} }
} }

View file

@ -29,7 +29,9 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using OpenTK.Platform.MacOS; using OpenTK.Platform.MacOS;
namespace OpenTK.Platform.MacOS namespace OpenTK.Platform.MacOS
@ -41,14 +43,21 @@ namespace OpenTK.Platform.MacOS
static readonly IntPtr selQuit = Selector.Get("quit"); 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 // Create the NSAutoreleasePool
AutoreleasePool = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.NSAutoreleasePool, Selector.Alloc), Selector.Init); AutoreleasePool = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.NSAutoreleasePool, Selector.Alloc), Selector.Init);
// Register a Quit method to be called on cmd-q // Register a Quit method to be called on cmd-q
IntPtr nsapp = Class.Get("NSApplication"); IntPtr nsapp = Class.Get("NSApplication");
Class.RegisterMethod(nsapp, new OnQuitDelegate(OnQuit), "quit", "v@:"); Class.RegisterMethod(nsapp, OnQuitHandler, "quit", "v@:");
// Fetch the application handle // Fetch the application handle
Handle = Cocoa.SendIntPtr(nsapp, Selector.Get("sharedApplication")); Handle = Cocoa.SendIntPtr(nsapp, Selector.Get("sharedApplication"));
@ -58,22 +67,17 @@ namespace OpenTK.Platform.MacOS
Cocoa.SendVoid(Handle, Selector.Get("activateIgnoringOtherApps:"), true); Cocoa.SendVoid(Handle, Selector.Get("activateIgnoringOtherApps:"), true);
// Create the menu bar // Create the menu bar
var menubar = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc), var menubar = Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc);
Selector.Autorelease); var menuItem = Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc);
var menuItem = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc),
Selector.Autorelease);
// Add menu item to bar, and bar to application // Add menu item to bar, and bar to application
Cocoa.SendIntPtr(menubar, Selector.Get("addItem:"), menuItem); Cocoa.SendIntPtr(menubar, Selector.Get("addItem:"), menuItem);
Cocoa.SendIntPtr(Handle, Selector.Get("setMainMenu:"), menubar); Cocoa.SendIntPtr(Handle, Selector.Get("setMainMenu:"), menubar);
// Add a "Quit" menu item and bind the button. // Add a "Quit" menu item and bind the button.
var appMenu = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc), var appMenu = Cocoa.SendIntPtr(Class.Get("NSMenu"), Selector.Alloc);
Selector.Autorelease); var quitMenuItem = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSMenuItem"), Selector.Alloc),
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.Get("initWithTitle:action:keyEquivalent:"), Cocoa.ToNSString("Quit"), selQuit, Cocoa.ToNSString("q")),
Selector.Autorelease);
Cocoa.SendIntPtr(appMenu, Selector.Get("addItem:"), quitMenuItem); Cocoa.SendIntPtr(appMenu, Selector.Get("addItem:"), quitMenuItem);
Cocoa.SendIntPtr(menuItem, Selector.Get("setSubmenu:"), appMenu); Cocoa.SendIntPtr(menuItem, Selector.Get("setSubmenu:"), appMenu);
@ -99,9 +103,27 @@ namespace OpenTK.Platform.MacOS
Cocoa.SendVoid(settings, Selector.Release); 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<CancelEventArgs> Quit = delegate { }; internal static event EventHandler<CancelEventArgs> Quit = delegate { };
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void OnQuitDelegate(IntPtr self, IntPtr cmd); delegate void OnQuitDelegate(IntPtr self, IntPtr cmd);
static OnQuitDelegate OnQuitHandler = OnQuit;
static void OnQuit(IntPtr self, IntPtr cmd) static void OnQuit(IntPtr self, IntPtr cmd)
{ {
var e = new CancelEventArgs(); var e = new CancelEventArgs();

View file

@ -326,23 +326,16 @@ namespace OpenTK
#region IDisposable Members #region IDisposable Members
~CocoaContext() protected override void Dispose(bool disposing)
{
Dispose(false);
}
public override void Dispose()
{
Dispose(true);
}
void Dispose(bool disposing)
{ {
if (IsDisposed || Handle.Handle == IntPtr.Zero) if (IsDisposed || Handle.Handle == IntPtr.Zero)
return; return;
Debug.Print("Disposing of Cocoa context."); Debug.Print("Disposing of Cocoa context.");
if (!NSApplication.IsUIThread)
return;
Cocoa.SendVoid(NSOpenGLContext, Selector.Get("clearCurrentContext")); Cocoa.SendVoid(NSOpenGLContext, Selector.Get("clearCurrentContext"));
Cocoa.SendVoid(Handle.Handle, Selector.Get("clearDrawable")); Cocoa.SendVoid(Handle.Handle, Selector.Get("clearDrawable"));
Cocoa.SendVoid(Handle.Handle, Selector.Get("release")); Cocoa.SendVoid(Handle.Handle, Selector.Get("release"));

View file

@ -128,6 +128,7 @@ namespace OpenTK.Platform.MacOS
private CocoaWindowInfo windowInfo; private CocoaWindowInfo windowInfo;
private IntPtr windowClass; private IntPtr windowClass;
private IntPtr trackingArea; private IntPtr trackingArea;
private IntPtr current_icon_handle;
private bool disposed = false; private bool disposed = false;
private bool exists; private bool exists;
private bool cursorVisible = true; 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) 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 // Create the window class
Interlocked.Increment(ref UniqueId); int unique_id = Interlocked.Increment(ref UniqueId);
windowClass = Class.AllocateClass("OpenTK_GameWindow" + UniqueId, "NSWindow"); windowClass = Class.AllocateClass("OpenTK_GameWindow" + unique_id, "NSWindow");
Class.RegisterMethod(windowClass, new WindowKeyDownDelegate(WindowKeyDown), "keyDown:", "v@:@"); Class.RegisterMethod(windowClass, WindowKeyDownHandler, "keyDown:", "v@:@");
Class.RegisterMethod(windowClass, new WindowDidResizeDelegate(WindowDidResize), "windowDidResize:", "v@:@"); Class.RegisterMethod(windowClass, WindowDidResizeHandler, "windowDidResize:", "v@:@");
Class.RegisterMethod(windowClass, new WindowDidMoveDelegate(WindowDidMove), "windowDidMove:", "v@:@"); Class.RegisterMethod(windowClass, WindowDidMoveHandler, "windowDidMove:", "v@:@");
Class.RegisterMethod(windowClass, new WindowDidBecomeKeyDelegate(WindowDidBecomeKey), "windowDidBecomeKey:", "v@:@"); Class.RegisterMethod(windowClass, WindowDidBecomeKeyHandler, "windowDidBecomeKey:", "v@:@");
Class.RegisterMethod(windowClass, new WindowDidResignKeyDelegate(WindowDidResignKey), "windowDidResignKey:", "v@:@"); Class.RegisterMethod(windowClass, WindowDidResignKeyHandler, "windowDidResignKey:", "v@:@");
Class.RegisterMethod(windowClass, new WindowWillMiniaturizeDelegate(WindowWillMiniaturize), "windowWillMiniaturize:", "v@:@"); Class.RegisterMethod(windowClass, WindowWillMiniaturizeHandler, "windowWillMiniaturize:", "v@:@");
Class.RegisterMethod(windowClass, new WindowDidMiniaturizeDelegate(WindowDidMiniaturize), "windowDidMiniaturize:", "v@:@"); Class.RegisterMethod(windowClass, WindowDidMiniaturizeHandler, "windowDidMiniaturize:", "v@:@");
Class.RegisterMethod(windowClass, new WindowDidDeminiaturizeDelegate(WindowDidDeminiaturize), "windowDidDeminiaturize:", "v@:@"); Class.RegisterMethod(windowClass, WindowDidDeminiaturizeHandler, "windowDidDeminiaturize:", "v@:@");
Class.RegisterMethod(windowClass, new WindowShouldZoomToFrameDelegate(WindowShouldZoomToFrame), "windowShouldZoom:toFrame:", "b@:@{NSRect={NSPoint=ff}{NSSize=ff}}"); Class.RegisterMethod(windowClass, WindowShouldZoomToFrameHandler, "windowShouldZoom:toFrame:", "b@:@{NSRect={NSPoint=ff}{NSSize=ff}}");
Class.RegisterMethod(windowClass, new WindowShouldCloseDelegate(WindowShouldClose), "windowShouldClose:", "b@:@"); Class.RegisterMethod(windowClass, WindowShouldCloseHandler, "windowShouldClose:", "b@:@");
Class.RegisterMethod(windowClass, new AcceptsFirstResponderDelegate(AcceptsFirstResponder), "acceptsFirstResponder", "b@:"); Class.RegisterMethod(windowClass, AcceptsFirstResponderHandler, "acceptsFirstResponder", "b@:");
Class.RegisterMethod(windowClass, new CanBecomeKeyWindowDelegate(CanBecomeKeyWindow), "canBecomeKeyWindow", "b@:"); Class.RegisterMethod(windowClass, CanBecomeKeyWindowHandler, "canBecomeKeyWindow", "b@:");
Class.RegisterMethod(windowClass, new CanBecomeMainWindowDelegate(CanBecomeMainWindow), "canBecomeMainWindow", "b@:"); Class.RegisterMethod(windowClass, CanBecomeMainWindowHandler, "canBecomeMainWindow", "b@:");
Class.RegisterClass(windowClass); Class.RegisterClass(windowClass);
IntPtr viewClass = Class.AllocateClass("OpenTK_NSView" + UniqueId, "NSView"); IntPtr viewClass = Class.AllocateClass("OpenTK_NSView" + unique_id, "NSView");
Class.RegisterMethod(viewClass, new ResetCursorRectsDelegate(ResetCursorRects), "resetCursorRects", "v@:"); Class.RegisterMethod(viewClass, ResetCursorRectsHandler, "resetCursorRects", "v@:");
Class.RegisterClass(viewClass); Class.RegisterClass(viewClass);
// Create window instance // Create window instance
@ -182,15 +201,34 @@ namespace OpenTK.Platform.MacOS
var style = GetStyleMask(windowBorder); var style = GetStyleMask(windowBorder);
var bufferingType = NSBackingStore.Buffered; var bufferingType = NSBackingStore.Buffered;
IntPtr windowPtr; IntPtr classPtr;
windowPtr = Cocoa.SendIntPtr(windowClass, Selector.Alloc); classPtr = Cocoa.SendIntPtr(windowClass, Selector.Alloc);
windowPtr = Cocoa.SendIntPtr(windowPtr, Selector.Get("initWithContentRect:styleMask:backing:defer:"), contentRect, (int)style, (int)bufferingType, false); 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 // Replace view with our custom implementation
// that overrides resetCursorRects (maybe there is // that overrides resetCursorRects (maybe there is
// a better way to implement this override?) // a better way to implement this override?)
// Existing view: // Existing view:
IntPtr viewPtr = Cocoa.SendIntPtr(windowPtr, Selector.Get("contentView")); 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: // Our custom view with the same bounds:
viewPtr = Cocoa.SendIntPtr( viewPtr = Cocoa.SendIntPtr(
Cocoa.SendIntPtr(viewClass, Selector.Alloc), Cocoa.SendIntPtr(viewClass, Selector.Alloc),
@ -200,6 +238,11 @@ namespace OpenTK.Platform.MacOS
{ {
Cocoa.SendVoid(windowPtr, Selector.Get("setContentView:"), viewPtr); 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); windowInfo = new CocoaWindowInfo(windowPtr);
@ -214,21 +257,50 @@ namespace OpenTK.Platform.MacOS
NSApplication.Quit += ApplicationQuit; NSApplication.Quit += ApplicationQuit;
} }
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowKeyDownDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowKeyDownDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowDidResizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidResizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowDidMoveDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidMoveDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowDidBecomeKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidBecomeKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowDidResignKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidResignKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowWillMiniaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowWillMiniaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowDidMiniaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidMiniaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void WindowDidDeminiaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidDeminiaturizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate bool WindowShouldZoomToFrameDelegate(IntPtr self, IntPtr cmd, IntPtr nsWindow, RectangleF toFrame); delegate bool WindowShouldZoomToFrameDelegate(IntPtr self, IntPtr cmd, IntPtr nsWindow, RectangleF toFrame);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate bool WindowShouldCloseDelegate(IntPtr self, IntPtr cmd, IntPtr sender); delegate bool WindowShouldCloseDelegate(IntPtr self, IntPtr cmd, IntPtr sender);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate bool AcceptsFirstResponderDelegate(IntPtr self, IntPtr cmd); delegate bool AcceptsFirstResponderDelegate(IntPtr self, IntPtr cmd);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate bool CanBecomeKeyWindowDelegate(IntPtr self, IntPtr cmd); delegate bool CanBecomeKeyWindowDelegate(IntPtr self, IntPtr cmd);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate bool CanBecomeMainWindowDelegate(IntPtr self, IntPtr cmd); delegate bool CanBecomeMainWindowDelegate(IntPtr self, IntPtr cmd);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void ResetCursorRectsDelegate(IntPtr self, IntPtr cmd); 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) 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. // 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) 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) private void OnResize(bool resetTracking)
@ -258,77 +337,141 @@ namespace OpenTK.Platform.MacOS
private void ApplicationQuit(object sender, CancelEventArgs e) private void ApplicationQuit(object sender, CancelEventArgs e)
{ {
bool close = WindowShouldClose(windowInfo.Handle, IntPtr.Zero, IntPtr.Zero); try
e.Cancel |= !close; {
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) private void WindowDidMove(IntPtr self, IntPtr cmd, IntPtr notification)
{ {
// Problem: Called only when you stop moving for a brief moment, try
// not each frame as it is on PC. {
OnMove(EventArgs.Empty); // 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) 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) 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) private void WindowWillMiniaturize(IntPtr self, IntPtr cmd, IntPtr notification)
{ {
// Can get stuck in weird states if we maximize, then minimize; try
// restoring to the old state would override the normalBounds. {
// To avoid this without adding complexity, just restore state here. // Can get stuck in weird states if we maximize, then minimize;
RestoreWindowState(); // Avoid getting in weird states // 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) private void WindowDidMiniaturize(IntPtr self, IntPtr cmd, IntPtr notification)
{ {
windowState = WindowState.Minimized; try
OnWindowStateChanged(EventArgs.Empty); {
OnResize(false); // Don't set tracking area when we minimize 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) private void WindowDidDeminiaturize(IntPtr self, IntPtr cmd, IntPtr notification)
{ {
windowState = WindowState.Normal; try
OnWindowStateChanged(EventArgs.Empty); {
OnResize(true); 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) 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; Debug.Print(e.ToString());
previousWindowBorder = WindowBorder;
InternalBounds = toFrame;
windowState = WindowState.Maximized;
OnWindowStateChanged(EventArgs.Empty);
} }
return false; return false;
} }
private bool WindowShouldClose(IntPtr self, IntPtr cmd, IntPtr sender) private bool WindowShouldClose(IntPtr self, IntPtr cmd, IntPtr sender)
{ {
var cancelArgs = new CancelEventArgs(); try
OnClosing(cancelArgs);
if (!cancelArgs.Cancel)
{ {
OnClosed(EventArgs.Empty); var cancelArgs = new CancelEventArgs();
return true; OnClosing(cancelArgs);
if (!cancelArgs.Cancel)
{
OnClosed(EventArgs.Empty);
return true;
}
}
catch (Exception e)
{
Debug.Print(e.ToString());
} }
return false; return false;
@ -351,24 +494,31 @@ namespace OpenTK.Platform.MacOS
private void ResetTrackingArea() private void ResetTrackingArea()
{ {
var owner = windowInfo.ViewHandle; try
if (trackingArea != IntPtr.Zero)
{ {
Cocoa.SendVoid(owner, selRemoveTrackingArea, trackingArea); var owner = windowInfo.ViewHandle;
Cocoa.SendVoid(trackingArea, Selector.Release); 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() public override void Close()
@ -573,20 +723,7 @@ namespace OpenTK.Platform.MacOS
if (shouldClose) if (shouldClose)
{ {
shouldClose = false; shouldClose = false;
CloseWindow(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);
}
} }
} }
@ -613,13 +750,35 @@ namespace OpenTK.Platform.MacOS
get { return icon; } get { return icon; }
set set
{ {
icon = value; if (value != null && value != icon)
using (Image img = icon.ToBitmap())
{ {
IntPtr nsimg = Cocoa.ToNSImage(img); // Create and set new icon
Cocoa.SendVoid(NSApplication.Handle, selSetApplicationIconImage, nsimg); 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." // effect on output quality."
IntPtr imgdata = IntPtr imgdata =
Cocoa.SendIntPtr( Cocoa.SendIntPtr(
Cocoa.SendIntPtr( Cocoa.SendIntPtr(NSBitmapImageRep, Selector.Alloc),
Cocoa.SendIntPtr(NSBitmapImageRep, Selector.Alloc), selInitWithBitmapDataPlanes,
selInitWithBitmapDataPlanes, IntPtr.Zero,
IntPtr.Zero, cursor.Width,
cursor.Width, cursor.Height,
cursor.Height, 8,
8, 4,
4, 1,
1, 0,
0, NSDeviceRGBColorSpace,
NSDeviceRGBColorSpace, NSBitmapFormat.AlphaFirst,
NSBitmapFormat.AlphaFirst, 4 * cursor.Width,
4 * cursor.Width, 32);
32),
Selector.Autorelease);
if (imgdata == IntPtr.Zero) if (imgdata == IntPtr.Zero)
{ {
Debug.Print("Failed to create NSBitmapImageRep with size ({0},{1]})", Debug.Print("Failed to create NSBitmapImageRep with size ({0},{1]})",
@ -935,14 +1092,13 @@ namespace OpenTK.Platform.MacOS
// Construct the actual NSImage // Construct the actual NSImage
IntPtr img = IntPtr img =
Cocoa.SendIntPtr( Cocoa.SendIntPtr(
Cocoa.SendIntPtr( Cocoa.SendIntPtr(NSImage, Selector.Alloc),
Cocoa.SendIntPtr(NSImage, Selector.Alloc), selInitWithSize,
selInitWithSize, new SizeF(cursor.Width, cursor.Height));
new SizeF(cursor.Width, cursor.Height)),
Selector.Autorelease);
if (img == IntPtr.Zero) if (img == IntPtr.Zero)
{ {
Debug.Print("Failed to construct NSImage from NSBitmapImageRep"); Debug.Print("Failed to construct NSImage from NSBitmapImageRep");
Cocoa.SendVoid(imgdata, Selector.Release);
return IntPtr.Zero; return IntPtr.Zero;
} }
Cocoa.SendVoid(img, selAddRepresentation, imgdata); Cocoa.SendVoid(img, selAddRepresentation, imgdata);
@ -950,14 +1106,13 @@ namespace OpenTK.Platform.MacOS
// Convert the NSImage to a NSCursor // Convert the NSImage to a NSCursor
IntPtr nscursor = IntPtr nscursor =
Cocoa.SendIntPtr( Cocoa.SendIntPtr(
Cocoa.SendIntPtr( Cocoa.SendIntPtr(NSCursor, Selector.Alloc),
Cocoa.SendIntPtr(NSCursor, Selector.Alloc), selInitWithImageHotSpot,
selInitWithImageHotSpot, img,
img, new PointF(cursor.X, cursor.Y));
new PointF(cursor.X, cursor.Y)
),
Selector.Autorelease);
Cocoa.SendVoid(imgdata, Selector.Release);
Cocoa.SendVoid(img, Selector.Release);
return nscursor; return nscursor;
} }
@ -994,15 +1149,15 @@ namespace OpenTK.Platform.MacOS
get { return cursorVisible; } get { return cursorVisible; }
set set
{ {
cursorVisible = value; if (value && !cursorVisible)
if (value)
{ {
SetCursorVisible(true); SetCursorVisible(true);
} }
else else if (!value && cursorVisible)
{ {
SetCursorVisible(false); SetCursorVisible(false);
} }
cursorVisible = value;
} }
} }
@ -1011,15 +1166,21 @@ namespace OpenTK.Platform.MacOS
if (disposed) if (disposed)
return; return;
Debug.Print("Disposing of CocoaNativeWindow."); Debug.Print("Disposing of CocoaNativeWindow (disposing={0}).", disposing);
NSApplication.Quit -= ApplicationQuit;
CursorVisible = true; if (!NSApplication.IsUIThread)
disposed = true; return;
exists = false;
NSApplication.Quit -= ApplicationQuit;
if (disposing) if (disposing)
{ {
CursorVisible = true;
if (exists)
{
CloseWindow(true);
}
if (trackingArea != IntPtr.Zero) if (trackingArea != IntPtr.Zero)
{ {
Cocoa.SendVoid(windowInfo.ViewHandle, selRemoveTrackingArea, trackingArea); Cocoa.SendVoid(windowInfo.ViewHandle, selRemoveTrackingArea, trackingArea);
@ -1027,9 +1188,15 @@ namespace OpenTK.Platform.MacOS
trackingArea = IntPtr.Zero; trackingArea = IntPtr.Zero;
} }
Debug.Print("[Mac] Disposing {0}", windowInfo);
windowInfo.Dispose(); windowInfo.Dispose();
} }
else
{
Debug.Print("{0} leaked, did you forget to call Dispose()?", GetType().FullName);
}
disposed = true;
OnDisposed(EventArgs.Empty); OnDisposed(EventArgs.Empty);
} }
@ -1119,5 +1286,28 @@ namespace OpenTK.Platform.MacOS
{ {
return (NSWindowStyle)Cocoa.SendUint(windowInfo.Handle, selStyleMask); 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);
}
}
} }
} }

View file

@ -77,7 +77,7 @@ namespace OpenTK.Platform.MacOS
new Dictionary<IOHIDElementRef, JoystickHat>(new IntPtrEqualityComparer()); new Dictionary<IOHIDElementRef, JoystickHat>(new IntPtrEqualityComparer());
} }
readonly IOHIDManagerRef hidmanager; IOHIDManagerRef hidmanager;
readonly Dictionary<IntPtr, MouseData> MouseDevices = readonly Dictionary<IntPtr, MouseData> MouseDevices =
new Dictionary<IntPtr, MouseData>(new IntPtrEqualityComparer()); new Dictionary<IntPtr, MouseData>(new IntPtrEqualityComparer());
@ -94,7 +94,7 @@ namespace OpenTK.Platform.MacOS
readonly Dictionary<int, IntPtr> JoystickIndexToDevice = readonly Dictionary<int, IntPtr> JoystickIndexToDevice =
new Dictionary<int, IntPtr>(); new Dictionary<int, IntPtr>();
readonly CFRunLoop RunLoop = CF.CFRunLoopGetMain(); readonly CFRunLoop RunLoop;
readonly CFString InputLoopMode = CF.RunLoopModeDefault; readonly CFString InputLoopMode = CF.RunLoopModeDefault;
readonly CFDictionary DeviceTypes = new CFDictionary(); readonly CFDictionary DeviceTypes = new CFDictionary();
@ -118,12 +118,28 @@ namespace OpenTK.Platform.MacOS
{ {
Debug.Print("Using HIDInput."); 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; HandleDeviceAdded = DeviceAdded;
HandleDeviceRemoved = DeviceRemoved; HandleDeviceRemoved = DeviceRemoved;
HandleDeviceValueReceived = DeviceValueReceived; HandleDeviceValueReceived = DeviceValueReceived;
// For retrieving input directly from the hardware // For retrieving input directly from the hardware
hidmanager = CreateHIDManager(); hidmanager = CreateHIDManager();
if (hidmanager == IntPtr.Zero)
{
Debug.Print("[Mac] Failed to create IO HID manager, HIDInput driver not supported.");
throw new NotSupportedException();
}
RegisterHIDCallbacks(hidmanager); RegisterHIDCallbacks(hidmanager);
// For retrieving the global cursor position // For retrieving the global cursor position
@ -164,53 +180,61 @@ namespace OpenTK.Platform.MacOS
IntPtr @event, IntPtr @event,
IntPtr refcon) IntPtr refcon)
{ {
CursorState.SetIsConnected(true); try
switch (type)
{ {
case CGEventType.MouseMoved: CursorState.SetIsConnected(true);
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.ScrollWheel: switch (type)
{ {
// Note: OpenTK follows the win32 convention, where case CGEventType.MouseMoved:
// (+h, +v) = (right, up). MacOS reports (+h, +v) = (left, up) case CGEventType.LeftMouseDragged:
// so we need to flip the horizontal scroll direction. case CGEventType.RightMouseDragged:
double h = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis2) * MacOSFactory.ScrollFactor; case CGEventType.OtherMouseDragged:
double v = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis1) * MacOSFactory.ScrollFactor; {
CursorState.SetScrollRelative((float)(-h), (float)v); Carbon.HIPoint p = CG.EventGetLocation(@event);
} CursorState.X = (int)Math.Round(p.X);
break; CursorState.Y = (int)Math.Round(p.Y);
}
break;
case CGEventType.LeftMouseDown: case CGEventType.ScrollWheel:
case CGEventType.RightMouseDown: {
case CGEventType.OtherMouseDown: // Note: OpenTK follows the win32 convention, where
{ // (+h, +v) = (right, up). MacOS reports (+h, +v) = (left, up)
int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber); // so we need to flip the horizontal scroll direction.
n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK double h = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis2) * MacOSFactory.ScrollFactor;
MouseButton b = MouseButton.Left + n; double v = CG.EventGetDoubleValueField(@event, CGEventField.ScrollWheelEventPointDeltaAxis1) * MacOSFactory.ScrollFactor;
CursorState[b] = true; CursorState.SetScrollRelative((float)(-h), (float)v);
} }
break; break;
case CGEventType.LeftMouseUp: case CGEventType.LeftMouseDown:
case CGEventType.RightMouseUp: case CGEventType.RightMouseDown:
case CGEventType.OtherMouseUp: case CGEventType.OtherMouseDown:
{ {
int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber); int n = CG.EventGetIntegerValueField(@event, CGEventField.MouseEventButtonNumber);
n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK n = n == 1 ? 2 : n == 2 ? 1 : n; // flip middle and right button numbers to match OpenTK
MouseButton b = MouseButton.Left + n; MouseButton b = MouseButton.Left + n;
CursorState[b] = false; CursorState[b] = true;
} }
break; 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; return @event;
@ -234,8 +258,6 @@ namespace OpenTK.Platform.MacOS
NativeMethods.IOHIDManagerSetDeviceMatching(hidmanager, DeviceTypes.Ref); NativeMethods.IOHIDManagerSetDeviceMatching(hidmanager, DeviceTypes.Ref);
NativeMethods.IOHIDManagerOpen(hidmanager, IOOptionBits.Zero); 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) 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.IOHIDDeviceRegisterInputValueCallback(device, IntPtr.Zero, IntPtr.Zero);
NativeMethods.IOHIDDeviceUnscheduleFromRunLoop(device, RunLoop, InputLoopMode); NativeMethods.IOHIDDeviceUnscheduleFromRunLoop(device, RunLoop, InputLoopMode);
NativeMethods.IOHIDDeviceClose(device, IOOptionBits.Zero);
} }
} }
catch (Exception e) catch (Exception e)
@ -1032,7 +1055,11 @@ namespace OpenTK.Platform.MacOS
[DllImport(hid)] [DllImport(hid)]
public static extern IOHIDManagerRef IOHIDManagerCreate( 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. // This routine will be called when a new (matching) device is connected.
[DllImport(hid)] [DllImport(hid)]
@ -1075,7 +1102,7 @@ namespace OpenTK.Platform.MacOS
[DllImport(hid)] [DllImport(hid)]
public static extern void IOHIDManagerSetDeviceMatching( public static extern void IOHIDManagerSetDeviceMatching(
IOHIDManagerRef manager, IOHIDManagerRef manager,
CFDictionaryRef matching) ; CFDictionaryRef matching);
[DllImport(hid)] [DllImport(hid)]
public static extern IOReturn IOHIDManagerOpen( public static extern IOReturn IOHIDManagerOpen(
@ -1087,6 +1114,11 @@ namespace OpenTK.Platform.MacOS
IOHIDDeviceRef manager, IOHIDDeviceRef manager,
IOOptionBits opts); IOOptionBits opts);
[DllImport(hid)]
public static extern IOReturn IOHIDDeviceClose(
IOHIDDeviceRef device,
IOOptionBits options);
[DllImport(hid)] [DllImport(hid)]
public static extern CFTypeRef IOHIDDeviceGetProperty( public static extern CFTypeRef IOHIDDeviceGetProperty(
IOHIDDeviceRef device, IOHIDDeviceRef device,
@ -1724,6 +1756,15 @@ namespace OpenTK.Platform.MacOS
{ {
if (manual) 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( NativeMethods.IOHIDManagerRegisterDeviceMatchingCallback(
hidmanager, IntPtr.Zero, IntPtr.Zero); hidmanager, IntPtr.Zero, IntPtr.Zero);
NativeMethods.IOHIDManagerRegisterDeviceRemovalCallback( NativeMethods.IOHIDManagerRegisterDeviceRemovalCallback(
@ -1746,19 +1787,15 @@ namespace OpenTK.Platform.MacOS
DeviceRemoved(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, device); DeviceRemoved(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, device);
} }
HandleDeviceAdded = null; if (hidmanager != IntPtr.Zero)
HandleDeviceRemoved = null;
HandleDeviceValueReceived = null;
if (MouseEventTap != IntPtr.Zero)
{ {
CF.CFRelease(MouseEventTap); NativeMethods.IOHIDManagerClose(hidmanager, IOOptionBits.Zero);
MouseEventTap = IntPtr.Zero; hidmanager = IntPtr.Zero;
} }
if (MouseEventTapSource != IntPtr.Zero)
if (RunLoop != IntPtr.Zero)
{ {
CF.CFRelease(MouseEventTapSource); CF.CFRelease(RunLoop);
MouseEventTapSource = IntPtr.Zero;
} }
} }
else else

View file

@ -43,7 +43,13 @@ namespace OpenTK.Platform.MacOS
internal const float ScrollFactor = 0.1f; internal const float ScrollFactor = 0.1f;
internal static bool ExclusiveFullscreen = false; internal static bool ExclusiveFullscreen = false;
readonly IInputDriver2 InputDriver = new HIDInput(); readonly IInputDriver2 InputDriver;
public MacOSFactory()
{
NSApplication.Initialize();
InputDriver = new HIDInput();
}
#region IPlatformFactory Members #region IPlatformFactory Members

View file

@ -112,6 +112,9 @@ namespace OpenTK.Platform.MacOS.Carbon
[DllImport(appServices)] [DllImport(appServices)]
internal static extern IntPtr CFDictionaryGetValue(IntPtr theDictionary, IntPtr theKey); internal static extern IntPtr CFDictionaryGetValue(IntPtr theDictionary, IntPtr theKey);
[DllImport(appServices)]
internal static extern IntPtr CFRetain(CFTypeRef cf);
[DllImport(appServices)] [DllImport(appServices)]
internal static extern void CFRelease(CFTypeRef cf); internal static extern void CFRelease(CFTypeRef cf);
@ -230,5 +233,11 @@ namespace OpenTK.Platform.MacOS.Carbon
CFRunLoopRef rl, CFRunLoopRef rl,
CFRunLoopSourceRef source, CFRunLoopSourceRef source,
CFStringRef mode); CFStringRef mode);
[DllImport(appServices, EntryPoint = "CFRunLoopRemoveSource")]
internal static extern void RunLoopRemoveSource(
CFRunLoopRef rl,
CFRunLoopSourceRef source,
CFStringRef mode);
} }
} }

View file

@ -10,10 +10,22 @@ using System;
namespace OpenTK namespace OpenTK
{ {
/// <summary>Defines a plaftorm specific exception.</summary> /// <summary>
/// Defines a plaftorm-specific exception.
/// </summary>
public class PlatformException : Exception public class PlatformException : Exception
{ {
/// <summary>Constructs a new PlatformException.</summary> /// <summary>
public PlatformException(string s) : base(s) { } /// Initializes a new instance of the <see cref="OpenTK.PlatformException"/> class.
/// </summary>
public PlatformException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OpenTK.PlatformException"/> class.
/// </summary>
/// <param name="message">A message explaining the cause for this exception.</param>
public PlatformException(string message) : base(message) { }
} }
} }

View file

@ -28,6 +28,7 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input; using OpenTK.Input;
@ -42,6 +43,9 @@ namespace OpenTK.Platform
/// </summary> /// </summary>
abstract class PlatformFactoryBase : IPlatformFactory abstract class PlatformFactoryBase : IPlatformFactory
{ {
static readonly object sync = new object();
readonly List<IDisposable> Resources = new List<IDisposable>();
protected bool IsDisposed; protected bool IsDisposed;
public PlatformFactoryBase() public PlatformFactoryBase()
@ -80,6 +84,14 @@ namespace OpenTK.Platform
return new LegacyJoystickDriver(); return new LegacyJoystickDriver();
} }
public void RegisterResource(IDisposable resource)
{
lock (sync)
{
Resources.Add(resource);
}
}
#endregion #endregion
#region IDisposable implementation #region IDisposable implementation
@ -96,10 +108,19 @@ namespace OpenTK.Platform
{ {
if (manual) if (manual)
{ {
lock (sync)
{
foreach (var resource in Resources)
{
resource.Dispose();
}
Resources.Clear();
}
} }
else 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; IsDisposed = true;
} }

View file

@ -376,7 +376,7 @@ namespace OpenTK.Platform.SDL2
#region IDisposable Members #region IDisposable Members
void Dispose(bool manual) protected override void Dispose(bool manual)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
@ -397,17 +397,6 @@ namespace OpenTK.Platform.SDL2
} }
} }
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Sdl2GraphicsContext()
{
Dispose(false);
}
#endregion #endregion
} }
} }

View file

@ -465,13 +465,7 @@ namespace OpenTK.Platform.Windows
#region IDisposable Members #region IDisposable Members
public override void Dispose() protected override void Dispose(bool calledManually)
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool calledManually)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
@ -479,20 +473,10 @@ namespace OpenTK.Platform.Windows
{ {
DestroyContext(); DestroyContext();
} }
else
{
Debug.Print("[Warning] OpenGL context {0} leaked. Did you forget to call IGraphicsContext.Dispose()?",
Handle.Handle);
}
IsDisposed = true; IsDisposed = true;
} }
} }
~WinGLContext()
{
Dispose(false);
}
#region private void DestroyContext() #region private void DestroyContext()
private void DestroyContext() private void DestroyContext()

View file

@ -483,13 +483,7 @@ namespace OpenTK.Platform.X11
#region --- IDisposable Members --- #region --- IDisposable Members ---
public override void Dispose() protected override void Dispose(bool manuallyCalled)
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool manuallyCalled)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
@ -516,12 +510,6 @@ namespace OpenTK.Platform.X11
} }
IsDisposed = true; IsDisposed = true;
} }
~X11GLContext()
{
this.Dispose(false);
}
#endregion #endregion
} }

View file

@ -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
/// <summary> /// <summary>
/// Finalizes this instance. /// Finalizes this instance.
/// </summary> /// </summary>
~Toolkit() ~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 #endregion
} }