1018 lines
30 KiB
Objective-C
1018 lines
30 KiB
Objective-C
/*
|
|
* libcaca Colour ASCII-Art library
|
|
* Copyright © 2021 Sam Hocevar <sam@hocevar.net>
|
|
* 2006 Colin Delacroix <colin@zoy.org>
|
|
* All Rights Reserved
|
|
*
|
|
* This library is free software. It comes without any warranty, to
|
|
* the extent permitted by applicable law. You can redistribute it
|
|
* and/or modify it under the terms of the Do What the Fuck You Want
|
|
* to Public License, Version 2, as published by Sam Hocevar. See
|
|
* http://www.wtfpl.net/ for more details.
|
|
*/
|
|
|
|
/*
|
|
* This file contains the libcaca Cocoa input and output driver
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#if defined USE_COCOA
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#include "caca.h"
|
|
#include "caca.h"
|
|
#include "caca_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 _font_rect;
|
|
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;
|
|
|
|
[[self window] makeFirstResponder:self];
|
|
|
|
#ifdef PRECACHE_WHOLE_COLOR_TABLE
|
|
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
|
|
{
|
|
//[_font release];
|
|
#ifdef PRECACHE_WHOLE_COLOR_TABLE
|
|
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];
|
|
}
|
|
|
|
// to accelerate the window drawing speed
|
|
- (BOOL)isOpaque
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)isFlipped
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)setupNewSize
|
|
{
|
|
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));
|
|
}
|
|
|
|
- (void)keyDown:(NSEvent *)theEvent
|
|
{
|
|
NSLog(@"key %@", theEvent);
|
|
}
|
|
|
|
- (void)mouseMoved:(NSEvent *)theEvent
|
|
{
|
|
NSLog(@"mouse %@", theEvent);
|
|
}
|
|
|
|
- (void)setFont:(NSFont *)aFont
|
|
{
|
|
//[_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)resizeIfNeeded:(caca_display_t *)dp
|
|
{
|
|
if(_w != caca_get_canvas_width(dp->cv)
|
|
|| _h != caca_get_canvas_height(dp->cv)
|
|
|| !_attrs || !_bkg_rects || !_bkg_colors)
|
|
{
|
|
debug_log(@"%s resize to %ux%u", _cmd, _w, _h);
|
|
|
|
_w = caca_get_canvas_width(dp->cv);
|
|
_h = caca_get_canvas_height(dp->cv);
|
|
|
|
if(_attrs)
|
|
free(_attrs);
|
|
_attrs = _caca_alloc2d(_w , _h, sizeof(uint32_t) * 2);
|
|
|
|
if(_bkg_rects)
|
|
free(_bkg_rects);
|
|
_bkg_rects = _caca_alloc2d(_w, _h, sizeof(NSRect));
|
|
|
|
if(_bkg_colors)
|
|
free(_bkg_colors);
|
|
_bkg_colors = _caca_alloc2d(_w, _h, sizeof(NSColor*));
|
|
|
|
// [[self window] setContentSize: NSMakeSize(caca_get_canvas_width(dp->cv) * _font_rect.size.width,
|
|
// caca_get_canvas_height(dp->cv) * _font_rect.size.height)];
|
|
[[self window] setContentSize: NSMakeSize(caca_get_canvas_width(dp->cv) * 8,
|
|
caca_get_canvas_height(dp->cv) * 13)];
|
|
|
|
}
|
|
}
|
|
|
|
- (void)updateBuffersFromCaca:(caca_display_t *)dp
|
|
{
|
|
[self resizeIfNeeded:dp];
|
|
|
|
if(_attrs)
|
|
{
|
|
_chars = _attrs + _w * _h;
|
|
memcpy(_attrs, caca_get_canvas_attrs(dp->cv),
|
|
_w * _h * sizeof(uint32_t));
|
|
memcpy(_chars, caca_get_canvas_chars(dp->cv),
|
|
_w * _h * sizeof(uint32_t));
|
|
|
|
[self setNeedsDisplay:TRUE];
|
|
}
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)rect
|
|
{
|
|
//if([self inLiveResize]) [self setupNewSize];
|
|
|
|
if(!_attrs || !_chars)
|
|
{
|
|
[[NSColor blueColor] set];
|
|
NSRectFill(rect);
|
|
return;
|
|
}
|
|
|
|
int x, y;
|
|
float fw = 8;//_font_rect.size.width;
|
|
float fh = 13;//_font_rect.size.height;
|
|
uint32_t* attrs;
|
|
uint32_t* chars = _chars;
|
|
|
|
/* first take care of the background */
|
|
[[NSColor blackColor] set];
|
|
NSRectFill(rect);
|
|
|
|
int arrayLength = 0;
|
|
for(y = 0; y < _h; y++)
|
|
{
|
|
int yoff = y * fh;
|
|
for(x = 0; x < _w; x++)
|
|
{
|
|
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 = caca_attr_to_rgb12_bg(*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];
|
|
caca_attr_to_argb64(*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++)
|
|
{
|
|
int yoff = y * fh;
|
|
for(x = 0; x < _w; x++, chars++)
|
|
{
|
|
attrs = _attrs + x + y * _w;
|
|
|
|
/* Skip spaces */
|
|
if(*chars <= 0x00000020)
|
|
continue;
|
|
|
|
if(*chars == CACA_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 = caca_attr_to_rgb12_fg(*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];
|
|
caca_attr_to_argb64(*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 & CACA_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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
struct driver_private
|
|
{
|
|
NSWindow* window;
|
|
CacaView* view;
|
|
#ifdef USE_GLOBAL_AUTORELEASE_POOL
|
|
NSAutoreleasePool* pool;
|
|
#endif
|
|
};
|
|
|
|
//============================================================================
|
|
// NSApplication(Caca)
|
|
//============================================================================
|
|
|
|
@implementation NSApplication(Caca)
|
|
- (void)setRunning
|
|
{
|
|
_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
|
|
|
|
//============================================================================
|
|
// 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<NSMenuItem> 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))
|
|
{
|
|
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
|
|
SetFrontProcess(&psn);
|
|
}
|
|
|
|
if(NSApp == nil)
|
|
{
|
|
[NSApplication sharedApplication];
|
|
|
|
if(![NSApp mainMenu])
|
|
create_application_menus();
|
|
|
|
[NSApp finishLaunching];
|
|
}
|
|
|
|
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 void create_first_window(caca_display_t *dp)
|
|
{
|
|
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, caca_get_canvas_width(dp->cv) * fontRect.size.width,
|
|
caca_get_canvas_height(dp->cv) * 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;
|
|
}
|
|
|
|
static int get_caca_keycode(NSEvent* event)
|
|
{
|
|
int caca_keycode = 0;
|
|
/*
|
|
unsigned short mac_keycode = [event keyCode];
|
|
debug_log(@"keycode %u (%x)", mac_keycode, mac_keycode);
|
|
switch(mac_keycode)
|
|
{
|
|
}
|
|
*/
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
return caca_keycode;
|
|
}
|
|
|
|
static BOOL handle_key_event(caca_privevent_t *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:
|
|
;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return eventHandled;
|
|
}
|
|
|
|
// TODO: handle CACA_EVENT_RESIZE
|
|
static BOOL handle_mouse_event(caca_display_t *dp, caca_privevent_t *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];
|
|
int mouse_x = round(mouseLoc.x);
|
|
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 YES;
|
|
}
|
|
|
|
//============================================================================
|
|
// caca driver methods
|
|
//============================================================================
|
|
|
|
static int cocoa_init_graphics(caca_display_t *dp)
|
|
{
|
|
int width = caca_get_canvas_width(dp->cv);
|
|
int height = caca_get_canvas_height(dp->cv);
|
|
|
|
debug_log(@"%s dp->cv: %ux%u", __PRETTY_FUNCTION__, width, height);
|
|
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
dp->drv.p = malloc(sizeof(struct driver_private));
|
|
if(dp->drv.p == NULL)
|
|
return -1;
|
|
|
|
dp->resize.allow = 1;
|
|
caca_set_canvas_size(dp->cv, width ? width : 80, height ? height : 32);
|
|
dp->resize.allow = 0;
|
|
|
|
// 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 int cocoa_end_graphics(caca_display_t *dp)
|
|
{
|
|
debug_log(@"%s dp->cv: %ux%u", __PRETTY_FUNCTION__,
|
|
caca_get_canvas_width(dp->cv), caca_get_canvas_height(dp->cv));
|
|
|
|
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
|
|
free(dp->drv.p);
|
|
debug_log(@"%s end", __PRETTY_FUNCTION__);
|
|
[pool release];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cocoa_display(caca_display_t *dp)
|
|
{
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
[dp->drv.p->view updateBuffersFromCaca:dp];
|
|
[pool release];
|
|
}
|
|
|
|
static int cocoa_get_event(caca_display_t *dp, caca_privevent_t *ev)
|
|
{
|
|
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 = caca_get_canvas_width(dp->cv);
|
|
dp->resize.h = caca_get_canvas_height(dp->cv);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int cocoa_get_display_width(caca_display_t const *dp)
|
|
{
|
|
return [dp->drv.p->window frame].size.width;
|
|
}
|
|
|
|
static int cocoa_get_display_height(caca_display_t const *dp)
|
|
{
|
|
return [dp->drv.p->window frame].size.height;
|
|
}
|
|
|
|
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];
|
|
}
|
|
[pool release];
|
|
}
|
|
|
|
/*
|
|
* Driver initialisation
|
|
*/
|
|
|
|
int cocoa_install(caca_display_t *dp)
|
|
{
|
|
dp->drv.id = CACA_DRIVER_COCOA;
|
|
dp->drv.driver = "cocoa";
|
|
|
|
dp->drv.init_graphics = cocoa_init_graphics;
|
|
dp->drv.end_graphics = cocoa_end_graphics;
|
|
dp->drv.set_display_title = cocoa_set_display_title;
|
|
dp->drv.get_display_width = cocoa_get_display_width;
|
|
dp->drv.get_display_height = cocoa_get_display_height;
|
|
dp->drv.display = cocoa_display;
|
|
dp->drv.handle_resize = cocoa_handle_resize;
|
|
dp->drv.get_event = cocoa_get_event;
|
|
dp->drv.set_mouse = cocoa_set_mouse;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* USE_COCOA */
|