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);
handle_cached = ((IGraphicsContextInternal)implementation).Context;
factory.RegisterResource(this);
}
AddContext(this);

View file

@ -1,44 +1,47 @@
#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
// 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<IGraphicsContextInternal>
{
#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<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
}

View file

@ -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)
{

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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

View file

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

View file

@ -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<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)
{
@ -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.
}
}
}

View file

@ -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;
}
}

View file

@ -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<CancelEventArgs> 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();

View file

@ -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"));

View file

@ -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);
}
}
}
}

View file

@ -77,7 +77,7 @@ namespace OpenTK.Platform.MacOS
new Dictionary<IOHIDElementRef, JoystickHat>(new IntPtrEqualityComparer());
}
readonly IOHIDManagerRef hidmanager;
IOHIDManagerRef hidmanager;
readonly Dictionary<IntPtr, MouseData> MouseDevices =
new Dictionary<IntPtr, MouseData>(new IntPtrEqualityComparer());
@ -94,7 +94,7 @@ namespace OpenTK.Platform.MacOS
readonly Dictionary<int, IntPtr> JoystickIndexToDevice =
new Dictionary<int, IntPtr>();
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

View file

@ -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

View file

@ -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);
}
}

View file

@ -10,10 +10,22 @@ using System;
namespace OpenTK
{
/// <summary>Defines a plaftorm specific exception.</summary>
/// <summary>
/// Defines a plaftorm-specific exception.
/// </summary>
public class PlatformException : Exception
{
/// <summary>Constructs a new PlatformException.</summary>
public PlatformException(string s) : base(s) { }
/// <summary>
/// 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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using OpenTK.Graphics;
using OpenTK.Input;
@ -42,6 +43,9 @@ namespace OpenTK.Platform
/// </summary>
abstract class PlatformFactoryBase : IPlatformFactory
{
static readonly object sync = new object();
readonly List<IDisposable> Resources = new List<IDisposable>();
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;
}

View file

@ -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
}
}

View file

@ -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()

View file

@ -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
}

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>
/// Finalizes this instance.
/// </summary>
~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
}