diff --git a/caca/driver_cocoa.m b/caca/driver_cocoa.m index b2a297a..7ea2e5e 100644 --- a/caca/driver_cocoa.m +++ b/caca/driver_cocoa.m @@ -1,7 +1,6 @@ /* * libcaca Colour ASCII-Art library - * Copyright (c) 2006 Olivier Gutknecht - * 2006 Sam Hocevar + * Copyright (c) 2006 Colin Delacroix * All Rights Reserved * * $Id$ @@ -21,328 +20,947 @@ #if defined USE_COCOA -#include -#include - -#import -#include +#import #include "caca.h" #include "caca_internals.h" #include "cucul.h" #include "cucul_internals.h" +//#define COCOA_DEBUG + +// many ways to draw the chars : +// - NSString and drawInRect:withAttributes: or drawWithRect:options:attributes: +// - NSAttributedString and drawInRect: or drawWithRect:options: +// - NSTextLayout and co. +// - Quartz 2D +// - ATSUI (more accessible from carbon) +// 2 firsts are high level cocoa, 3rd is low-level cocoa, other are untested +// also see http://www.cocoabuilder.com/archive/message/cocoa/2004/11/18/121928 +// update: actually high-level is faster, so keep it like that +//#define USE_LOWLEVEL_COCOA 1 + +// build a complete color table cache for the view +#define PRECACHE_WHOLE_COLOR_TABLE 1 + +#define USE_RGB12_FGBG 1 + +//#define USE_GLOBAL_AUTORELEASE_POOL 1 + +#ifdef COCOA_DEBUG +#define debug_log NSLog +#else +#define debug_log(...) +#endif + +#define NCOLORS 0x1000 + +static BOOL s_quit = NO; +static BOOL s_quitting = NO; + @interface CacaView : NSView { - NSFont* _font; - NSRect _r; - int xx; - int h, w; - uint32_t *_attrs; - uint32_t *_chars; - CFMutableDictionaryRef _cache; + //NSFont* _font; + NSRect _font_rect; + unsigned int _h, _w; + uint32_t* _attrs; + uint32_t* _chars; + NSRect* _bkg_rects; + NSColor** _bkg_colors; +#ifdef PRECACHE_WHOLE_COLOR_TABLE + NSColor* _colorCache[NCOLORS]; +#else + NSMutableDictionary* _colorCache; +#endif + NSMutableDictionary* _attrDict; + NSMutableDictionary* _attrDictUnderline; // lame optim +#ifdef USE_LOWLEVEL_COCOA + NSTextStorage* _textStorage; + NSLayoutManager* _layoutManager; + NSTextContainer* _textContainer; +#endif } + - (void)setFont:(NSFont *)aFont; - (void)updateBuffersFromCaca:(caca_display_t *)dp; +@end +@interface NSColor(Caca) ++ (NSColor *)colorFromRgb12:(uint16_t) ui_rgb12; @end @implementation CacaView +- (id)initWithFrame:(NSRect)frameRect +{ + self = [super initWithFrame:frameRect]; + if(!self) + return nil; -- (void)keyDown:(NSEvent *)theEvent + [[self window] makeFirstResponder:self]; + +#ifdef PRECACHE_WHOLE_COLOR_TABLE + unsigned int i; + for(i = 0; i < NCOLORS; i++) + _colorCache[i] = [[NSColor colorFromRgb12:i] retain]; +#else + _colorCache = [[NSMutableDictionary alloc] initWithCapacity:NCOLORS]; +#endif + _attrDict = [[NSMutableDictionary alloc] initWithCapacity:3]; + _attrDictUnderline = [[NSMutableDictionary alloc] initWithCapacity:3]; + [_attrDictUnderline setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] + forKey:NSUnderlineStyleAttributeName]; +#ifdef USE_LOWLEVEL_COCOA + _textStorage = [[NSTextStorage alloc] init]; + _layoutManager = [[NSLayoutManager alloc] init]; + _textContainer = [[NSTextContainer alloc] init]; + [_textContainer setLineFragmentPadding:0.0]; + [_layoutManager addTextContainer:_textContainer]; + [_textStorage addLayoutManager:_layoutManager]; +#endif + + return self; +} + +- (void)dealloc { - NSLog(@"key %@", theEvent); + //[_font release]; +#ifdef PRECACHE_WHOLE_COLOR_TABLE + unsigned short i; + for(i = 0; i < NCOLORS; i++) + [_colorCache[i] release]; +#else + [_colorCache release]; +#endif + [_attrDict release]; + [_attrDictUnderline release]; +#ifdef USE_LOWLEVEL_COCOA + [_textStorage release]; + [_layoutManager release]; + [_textContainer release]; +#endif + if(_attrs) + free(_attrs); + if(_bkg_rects) + free(_bkg_rects); + if(_bkg_colors) + free(_bkg_colors); + + [super dealloc]; } -- (void)mouseMoved:(NSEvent *)theEvent +// to accelerate the window drawing speed +- (BOOL)isOpaque { - NSLog(@"mouse %@", theEvent); + return YES; +} + +- (BOOL)isFlipped +{ + return YES; } - (void)setupNewSize { - int fw = _r.size.width; - int fh = _r.size.height; - w = [self bounds].size.width /fw; - h = [self bounds].size.height / fh; -// NSLog(@"W %f %f %f %d %f", [self bounds].size.width , _r.size.width, [self bounds].size.width / _r.size.width, w, [self bounds].size.width-(w*fw)); -// NSLog(@"H %f %f %f %d %f", [self bounds].size.height , _r.size.height, [self bounds].size.height / _r.size.height, h, [self bounds].size.height-(h*fh)); + float fw = _font_rect.size.width; + float fh = _font_rect.size.height; + _w = ceilf([self bounds].size.width / fw); + _h = ceilf([self bounds].size.height / fh); + debug_log(@"fw=%f selfw=%f %u %f", fw, [self bounds].size.width, + _w, [self bounds].size.width-(_w*fw)); + debug_log(@"fh=%f selfh=%f %u %f", fh, [self bounds].size.height, + _h, [self bounds].size.height-(_h*fh)); } -- (id)initWithFrame:(NSRect)frameRect +- (void)keyDown:(NSEvent *)theEvent { - if((self = [super initWithFrame:frameRect]) != nil) { - [[self window] makeFirstResponder:self]; - } - return self; + NSLog(@"key %@", theEvent); } -- (NSFont *)font +- (void)mouseMoved:(NSEvent *)theEvent { - return _font; + NSLog(@"mouse %@", theEvent); } - (void)setFont:(NSFont *)aFont { - [_font release]; - _font = [aFont retain]; - _r = [_font boundingRectForFont]; - _r = NSMakeRect(0, 0, ceilf(_r.size.width), ceilf(_r.size.height)); + //[_font release]; + //_font = [aFont retain]; + _font_rect = [aFont boundingRectForFont]; + _font_rect = NSMakeRect(0, 0, ceilf(_font_rect.size.width), ceilf(_font_rect.size.height)); [self setupNewSize]; + [_attrDict setObject:aFont forKey:NSFontAttributeName]; + [_attrDictUnderline setObject:aFont forKey:NSFontAttributeName]; [aFont set]; } -- (void)updateBuffersFromCaca:(caca_display_t *)dp +- (void)resizeIfNeeded:(caca_display_t *)dp { -NSLog(@"update buffers"); - if(_attrs) - free(_attrs); + if( _w != dp->cv->width || _h != dp->cv->height + || !_attrs || !_bkg_rects || !_bkg_colors) + { + debug_log(@"%s resize to %ux%u", _cmd, _w, _h); + + _w = dp->cv->width; + _h = dp->cv->height; + + if(_attrs) + free(_attrs); + _attrs = malloc(_w * _h * sizeof(uint32_t) * 2); + + if(_bkg_rects) + free(_bkg_rects); + _bkg_rects = malloc(_w * _h * sizeof(NSRect)); + + if(_bkg_colors) + free(_bkg_colors); + _bkg_colors = malloc(_w * _h * sizeof(NSColor*)); + + [[self window] setContentSize: NSMakeSize(dp->cv->width * _font_rect.size.width, + dp->cv->height * _font_rect.size.height)]; + } +} - _attrs = malloc(dp->cv->width * dp->cv->height * sizeof(uint32_t) * 2); - _chars = _attrs + dp->cv->width * dp->cv->height; - memcpy(_attrs, dp->cv->attrs, dp->cv->width * dp->cv->height * sizeof(uint32_t)); - memcpy(_chars, dp->cv->chars, dp->cv->width * dp->cv->height * sizeof(uint32_t)); +- (void)updateBuffersFromCaca:(caca_display_t *)dp +{ + [self resizeIfNeeded:dp]; - w = dp->cv->width; - h = dp->cv->height; + if(_attrs) + { + _chars = _attrs + _w * _h; + memcpy(_attrs, dp->cv->attrs, _w * _h * sizeof(uint32_t)); + memcpy(_chars, dp->cv->chars, _w * _h * sizeof(uint32_t)); - [self setNeedsDisplay:TRUE]; + [self setNeedsDisplay:TRUE]; + } } - (void)drawRect:(NSRect)rect { //if([self inLiveResize]) [self setupNewSize]; - int x, y; - int fw = _r.size.width; - int fh = _r.size.height; - uint32_t *attrs = _attrs; - uint32_t *chars = _chars; - NSGraphicsContext* viewContext = [NSGraphicsContext currentContext]; - if(!attrs || !chars) + + if(!_attrs || !_chars) { [[NSColor blueColor] set]; NSRectFill(rect); return; } + + unsigned int x, y; + float fw = _font_rect.size.width; + float fh = _font_rect.size.height; + uint32_t* attrs; + uint32_t* chars = _chars; + + /* first take care of the background */ [[NSColor blackColor] set]; NSRectFill(rect); - for(y = 0; y < h; y++) + unsigned int arrayLength = 0; + for(y = 0; y < _h; y++) { - for(x = 0; x < w; x++) + unsigned int yoff = y * fh; + for(x = 0; x < _w; x++) { - unichar c = *chars++; - uint8_t argb[8]; - _cucul_attr_to_argb4(*attrs++, argb); - - /* FIXME: factorise this colour stuff */ - NSRect r = NSMakeRect(x * fw, y * fh, fw, fh); - [[NSColor colorWithDeviceRed: (float)argb[1] / 255.0 - green: (float)argb[2] / 255.0 - blue: (float)argb[3] / 255.0 - alpha: 1.0] setFill]; - [[NSColor colorWithDeviceRed: (float)argb[5] / 255.0 - green: (float)argb[6] / 255.0 - blue: (float)argb[7] / 255.0 - alpha: 1.0] setStroke]; - NSRectFill(r); -// NSLog(@"%d %d %C %d %d %@ %@ %@", x, y, c, fg, bg, NSStringFromRect(r), _colormap[fg], _colormap[bg]); -// [[NSString stringWithCharacters:&c length:1] drawInRect:r withAttributes:[NSDictionary dictionaryWithObject:_colormap[fg] forKey:NSForegroundColorAttributeName]]; - [[NSColor colorWithDeviceRed: (float)argb[5] / 255.0 - green: (float)argb[6] / 255.0 - blue: (float)argb[7] / 255.0 - alpha: 1.0] setFill]; - [[NSColor colorWithDeviceRed: (float)argb[5] / 255.0 - green: (float)argb[6] / 255.0 - blue: (float)argb[7] / 255.0 - alpha: 1.0] setStroke]; - [[NSString stringWithCharacters:&c length:1] drawInRect:r withAttributes:nil]; -// [[NSString stringWithCharacters:&c length:1] drawInRect:r withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: -// _colormap[fg], NSForegroundColorAttributeName, _colormap[bg], NSBackgroundColorAttributeName, nil]]; -} + NSRect r = NSMakeRect(x * fw, yoff, fw, fh); + if(NSIntersectsRect(r, rect)) + { + attrs = _attrs + x + y * _w; + NSColor* color = nil; + #if USE_RGB12_FGBG + uint16_t bg = _cucul_attr_to_rgb12bg(*attrs); + if(bg) + { + #ifdef PRECACHE_WHOLE_COLOR_TABLE + color = _colorCache[bg]; + #else + NSNumber* numberBg = [NSNumber numberWithInt:bg]; + color = [_colorCache objectForKey:numberBg]; + if(!color) + { + color = [NSColor colorFromRgb12:bg]; + if(color) + [_colorCache setObject:color forKey:numberBg]; + } + #endif + } + #else + uint8_t argb[8]; + _cucul_attr_to_argb4(*attrs, argb); + color = [NSColor colorWithCalibratedRed:((float)argb[1]) / 15.0 + green:((float)argb[2]) / 15.0 + blue:((float)argb[3]) / 15.0 + alpha:1.0]; + #endif + if(color) + { + _bkg_colors[arrayLength] = color; + _bkg_rects[arrayLength++] = r; + } + } + } + } + NSRectFillListWithColors(_bkg_rects, _bkg_colors, arrayLength); + + /* Then print the foreground characters */ + for(y = 0; y < _h; y++) + { + unsigned int yoff = y * fh; + for(x = 0; x < _w; x++, chars++) + { + attrs = _attrs + x + y * _w; + + /* Skip spaces */ + if(*chars <= 0x00000020) + continue; + + if(*chars == CUCUL_MAGIC_FULLWIDTH) + continue; + + /* Plain ASCII, no problem. */ + // TODO: test me with wide chars + //if(*chars > 0x00000020 && *chars < 0x00000080) + { + NSRect r = NSMakeRect(x * fw + 1, yoff, fw - 1, fh); + if(NSIntersectsRect(r, rect)) + { + NSColor* color = nil; + #if USE_RGB12_FGBG + uint16_t fg = _cucul_attr_to_rgb12fg(*attrs); + #ifdef PRECACHE_WHOLE_COLOR_TABLE + color = _colorCache[fg]; + #else // PRECACHE_WHOLE_COLOR_TABLE + NSNumber* numberFg = [NSNumber numberWithInt:fg]; + color = [_colorCache objectForKey:numberFg]; + if(!color) + { + color = [NSColor colorFromRgb12:fg]; + if(color) + [_colorCache setObject:color forKey:numberFg]; + } + #endif // PRECACHE_WHOLE_COLOR_TABLE + #else // USE_RGB12_FGBG + uint8_t argb[8]; + _cucul_attr_to_argb4(*attrs, argb); + debug_log(@"x,y=[%d,%d] r,g,b back=[%u %u %u] front=[%u %u %u]", + x, y, argb[1], argb[2], argb[3], argb[5], argb[6], argb[7]); + color = [NSColor colorWithCalibratedRed:((float)argb[5]) / 15.0 + green:((float)argb[6]) / 15.0 + blue:((float)argb[7]) / 15.0 + alpha:1.0]; + #endif // USE_RGB12_FGBG + + if(color) + { + NSMutableDictionary* attrDict = (*attrs & CUCUL_UNDERLINE) ? + _attrDictUnderline : _attrDict; + [attrDict setObject:color forKey:NSForegroundColorAttributeName]; + + unichar ch = *chars; + NSString* str = [[NSString alloc] initWithCharacters:&ch length:1]; + + #ifdef USE_LOWLEVEL_COCOA + [[_textStorage mutableString] setString:str]; + [_textStorage setAttributes:attrDict range:NSMakeRange(0, 1)]; + [_layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, 1) atPoint:r.origin]; + #else + [str drawInRect:r withAttributes:attrDict]; + #endif + [str release]; + } + } + continue; + } + } } - [NSGraphicsContext setCurrentContext:viewContext]; } + @end struct driver_private { NSWindow* window; - id view; - NSFont* font; + CacaView* view; +#ifdef USE_GLOBAL_AUTORELEASE_POOL NSAutoreleasePool* pool; +#endif }; +//============================================================================ +// NSApplication(Caca) +//============================================================================ -//-------------------------------------------------------------------------------------------- -static OSStatus -AppEventHandler(EventHandlerCallRef inCaller, EventRef inEvent, void* inRefcon) +@implementation NSApplication(Caca) +- (void)setRunning { - OSStatus result = eventNotHandledErr; + _running = 1; +} +@end + +//============================================================================ +// NSColor(Caca) +//============================================================================ + +@implementation NSColor(Caca) ++ (NSColor *)colorFromRgb12:(uint16_t)ui_rgb12 +{ + float red = ((float)((ui_rgb12 & 0x0f00) >> 3)) / 15.0, + green = ((float)((ui_rgb12 & 0x00f0) >> 2)) / 15.0, + blue = ((float)( ui_rgb12 & 0x000f) ) / 15.0; + return [NSColor colorWithDeviceRed:red green:green + blue:blue alpha:1.0]; +} +@end - switch(GetEventClass(inEvent)) +//============================================================================ +// CacaWindowDelegate +//============================================================================ + +@interface CacaWindowDelegate : NSObject +@end + +@implementation CacaWindowDelegate +- (BOOL)windowShouldClose:(id)sender +{ + debug_log(@"%s", _cmd); + [NSApp terminate:self]; + return NO; +} +@end + +//============================================================================ +// CacaAppDelegate +//============================================================================ + +@interface CacaAppDelegate : NSObject +@end + +@implementation CacaAppDelegate : NSObject +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + s_quit = YES; + return NSTerminateCancel; +} +@end + +/* setAppleMenu disappeared from the headers in 10.4 */ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 +@interface NSApplication(NSAppleMenu) +- (void)setAppleMenu:(NSMenu *)menu; +@end +#endif + +//============================================================================ +// utility methods +//============================================================================ + +static NSString* get_application_name() +{ + NSString* appName = [[NSBundle mainBundle] objectForInfoDictionaryKey: + @"CFBundleName"]; + if(![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +static void create_application_menus() +{ + /* Create the main menu bar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + + /* Create the application menu */ + NSString* appName = get_application_name(); + NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + NSString* title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) + keyEquivalent:@"h"]; + + id menuItem = [appleMenu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) + keyEquivalent:@"q"]; + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + [menuItem release]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + [appleMenu release]; + + /* Create the window menu */ + NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:menuItem]; + [menuItem release]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + [windowMenu release]; +} + +static void register_cocoa_app(caca_display_t *dp) +{ + ProcessSerialNumber psn; + if(!GetCurrentProcess(&psn)) { - case kEventClassCommand: - { - HICommandExtended cmd; - verify_noerr(GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd)); + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + SetFrontProcess(&psn); + } - switch(GetEventKind(inEvent)) - { - case kEventCommandProcess: - switch(cmd.commandID) - { - default: - break; - } - break; - } - break; - } + if(NSApp == nil) + { + [NSApplication sharedApplication]; - default: - break; + if(![NSApp mainMenu]) + create_application_menus(); + + [NSApp finishLaunching]; } - return result; + if ([NSApp delegate] == nil) + [NSApp setDelegate:[[CacaAppDelegate alloc] init]]; + + [NSApp setRunning]; } +static __inline__ void convert_NSRect(NSRect *r) +{ + float mb_height = 38.0; // [[NSApp mainMenu] menuBarHeight] is 0 - wtf ? + /*debug_log(@"%@ %f %f %d %d %d", [NSApp mainMenu], + [[NSApp mainMenu] menuBarHeight], mb_height, + (int)CGDisplayPixelsHigh(kCGDirectMainDisplay), + (int)r->origin.y, (int)r->size.height);*/ + r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - mb_height + - r->origin.y - r->size.height; +} -static OSStatus -WindowEventHandler(EventHandlerCallRef inCaller, EventRef inEvent, void* inRefcon) +static void create_first_window(caca_display_t *dp) { - OSStatus err = eventNotHandledErr; + NSFont* font = [NSFont fontWithName:@"Monaco" size:10]; + NSRect fontRect = [font boundingRectForFont]; + fontRect = NSMakeRect(0, 0, ceilf(fontRect.size.width), ceilf(fontRect.size.height)); + NSRect windowRect = NSMakeRect(20, 20, dp->cv->width * fontRect.size.width, + dp->cv->height * fontRect.size.height); + convert_NSRect(&windowRect); + + CacaView* view = [[CacaView alloc] initWithFrame:windowRect]; + NSWindow* win = [[NSWindow alloc] initWithContentRect:windowRect + styleMask: NSTitledWindowMask + //| NSResizableWindowMask + | NSClosableWindowMask + | NSWindowMiniaturizeButton + backing:NSBackingStoreBuffered + defer:NO]; + + NSString* appName = get_application_name(); + if(appName) + [win setTitle: appName]; + [win setDelegate:[CacaWindowDelegate new]]; + [win setContentView:view]; + [view setFont:font]; + [win makeKeyAndOrderFront:nil]; + + dp->drv.p->window = win; + dp->drv.p->view = view; +} - switch(GetEventClass(inEvent)) +static unsigned int get_caca_keycode(NSEvent* event) +{ + unsigned int caca_keycode = 0; + /* + unsigned short mac_keycode = [event keyCode]; + debug_log(@"keycode %u (%x)", mac_keycode, mac_keycode); + switch(mac_keycode) { - case kEventClassCommand: + } + */ + if(/*!caca_keycode &&*/ ([event modifierFlags] & NSControlKeyMask)) + { + NSString *chars = [event charactersIgnoringModifiers]; + unichar ch = [chars characterAtIndex: 0]; + // CACA_KEY_CTRL_A -> CACA_KEY_CTRL_Z + if(ch >= 'a' && ch <= 'z') + caca_keycode = CACA_KEY_CTRL_A + ch - 'a'; + } + + if(!caca_keycode) + { + NSString *chars = [event characters]; + unichar ch = 0; + if([chars length]) + ch = [chars characterAtIndex: 0]; + switch(ch) { - HICommandExtended cmd; - verify_noerr(GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd)); + case NSUpArrowFunctionKey: + caca_keycode = CACA_KEY_UP; + break; + case NSDownArrowFunctionKey: + caca_keycode = CACA_KEY_DOWN; + break; + case NSLeftArrowFunctionKey: + caca_keycode = CACA_KEY_LEFT; + break; + case NSRightArrowFunctionKey: + caca_keycode = CACA_KEY_RIGHT; + break; + case 27: + caca_keycode = CACA_KEY_ESCAPE; + break; + case NSDeleteCharacter: + caca_keycode = CACA_KEY_DELETE; + break; + case NSBackspaceCharacter: + caca_keycode = CACA_KEY_BACKSPACE; + break; + case NSTabCharacter: + caca_keycode = CACA_KEY_TAB; + break; + case NSNewlineCharacter: + case NSCarriageReturnCharacter: + caca_keycode = CACA_KEY_RETURN; + break; + case NSPageUpFunctionKey: + caca_keycode = CACA_KEY_PAGEUP; + break; + case NSPageDownFunctionKey: + caca_keycode = CACA_KEY_PAGEDOWN; + break; + case NSF1FunctionKey: + caca_keycode = CACA_KEY_F1; + break; + case NSF2FunctionKey: + caca_keycode = CACA_KEY_F2; + break; + case NSF3FunctionKey: + caca_keycode = CACA_KEY_F3; + break; + case NSF4FunctionKey: + caca_keycode = CACA_KEY_F4; + break; + case NSF5FunctionKey: + caca_keycode = CACA_KEY_F5; + break; + case NSF6FunctionKey: + caca_keycode = CACA_KEY_F6; + break; + case NSF7FunctionKey: + caca_keycode = CACA_KEY_F7; + break; + case NSF8FunctionKey: + caca_keycode = CACA_KEY_F8; + break; + case NSF9FunctionKey: + caca_keycode = CACA_KEY_F9; + break; + case NSF10FunctionKey: + caca_keycode = CACA_KEY_F10; + break; + case NSF11FunctionKey: + caca_keycode = CACA_KEY_F11; + break; + case NSF12FunctionKey: + caca_keycode = CACA_KEY_F12; + break; + case NSF13FunctionKey: + caca_keycode = CACA_KEY_F13; + break; + case NSF14FunctionKey: + caca_keycode = CACA_KEY_F14; + break; + case NSF15FunctionKey: + caca_keycode = CACA_KEY_F15; + break; + case NSPauseFunctionKey: + caca_keycode = CACA_KEY_PAUSE; + break; + case NSInsertFunctionKey: + debug_log(@"insert key"); + caca_keycode = CACA_KEY_INSERT; + break; + case NSHomeFunctionKey: + caca_keycode = CACA_KEY_HOME; + break; + case NSEndFunctionKey: + caca_keycode = CACA_KEY_END; + break; + } + } - switch(GetEventKind(inEvent)) - { - case kEventCommandProcess: - switch(cmd.commandID) - { - // Add your own command-handling cases here - default: - break; - } - break; - } + return caca_keycode; +} + +static BOOL handle_key_event(struct caca_event *ev, NSEvent* event) +{ + if(!ev || !event) + return NO; + + BOOL eventHandled = NO; + + if([event modifierFlags] & NSCommandKeyMask) + { + // let the system handle the Apple-commands for now + return NO; + } + + switch ([event type]) { + case NSKeyDown: + /* test [event isARepeat] ? */ + ev->type = CACA_EVENT_KEY_PRESS; break; + case NSKeyUp: + ev->type = CACA_EVENT_KEY_RELEASE; + break; + default: + ; + } + + unsigned int caca_keycode = get_caca_keycode(event); + if(caca_keycode) + { + ev->data.key.ch = caca_keycode; + eventHandled = YES; + } + else + { + NSString *chars = [event characters]; + unichar mac_keycode = 0; + if([chars length]) + mac_keycode = [chars characterAtIndex: 0]; + if(mac_keycode) + { + ev->data.key.ch = mac_keycode; + ev->data.key.utf32 = (uint32_t)mac_keycode; + ev->data.key.utf8[0] = mac_keycode & 0x00ff; // FIXME: endianness + ev->data.key.utf8[1] = mac_keycode & 0xff00; + + eventHandled = YES; } + } - default: + return eventHandled; +} + +// TODO: handle CACA_EVENT_RESIZE +static BOOL handle_mouse_event(caca_display_t *dp, struct caca_event *ev, + NSEvent* event) +{ + if(!ev || !event) + return NO; + + switch ([event type]) { + case NSLeftMouseDown: + ev->type = CACA_EVENT_MOUSE_PRESS; + ev->data.mouse.button = 1; + break; + case NSLeftMouseUp: + ev->type = CACA_EVENT_MOUSE_RELEASE; + ev->data.mouse.button = 1; + break; + case NSRightMouseDown: + ev->type = CACA_EVENT_MOUSE_PRESS; + ev->data.mouse.button = 2; + break; + case NSRightMouseUp: + ev->type = CACA_EVENT_MOUSE_RELEASE; + ev->data.mouse.button = 2; break; + case NSMouseMoved: + { + NSPoint mouseLoc = [NSEvent mouseLocation]; + unsigned int mouse_x = round(mouseLoc.x); + unsigned int mouse_y = round(mouseLoc.y); + if(dp->mouse.x == mouse_x && dp->mouse.y == mouse_y) + break; + + dp->mouse.x = mouse_x; + dp->mouse.y = mouse_y; + + ev->type = CACA_EVENT_MOUSE_MOTION; + ev->data.mouse.x = dp->mouse.x; + ev->data.mouse.y = dp->mouse.y; + break; + } + default: + ; } - return err; + return YES; } + +//============================================================================ +// caca driver methods +//============================================================================ + static int cocoa_init_graphics(caca_display_t *dp) { - int i; + debug_log(@"%s dp->cv: %ux%u", __PRETTY_FUNCTION__, + dp->cv->width, dp->cv->height); + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; dp->drv.p = malloc(sizeof(struct driver_private)); if(dp->drv.p == NULL) return -1; - Handle aHand = GetNewMBar(128); - SetMenuBar(aHand); - MenuHandle menu = GetMenuHandle(128); - CreateStandardWindowMenu(0, &menu); - InsertMenu(menu, 0); - - DrawMenuBar(); - DisposeHandle(aHand); - - NSAutoreleasePool* p = [[NSAutoreleasePool alloc] init]; -// [NSApplication sharedApplication]; - NSApplicationLoad(); - - NSLog(@"Start"); - - NSFont* f = [NSFont fontWithName:@"Monaco" size:10]; - NSRect r = [f boundingRectForFont]; - - NSRect windowRect = NSMakeRect(0, 0, dp->cv->width * r.size.width, - dp->cv->height * r.size.height); - CacaView* v = [[[CacaView alloc] initWithFrame:windowRect] autorelease]; - -// [NSApp setMainMenu:[[[NSMenu alloc] initWithTitle:@"Caca"] autorelease]]; - //[NSApp setAppleMenu:[[[NSMenu alloc] initWithTitle:@"ca"] autorelease]]; - //[NSApp setDelegate:v]; - - NSWindow* w = [[NSWindow alloc] - initWithContentRect:windowRect - styleMask:NSTitledWindowMask - |NSResizableWindowMask - |NSClosableWindowMask - |NSWindowMiniaturizeButton - backing:NSBackingStoreBuffered - defer:NO]; - [w setContentView:v]; - [v setFont:f]; - [v setupNewSize]; - [w setFrameTopLeftPoint:NSMakePoint(650, 650)]; - [w makeKeyAndOrderFront:w]; - [w setLevel:NSFloatingWindowLevel]; - [w orderFrontRegardless]; - - dp->drv.p->window = w; - dp->drv.p->view = v; - dp->drv.p->font = f; - dp->drv.p->pool = p; - -// NSLog(@"update4 %d %d", w, h); -// _cucul_set_size(dp->cv, windowRect.size.width, windowRect.size.height); - OSStatus err; - static const EventTypeSpec kAppEvents[] = - { - { kEventClassCommand, kEventCommandProcess } - }; - InstallApplicationEventHandler(NewEventHandlerUPP(AppEventHandler), - GetEventTypeCount(kAppEvents), kAppEvents, - 0, NULL); - NSLog(@"a"); - /* Should not be run here, should it? */ - RunApplicationEventLoop(); - NSLog(@"ab"); + unsigned int width = dp->cv->width, height = dp->cv->height; + _cucul_set_canvas_size(dp->cv, width ? width : 80, height ? height : 32); + + // first create a full cocoa app if the host has no bundle + if(![[NSBundle mainBundle] bundleIdentifier]) + register_cocoa_app(dp); + create_first_window(dp); + +#ifdef USE_GLOBAL_AUTORELEASE_POOL + dp->drv.p->pool = pool; +#else + [pool release]; +#endif + return 0; } - -static void cocoa_display(caca_display_t *dp) +static int cocoa_end_graphics(caca_display_t *dp) { -//NSLog(@"display1"); - [dp->drv.p->view updateBuffersFromCaca:dp]; -#if 1 - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; -#else - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[[NSCalendarDate date] - dateByAddingYears:0 months:(int)0 days:(int)0 hours:(int)0 minutes:(int)0 seconds:(int)5]]; + debug_log(@"%s dp->cv: %ux%u", __PRETTY_FUNCTION__, + dp->cv->width, dp->cv->height); + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [dp->drv.p->window close]; + CacaWindowDelegate* delegate = [dp->drv.p->window delegate]; + [dp->drv.p->window setDelegate:nil]; + [delegate release]; + // don't release the window yourself + //[dp->drv.p->window release]; +#ifdef USE_GLOBAL_AUTORELEASE_POOL + [dp->drv.p->pool release]; #endif -//NSLog(@"display2"); + free(dp->drv.p); + debug_log(@"%s end", __PRETTY_FUNCTION__); + [pool release]; + + return 0; } -static void cocoa_handle_resize(caca_display_t *dp) +static void cocoa_display(caca_display_t *dp) { - dp->resize.w = dp->cv->width; - dp->resize.h = dp->cv->height; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [dp->drv.p->view updateBuffersFromCaca:dp]; + [pool release]; } static int cocoa_get_event(caca_display_t *dp, struct caca_event *ev) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate date]]; + if(s_quit) + { + if(s_quitting) + { + // host app isn't handling the quit event properly, aborting + debug_log(@"duplicate quit event, aborting."); + abort(); + } + debug_log(@"posting quit event."); + ev->type = CACA_EVENT_QUIT; + s_quitting = YES; + return 1; + } + + BOOL eventHandled = NO, forceRedispatch = NO; ev->type = CACA_EVENT_NONE; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + if([NSApp isRunning]) + { + NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if(event) + { + switch([event type]) + { + case NSKeyDown: + case NSKeyUp: + eventHandled = handle_key_event(ev, event); + break; + + case NSFlagsChanged: + break; + + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: + if([NSApp isActive]) + { + eventHandled = handle_mouse_event(dp, ev, event); + forceRedispatch = YES; + } + else + { + [NSApp sendEvent:event]; + eventHandled = YES; + } + break; + + default: + ; + } + + if(!eventHandled || forceRedispatch) + [NSApp sendEvent:event]; + } + } + [pool release]; + + if(eventHandled) + return 1; + return 0; } +static void cocoa_handle_resize(caca_display_t *dp) +{ + debug_log(@"%s", __PRETTY_FUNCTION__); + dp->resize.w = dp->cv->width; + dp->resize.h = dp->cv->height; +} + static int cocoa_set_display_title(caca_display_t *dp, char const *title) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [dp->drv.p->window setTitle:[NSString stringWithUTF8String:title]]; + [pool release]; return 0; } @@ -358,21 +976,13 @@ static unsigned int cocoa_get_display_height(caca_display_t *dp) static void cocoa_set_mouse(caca_display_t *dp, int flag) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; if(flag) [[NSCursor arrowCursor] set]; else { [[NSCursor disappearingItemCursor] set]; } -} - -static int cocoa_end_graphics(caca_display_t *dp) -{ - [dp->drv.p->window close]; - [dp->drv.p->window release]; - [dp->drv.p->pool release]; - [dp->drv.p->font release]; - free(dp->drv.p); - return 0; + [pool release]; } /* @@ -397,4 +1007,3 @@ int cocoa_install(caca_display_t *dp) } #endif /* USE_COCOA */ - diff --git a/configure.ac b/configure.ac index c0b851c..882fa11 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ AC_PROG_CPP AC_PROG_CXX AC_PROG_CXXCPP _AM_DEPENDENCIES([OBJC]) -OBJC="${CXX}" +OBJC="${CC}" AC_SUBST(OBJC) AC_SUBST(OBJCFLAGS) AM_PROG_AS @@ -199,20 +199,41 @@ fi if test "${enable_cocoa}" != "no"; then ac_cv_my_have_cocoa="no" - AC_LANG_PUSH(C++) - savedCPPFLAGS="${CPPFLAGS}" - CPPFLAGS="${CPPFLAGS} -ObjC" AC_CHECK_HEADERS(Cocoa/Cocoa.h, [ac_cv_my_have_cocoa="yes"]) if test "${ac_cv_my_have_cocoa}" = "yes"; then + case x${target} in + xpowerpc*darwin*) + # 10.3 needed to link with X11 + MACOSX_SDK=/Developer/SDKs/MacOSX10.3.9.sdk + GCC_VERSION=3.3 + ARCH="-arch ppc" + MACOSX_SDK_CFLAGS="-nostdinc -isystem ${MACOSX_SDK}/usr/include/gcc/darwin/${GCC_VERSION} -isystem ${MACOSX_SDK}/usr/include" + MACOSX_SDK_CXXFLAGS="-nostdinc++ -I${MACOSX_SDK}/usr/include/gcc/darwin/${GCC_VERSION}/c++ -I${MACOSX_SDK}/usr/include/gcc/darwin/${GCC_VERSION}/c++/ppc-darwin -I${MACOSX_SDK}/usr/include/gcc/darwin/${GCC_VERSION}/c++/backward" + MACOSX_SDK_LDFLAGS="-L${MACOSX_SDK}/usr/lib/gcc/darwin -L${MACOSX_SDK}/usr/lib/gcc/darwin/${GCC_VERSION} -L${MACOSX_SDK}/usr/lib" + ;; + x*86*darwin*) + MACOSX_SDK=/Developer/SDKs/MacOSX10.4u.sdk + GCC_VERSION=4.0 + ARCH="-arch i386" + MACOSX_SDK_CFLAGS="-isysroot ${MACOSX_SDK}" + MACOSX_SDK_CXXFLAGS="${MACOSX_SDK_CFLAGS}" + ;; + esac + CC=gcc-${GCC_VERSION} + CXX=g++-${GCC_VERSION} + MACOSX_SDK_FRAMEWORKS="-F${MACOSX_SDK}/System/Library/Frameworks" + CPPFLAGS="${CPPFLAGS} ${ARCH} ${MACOSX_SDK_FRAMEWORKS}" + CFLAGS="${CFLAGS} ${MACOSX_SDK_CFLAGS}" + CXXFLAGS="${CXXFLAGS} ${MACOSX_SDK_CXXFLAGS}" + OBJCFLAGS="${OBJCFLAGS} ${MACOSX_SDK_CFLAGS}" + LDFLAGS="${ARCH} ${MACOSX_SDK_LDFLAGS} ${LDFLAGS}" AC_DEFINE(USE_COCOA, 1, Define to 1 to activate the Cocoa backend driver) - CACA_LIBS="${CACA_LIBS} -Wl,-framework,Carbon,-framework,Cocoa" + CACA_LIBS="${CACA_LIBS} -Wl,-syslibroot,${MACOSX_SDK},-framework,Cocoa" CACA_DRIVERS="${CACA_DRIVERS} cocoa" elif test "${enable_cocoa}" = "yes"; then AC_MSG_ERROR([cannot find Cocoa development files]) fi - CPPFLAGS="${savedCPPFLAGS}" - AC_LANG_POP(C++) fi AM_CONDITIONAL(USE_COCOA, test "${ac_cv_my_have_cocoa}" = "yes") @@ -286,7 +307,7 @@ AC_SUBST(GL_LIBS) CFLAGS="${CFLAGS} -g -O2 -fno-strength-reduce -fomit-frame-pointer" # Code qui fait des warnings == code de porc == deux baffes dans ta gueule # [Jylam] Removed -Wshadow in order to avoid ncurses/gl conflict -# (Comme quoi on est pas les seuls porcs) +# (Comme quoi on n'est pas les seuls porcs) CFLAGS="${CFLAGS} -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wstrict-prototypes -Wshadow -Waggregate-return -Wmissing-prototypes -Wnested-externs -Wsign-compare" # Build the C++ bindings?