diff --git a/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs b/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs index 9d84f69039..a294e51f54 100644 --- a/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs +++ b/Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs @@ -223,8 +223,6 @@ public static void Initialize() AppKitLibrary = NS.LoadLibrary("/System/Library/Frameworks/AppKit.framework/AppKit"); FoundationLibrary = NS.LoadLibrary("/System/Library/Frameworks/Foundation.framework/Foundation"); - - NSApplication.Initialize(); } } } diff --git a/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs b/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs index 1375b33d88..cc0a34f9ca 100644 --- a/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs +++ b/Source/OpenTK/Platform/MacOS/Cocoa/NSApplication.cs @@ -28,6 +28,7 @@ #endregion using System; +using System.ComponentModel; using System.Runtime.InteropServices; using OpenTK.Platform.MacOS; @@ -38,13 +39,19 @@ static class NSApplication internal static IntPtr Handle; internal static IntPtr AutoreleasePool; + static readonly IntPtr selQuit = Selector.Get("quit"); + internal static void Initialize() { // Create the NSAutoreleasePool AutoreleasePool = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("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@:"); + // Fetch the application handle - Handle = Cocoa.SendIntPtr(Class.Get("NSApplication"), Selector.Get("sharedApplication")); + Handle = Cocoa.SendIntPtr(nsapp, Selector.Get("sharedApplication")); // Setup the application Cocoa.SendBool(Handle, Selector.Get("setActivationPolicy:"), (int)NSApplicationActivationPolicy.Regular); @@ -61,8 +68,31 @@ internal static void Initialize() 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); + + Cocoa.SendIntPtr(appMenu, Selector.Get("addItem:"), quitMenuItem); + Cocoa.SendIntPtr(menuItem, Selector.Get("setSubmenu:"), appMenu); + // Tell cocoa we're ready to run the application (usually called by [NSApp run]). Cocoa.SendVoid(Handle, Selector.Get("finishLaunching")); } + + internal static event EventHandler Quit = delegate { }; + + delegate void OnQuitDelegate(IntPtr self, IntPtr cmd); + static void OnQuit(IntPtr self, IntPtr cmd) + { + var e = new CancelEventArgs(); + Quit(null, e); + if (!e.Cancel) + { + Cocoa.SendVoid(Handle, Selector.Get("terminate:"), Handle); + } + } } } diff --git a/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs b/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs index e6669390a4..c2b2396e3a 100644 --- a/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs +++ b/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs @@ -61,7 +61,6 @@ class CocoaNativeWindow : INativeWindow static readonly IntPtr selNextEventMatchingMask = Selector.Get("nextEventMatchingMask:untilDate:inMode:dequeue:"); static readonly IntPtr selSendEvent = Selector.Get("sendEvent:"); //static readonly IntPtr selUpdateWindows = Selector.Get("updateWindows"); - static readonly IntPtr selContentView = Selector.Get("contentView"); static readonly IntPtr selConvertRectFromScreen = Selector.Get("convertRectFromScreen:"); static readonly IntPtr selConvertRectToScreen = Selector.Get("convertRectToScreen:"); static readonly IntPtr selPerformClose = Selector.Get("performClose:"); @@ -120,6 +119,7 @@ class CocoaNativeWindow : INativeWindow static CocoaNativeWindow() { Cocoa.Initialize(); + NSApplication.Initialize(); // Problem: This does not allow creating a separate app and using CocoaNativeWindow. NSDefaultRunLoopMode = Cocoa.GetStringConstant(Cocoa.FoundationLibrary, "NSDefaultRunLoopMode"); NSCursor = Class.Get("NSCursor"); } @@ -128,7 +128,7 @@ static CocoaNativeWindow() private IntPtr windowClass; private IntPtr trackingArea; private bool disposed = false; - private bool exists = true; + private bool exists; private bool cursorVisible = true; private System.Drawing.Icon icon; private LegacyInputDriver inputDriver = new LegacyInputDriver(); @@ -154,6 +154,7 @@ public CocoaNativeWindow(int x, int y, int width, int height, string title, Grap // 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@:@"); @@ -186,8 +187,12 @@ public CocoaNativeWindow(int x, int y, int width, int height, string title, Grap SetTitle(title, false); ResetTrackingArea(); + + exists = true; + NSApplication.Quit += ApplicationQuit; } + delegate void WindowKeyDownDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidResizeDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidMoveDelegate(IntPtr self, IntPtr cmd, IntPtr notification); delegate void WindowDidBecomeKeyDelegate(IntPtr self, IntPtr cmd, IntPtr notification); @@ -201,6 +206,11 @@ public CocoaNativeWindow(int x, int y, int width, int height, string title, Grap delegate bool CanBecomeKeyWindowDelegate(IntPtr self, IntPtr cmd); delegate bool CanBecomeMainWindowDelegate(IntPtr self, IntPtr cmd); + 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. + } + private void WindowDidResize(IntPtr self, IntPtr cmd, IntPtr notification) { OnResize(true); @@ -218,6 +228,12 @@ private void OnResize(bool resetTracking) Resize(this, EventArgs.Empty); } + private void ApplicationQuit(object sender, CancelEventArgs e) + { + bool close = WindowShouldClose(windowInfo.Handle, IntPtr.Zero, IntPtr.Zero); + e.Cancel |= !close; + } + private void WindowDidMove(IntPtr self, IntPtr cmd, IntPtr notification) { // Problem: Called only when you stop moving for a brief moment, @@ -392,10 +408,8 @@ public void ProcessEvents() KeyPress(this, keyPressArgs); } } - - // Steal all keydown events to avoid the annoying "bleep" sound. - return; } + break; case NSEventType.KeyUp: { @@ -940,6 +954,7 @@ protected virtual void Dispose(bool disposing) return; Debug.Print("Disposing of CocoaNativeWindow."); + NSApplication.Quit -= ApplicationQuit; CursorVisible = true; disposed = true; @@ -965,11 +980,6 @@ protected virtual void Dispose(bool disposing) Dispose(false); } - public static IntPtr GetView(IntPtr windowHandle) - { - return Cocoa.SendIntPtr(windowHandle, selContentView); - } - private RectangleF GetContentViewFrame() { return Cocoa.SendRect(windowInfo.ViewHandle, selFrame); diff --git a/Source/OpenTK/Platform/MacOS/CocoaWindowInfo.cs b/Source/OpenTK/Platform/MacOS/CocoaWindowInfo.cs index 80e6fdf59d..5858f804d4 100644 --- a/Source/OpenTK/Platform/MacOS/CocoaWindowInfo.cs +++ b/Source/OpenTK/Platform/MacOS/CocoaWindowInfo.cs @@ -40,7 +40,10 @@ namespace OpenTK.Platform.MacOS /// sealed class CocoaWindowInfo : IWindowInfo { + static readonly IntPtr selContentView = Selector.Get("contentView"); + IntPtr nsWindowRef; + IntPtr nsViewRef; bool disposed = false; @@ -49,10 +52,22 @@ sealed class CocoaWindowInfo : IWindowInfo /// /// Constructs a new instance with the specified parameters. /// - /// A valid NSView reference. - public CocoaWindowInfo(IntPtr nsWindowRef) + /// This constructor assumes that the NSWindow's contentView is the NSView we want to attach to our context. + /// A valid NSWindow reference. + public CocoaWindowInfo(IntPtr nsWindowRef) : this(nsWindowRef, Cocoa.SendIntPtr(nsWindowRef, selContentView)) + { + + } + + /// + /// Constructs a new instance with the specified parameters. + /// + /// A valid NSWindow reference. + /// A valid NSView reference. + public CocoaWindowInfo(IntPtr nsWindowRef, IntPtr nsViewRef) { this.nsWindowRef = nsWindowRef; + this.nsViewRef = nsViewRef; } #endregion @@ -67,19 +82,13 @@ public CocoaWindowInfo(IntPtr nsWindowRef) /// /// Gets the view reference for this instance. /// - public IntPtr ViewHandle - { - get - { - return CocoaNativeWindow.GetView(nsWindowRef); - } - } + public IntPtr ViewHandle { get { return nsViewRef; } } /// Returns a System.String that represents the current window. /// A System.String that represents the current window. public override string ToString() { - return String.Format("MacOS.CocoaWindowInfo: NSWindow {0}", nsWindowRef); + return String.Format("MacOS.CocoaWindowInfo: NSWindow {0}, NSView {1}", nsWindowRef, nsViewRef); } #endregion diff --git a/Source/OpenTK/Platform/Utilities.cs b/Source/OpenTK/Platform/Utilities.cs index 8e4c78e4a7..a7b801eb55 100644 --- a/Source/OpenTK/Platform/Utilities.cs +++ b/Source/OpenTK/Platform/Utilities.cs @@ -309,12 +309,24 @@ public static IWindowInfo CreateMacOSCarbonWindowInfo(IntPtr windowHandle, bool /// Creates an IWindowInfo instance for the Mac OS X platform. /// /// The handle of the NSWindow. + /// Assumes that the NSWindow's contentView is the NSView we want to attach to our context. /// A new IWindowInfo instance. public static IWindowInfo CreateMacOSWindowInfo(IntPtr windowHandle) { return new OpenTK.Platform.MacOS.CocoaWindowInfo(windowHandle); } + /// + /// Creates an IWindowInfo instance for the Mac OS X platform. + /// + /// The handle of the NSWindow. + /// The handle of the NSView. + /// A new IWindowInfo instance. + public static IWindowInfo CreateMacOSWindowInfo(IntPtr windowHandle, IntPtr viewHandle) + { + return new OpenTK.Platform.MacOS.CocoaWindowInfo(windowHandle, viewHandle); + } + #endregion #region CreateDummyWindowInfo