mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
[317/906] implement gstglwindow for Cocoa (MacOS and GNUstep)
This commit is contained in:
parent
573f54433c
commit
4ccd32c444
3 changed files with 581 additions and 4 deletions
|
@ -2,15 +2,13 @@ include $(GNUSTEP_MAKEFILES)/common.make
|
|||
|
||||
SUBPROJECT_NAME = gstlibsgstgl
|
||||
|
||||
#gstglwindow_win32.c will be replaced by gstglwindow_cocoa.m
|
||||
gstlibsgstgl_C_FILES = \
|
||||
gstglwindow_win32.c \
|
||||
gstgldisplay.c \
|
||||
gstglbuffer.c \
|
||||
gstglfilter.c \
|
||||
gstglshader.c
|
||||
|
||||
#gstlibsgstgl_OBJC_FILES = gstglwindow_cocoa.m
|
||||
gstlibsgstgl_OBJC_FILES = gstglwindow_cocoa.m
|
||||
|
||||
ifeq ($(GNUSTEP_TARGET_OS), mingw32)
|
||||
gstlibsgstgl_INCLUDE_DIRS = \
|
||||
|
|
|
@ -21,9 +21,15 @@
|
|||
#ifndef __GST_GL_WINDOW_H__
|
||||
#define __GST_GL_WINDOW_H__
|
||||
|
||||
|
||||
#if (!GNUSTEP && MACOS)
|
||||
#include <OpenGL/glew.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl.h>
|
||||
#else
|
||||
#include <GL/glew.h>
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
|
573
gst-libs/gst/gl/gstglwindow_cocoa.m
Normal file
573
gst-libs/gst/gl/gstglwindow_cocoa.m
Normal file
|
@ -0,0 +1,573 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstglwindow.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* GstGLNSWindow declaration */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
@interface GstGLNSWindow: NSWindow {
|
||||
BOOL m_isClosed;
|
||||
GstGLWindowPrivate *m_priv;
|
||||
}
|
||||
- (id)initWithContentRect:(NSRect)contentRect
|
||||
styleMask: (unsigned int) styleMask
|
||||
backing: (NSBackingStoreType) bufferingType
|
||||
defer: (BOOL) flag screen: (NSScreen *) aScreen
|
||||
gstWin: (GstGLWindowPrivate *) priv;
|
||||
@end
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* AppThreadPerformer declaration */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
/* Perform actions in the Application thread */
|
||||
@interface AppThreadPerformer : NSObject {
|
||||
GstGLWindowPrivate *m_priv;
|
||||
GstGLWindowCB m_callback;
|
||||
gpointer m_data;
|
||||
}
|
||||
- (id) initWithPrivate : (GstGLWindowPrivate *) priv;
|
||||
- (id) initWithCallback : (GstGLWindowCB) callback userData: (gpointer) data;
|
||||
- (id) initWithAll: (GstGLWindowCB) callback userData: (gpointer) data private: (GstGLWindowPrivate *) priv;
|
||||
- (void) updateWindow;
|
||||
- (void) sendToApp;
|
||||
- (void) setWindow: (NSWindow *) window;
|
||||
- (void) stopApp;
|
||||
@end
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* GstGLWindow */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
#ifndef GNUSTEP
|
||||
static BOOL GSRegisterCurrentThread() { return TRUE; };
|
||||
static GSUnregisterCurrentThread() {};
|
||||
#endif
|
||||
|
||||
#define GST_GL_WINDOW_GET_PRIVATE(o) \
|
||||
(G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_WINDOW, GstGLWindowPrivate))
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0
|
||||
};
|
||||
|
||||
struct _GstGLWindowPrivate
|
||||
{
|
||||
GstGLNSWindow *internal_win_id;
|
||||
GstGLWindowCB draw_cb;
|
||||
gpointer draw_data;
|
||||
GstGLWindowCB2 resize_cb;
|
||||
gpointer resize_data;
|
||||
GstGLWindowCB close_cb;
|
||||
gpointer close_data;
|
||||
gboolean visible;
|
||||
NSWindow *parent;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GstGLWindow, gst_gl_window, G_TYPE_OBJECT);
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "GstGLWindow"
|
||||
|
||||
gboolean _gst_gl_window_debug = FALSE;
|
||||
|
||||
/* Must be called in the gl thread */
|
||||
static void
|
||||
gst_gl_window_finalize (GObject * object)
|
||||
{
|
||||
G_OBJECT_CLASS (gst_gl_window_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gl_window_log_handler (const gchar * domain, GLogLevelFlags flags,
|
||||
const gchar * message, gpointer user_data)
|
||||
{
|
||||
if (_gst_gl_window_debug) {
|
||||
g_log_default_handler (domain, flags, message, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gl_window_class_init (GstGLWindowClass * klass)
|
||||
{
|
||||
GObjectClass *obj_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
g_type_class_add_private (klass, sizeof (GstGLWindowPrivate));
|
||||
|
||||
obj_class->finalize = gst_gl_window_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gl_window_init (GstGLWindow * window)
|
||||
{
|
||||
window->priv = GST_GL_WINDOW_GET_PRIVATE (window);
|
||||
|
||||
if (g_getenv ("GST_GL_WINDOW_DEBUG") != NULL)
|
||||
_gst_gl_window_debug = TRUE;
|
||||
|
||||
g_log_set_handler ("GstGLWindow", G_LOG_LEVEL_DEBUG,
|
||||
gst_gl_window_log_handler, NULL);
|
||||
}
|
||||
|
||||
/* Must be called in the gl thread */
|
||||
GstGLWindow *
|
||||
gst_gl_window_new (gint width, gint height)
|
||||
{
|
||||
GstGLWindow *window = g_object_new (GST_GL_TYPE_WINDOW, NULL);
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
|
||||
NSAutoreleasePool *pool = nil;
|
||||
NSRect rect;
|
||||
|
||||
static gint x = 0;
|
||||
static gint y = 0;
|
||||
|
||||
x += 20;
|
||||
y += 20;
|
||||
|
||||
priv->internal_win_id = nil;
|
||||
priv->draw_cb = NULL;
|
||||
priv->draw_data = NULL;
|
||||
priv->resize_cb = NULL;
|
||||
priv->resize_data = NULL;
|
||||
priv->close_cb = NULL;
|
||||
priv->close_data = NULL;
|
||||
priv->visible = FALSE;
|
||||
priv->parent = nil;
|
||||
|
||||
GSRegisterCurrentThread();
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
rect.origin.x = x;
|
||||
rect.origin.y = y;
|
||||
rect.size.width = width;
|
||||
rect.size.height = height;
|
||||
|
||||
priv->internal_win_id =[[GstGLNSWindow alloc] initWithContentRect:rect
|
||||
styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask)
|
||||
backing: NSBackingStoreBuffered defer: NO screen: nil gstWin: priv];
|
||||
|
||||
[pool release];
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
GQuark
|
||||
gst_gl_window_error_quark (void)
|
||||
{
|
||||
return g_quark_from_static_string ("gst-gl-window-error");
|
||||
}
|
||||
|
||||
void
|
||||
gst_gl_window_set_external_window_id (GstGLWindow * window, guint64 id)
|
||||
{
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
|
||||
if (GSRegisterCurrentThread()) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc] initWithPrivate:priv];
|
||||
[app_thread_performer performSelectorOnMainThread:@selector(setWindow) withObject:(NSWindow *)(gulong)id waitUntilDone:YES];
|
||||
|
||||
[pool release];
|
||||
|
||||
GSUnregisterCurrentThread();
|
||||
}
|
||||
else
|
||||
g_debug ("failed to register current thread, cannot set external window id");
|
||||
}
|
||||
|
||||
void
|
||||
gst_gl_window_set_external_gl_context (GstGLWindow * window, guint64 context)
|
||||
{
|
||||
g_warning ("gst_gl_window_set_external_gl_context: not implemented\n");
|
||||
}
|
||||
|
||||
/* Must be called in the gl thread */
|
||||
void
|
||||
gst_gl_window_set_draw_callback (GstGLWindow * window, GstGLWindowCB callback,
|
||||
gpointer data)
|
||||
{
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
|
||||
priv->draw_cb = callback;
|
||||
priv->draw_data = data;
|
||||
}
|
||||
|
||||
/* Must be called in the gl thread */
|
||||
void
|
||||
gst_gl_window_set_resize_callback (GstGLWindow * window,
|
||||
GstGLWindowCB2 callback, gpointer data)
|
||||
{
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
|
||||
priv->resize_cb = callback;
|
||||
priv->resize_data = data;
|
||||
}
|
||||
|
||||
/* Must be called in the gl thread */
|
||||
void
|
||||
gst_gl_window_set_close_callback (GstGLWindow * window, GstGLWindowCB callback,
|
||||
gpointer data)
|
||||
{
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
|
||||
priv->close_cb = callback;
|
||||
priv->close_data = data;
|
||||
}
|
||||
|
||||
void
|
||||
gst_gl_window_draw_unlocked (GstGLWindow * window)
|
||||
{
|
||||
gst_gl_window_draw (window);
|
||||
}
|
||||
|
||||
/* Thread safe */
|
||||
void
|
||||
gst_gl_window_draw (GstGLWindow * window)
|
||||
{
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
|
||||
if (GSRegisterCurrentThread()) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc] initWithPrivate:priv];
|
||||
[app_thread_performer performSelectorOnMainThread:@selector(updateWindow) withObject:nil waitUntilDone:YES];
|
||||
|
||||
[pool release];
|
||||
|
||||
GSUnregisterCurrentThread();
|
||||
}
|
||||
else
|
||||
g_debug ("failed to register current thread, cannot draw");
|
||||
}
|
||||
|
||||
void
|
||||
gst_gl_window_run_loop (GstGLWindow * window)
|
||||
{
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
g_debug ("begin loop\n");
|
||||
|
||||
if (priv->internal_win_id != nil) {
|
||||
[NSApp setDelegate:priv->internal_win_id];
|
||||
[NSApp run];
|
||||
[priv->internal_win_id release];
|
||||
}
|
||||
|
||||
[pool release];
|
||||
|
||||
g_debug ("end loop\n");
|
||||
}
|
||||
|
||||
/* Thread safe */
|
||||
void
|
||||
gst_gl_window_quit_loop (GstGLWindow * window, GstGLWindowCB callback,
|
||||
gpointer data)
|
||||
{
|
||||
if (window) {
|
||||
if (GSRegisterCurrentThread()) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc] initWithCallback:callback userData:data];
|
||||
[app_thread_performer performSelectorOnMainThread:@selector(stopApp) withObject:nil waitUntilDone:YES];
|
||||
|
||||
[pool release];
|
||||
|
||||
GSUnregisterCurrentThread();
|
||||
}
|
||||
else
|
||||
g_debug ("failed to register current thread, application thread is lost");
|
||||
}
|
||||
}
|
||||
|
||||
/* Thread safe */
|
||||
void
|
||||
gst_gl_window_send_message (GstGLWindow * window, GstGLWindowCB callback,
|
||||
gpointer data)
|
||||
{
|
||||
if (window) {
|
||||
GstGLWindowPrivate *priv = window->priv;
|
||||
if (GSRegisterCurrentThread()) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
AppThreadPerformer* app_thread_performer = [[AppThreadPerformer alloc] initWithAll:callback userData:data private:priv];
|
||||
[app_thread_performer performSelectorOnMainThread:@selector(sendToApp) withObject:nil waitUntilDone:YES];
|
||||
|
||||
[pool release];
|
||||
|
||||
GSUnregisterCurrentThread();
|
||||
}
|
||||
else
|
||||
g_debug ("failed to register current thread, cannot send message");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* GstGLNSWindow implementation */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
@implementation GstGLNSWindow
|
||||
|
||||
- (id) initWithContentRect: (NSRect) contentRect
|
||||
styleMask: (unsigned int) styleMask
|
||||
backing: (NSBackingStoreType) bufferingType
|
||||
defer: (BOOL) flag screen: (NSScreen *) aScreen
|
||||
gstWin: (GstGLWindowPrivate *) priv {
|
||||
|
||||
m_isClosed = NO;
|
||||
m_priv = priv;
|
||||
NSOpenGLView *glView = nil;
|
||||
NSOpenGLPixelFormat *fmt = nil;
|
||||
NSOpenGLContext *glContext = nil;
|
||||
NSOpenGLPixelFormatAttribute attribs[] = {
|
||||
NSOpenGLPFAAccelerated,
|
||||
NSOpenGLPFANoRecovery,
|
||||
NSOpenGLPFADoubleBuffer,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFAAlphaSize, 8,
|
||||
NSOpenGLPFADepthSize, 24,
|
||||
NSOpenGLPFAWindow,
|
||||
0
|
||||
};
|
||||
|
||||
self = [super initWithContentRect: contentRect
|
||||
styleMask: styleMask backing: bufferingType
|
||||
defer: flag screen:aScreen];
|
||||
|
||||
[self setReleasedWhenClosed:NO];
|
||||
|
||||
g_debug ("initializing GstGLNSWindow");
|
||||
|
||||
glView = [NSOpenGLView alloc];
|
||||
|
||||
[self setContentView:glView];
|
||||
|
||||
fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
|
||||
|
||||
if (!fmt) {
|
||||
g_warning ("cannot create NSOpenGLPixelFormat");
|
||||
return nil;
|
||||
}
|
||||
|
||||
glView = [glView initWithFrame:contentRect pixelFormat:fmt];
|
||||
|
||||
glContext = [glView openGLContext];
|
||||
|
||||
/* OpenGL context is made current only one time threre.
|
||||
* Indeed, all OpenGL calls are made in only one thread,
|
||||
* the Application thread */
|
||||
[glContext makeCurrentContext];
|
||||
|
||||
[glContext update];
|
||||
|
||||
/* Back and front buffers are swapped only during the vertical retrace of the monitor.
|
||||
* Discarded if you configured your driver to Never-use-V-Sync.
|
||||
*/
|
||||
NS_DURING {
|
||||
if (glContext) {
|
||||
long swapInterval = 1;
|
||||
[[glView openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
|
||||
}
|
||||
} NS_HANDLER {
|
||||
g_debug ("your back-end does not implement NSOpenglContext::setValues");
|
||||
}
|
||||
NS_ENDHANDLER
|
||||
|
||||
g_debug ("opengl GstGLNSWindow initialized: %d x %d",
|
||||
(int) contentRect.size.width, (int) contentRect.size.height);
|
||||
|
||||
[self setTitle:@"OpenGL renderer"];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL) isClosed {
|
||||
return m_isClosed;
|
||||
}
|
||||
|
||||
- (BOOL) windowShouldClose:(id)sender {
|
||||
g_debug ("user clicked the close button");
|
||||
m_isClosed = YES;
|
||||
if (m_priv->close_cb)
|
||||
m_priv->close_cb (m_priv->close_data);
|
||||
m_priv->draw_cb = NULL;
|
||||
m_priv->draw_data = NULL;
|
||||
m_priv->resize_cb = NULL;
|
||||
m_priv->resize_data = NULL;
|
||||
m_priv->close_cb = NULL;
|
||||
m_priv->close_data = NULL;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) windowDidResize: (NSNotification *) not {
|
||||
NSLog(@"windowDidResize"); //FIXME: seems to be not reached on win32
|
||||
if (m_priv->resize_cb) {
|
||||
NSWindow *window = [not object];
|
||||
NSRect rect = [window frame];
|
||||
m_priv->resize_cb (m_priv->resize_data, rect.size.width, rect.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching: (NSNotification *) not {
|
||||
}
|
||||
|
||||
- (void) applicationWillFinishLaunching: (NSNotification *) not {
|
||||
}
|
||||
|
||||
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
|
||||
/* the application is manually stopped by calling stopApp on the AppThreadPerformer */
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) applicationWillTerminate:(NSNotification *)aNotification {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* AppThreadPerformer implementation */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
@implementation AppThreadPerformer
|
||||
|
||||
- (id) initWithPrivate: (GstGLWindowPrivate *) priv {
|
||||
m_priv = priv;
|
||||
m_callback = NULL;
|
||||
m_data = NULL;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithCallback: (GstGLWindowCB) callback userData: (gpointer) data {
|
||||
m_priv = NULL;
|
||||
m_callback = callback;
|
||||
m_data = data;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithAll: (GstGLWindowCB) callback userData: (gpointer) data private: (GstGLWindowPrivate *) priv {
|
||||
m_priv = priv;
|
||||
m_callback = callback;
|
||||
m_data = data;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) updateWindow {
|
||||
if ([NSApp isRunning]) {
|
||||
|
||||
if (![m_priv->internal_win_id isClosed]) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if (!m_priv->visible) {
|
||||
g_debug ("make the window available");
|
||||
[m_priv->internal_win_id makeMainWindow];
|
||||
//[m_priv->internal_win_id center];
|
||||
[m_priv->internal_win_id orderFront:m_priv->internal_win_id];
|
||||
m_priv->visible = TRUE;
|
||||
}
|
||||
|
||||
if (m_priv->parent) {
|
||||
NSRect parent_rect = [[m_priv->internal_win_id parentWindow] frame];
|
||||
NSRect rect = [m_priv->internal_win_id frame];
|
||||
|
||||
if (rect.origin.x != parent_rect.origin.x || rect.origin.y != parent_rect.origin.y ||
|
||||
rect.size.width != parent_rect.size.width || rect.size.height != parent_rect.size.height) {
|
||||
|
||||
[m_priv->internal_win_id setFrame:parent_rect display:YES];
|
||||
|
||||
g_debug ("parent resize: %d, %d, %d, %d\n",
|
||||
(int) parent_rect.origin.x, (int) parent_rect.origin.y,
|
||||
(int) parent_rect.size.width, (int) parent_rect.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
if ([[m_priv->internal_win_id contentView] lockFocusIfCanDraw]) {
|
||||
/* draw opengl scene in the back buffer */
|
||||
m_priv->draw_cb (m_priv->draw_data);
|
||||
/* Copy the back buffer to the front buffer */
|
||||
[[[m_priv->internal_win_id contentView] openGLContext] flushBuffer];
|
||||
[[m_priv->internal_win_id contentView] unlockFocus];
|
||||
}
|
||||
|
||||
[pool release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) sendToApp {
|
||||
if ([NSApp isRunning]) {
|
||||
if (![m_priv->internal_win_id isClosed]) {
|
||||
m_callback (m_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setWindow: (NSWindow *) window {
|
||||
if ([NSApp isRunning]) {
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
[m_priv->internal_win_id setParentWindow:window];
|
||||
|
||||
m_priv->parent = window;
|
||||
|
||||
[m_priv->internal_win_id setFrame:[window frame] display:YES];
|
||||
|
||||
[pool release];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) stopApp {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if ([NSApp isRunning])
|
||||
[NSApp stop:self];
|
||||
|
||||
[pool release];
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in a new issue