blob: f10614efb747e97eba46ad66963640927558caea [file] [log] [blame]
/*
* Cocoa's event loop must be in the main thread.
*/
#define Point OSXPoint
#define Rect OSXRect
#define Cursor OSXCursor
#import <Cocoa/Cocoa.h>
#undef Rect
#undef Point
#undef Cursor
#include <u.h>
#include <libc.h>
#include "cocoa-thread.h" // try libthread when possible
#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <cursor.h>
#include "cocoa-screen.h"
#include "osx-keycodes.h"
#include "devdraw.h"
#include "glendapng.h"
AUTOFRAMEWORK(Cocoa)
#define panic sysfatal
/*
* Incompatible with Magic Mouse?
*/
int reimplementswipe = 0;
int usecopygesture = 0;
int useoldfullscreen = 0;
void
usage(void)
{
fprint(2, "usage: devdraw (don't run directly)\n");
exits("usage");
}
@interface appdelegate : NSObject
@end
void
main(int argc, char **argv)
{
/*
* Move the protocol off stdin/stdout so that
* any inadvertent prints don't screw things up.
*/
dup(0,3);
dup(1,4);
close(0);
close(1);
open("/dev/null", OREAD);
open("/dev/null", OWRITE);
// Libdraw don't permit arguments currently.
ARGBEGIN{
case 'D': // only for good ps -a listings
break;
default:
usage();
}ARGEND
if(usecopygesture)
reimplementswipe = 1;
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setDelegate:[appdelegate new]];
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
}
struct {
NSWindow *p;
NSView *content;
Cursor *cursor;
NSString *title;
QLock titlel;
char *rectstr;
NSRect lastrect;
NSBitmapImageRep *img;
NSRect flushrect;
int needflush;
} win;
static void drawimg(void);
static void flushwin(void);
static void fullscreen(void);
static void getmousepos(void);
static void makeicon(void);
static void makemenu(void);
static void makewin(void);
static void resize(void);
static void sendmouse(int);
static void setcursor0(void);
@implementation appdelegate
- (void)applicationDidFinishLaunching:(id)arg
{
makeicon();
makemenu();
[NSApplication
detachDrawingThread:@selector(callservep9p:)
toTarget:[self class] withObject:nil];
}
- (void)windowDidBecomeKey:(id)arg
{
getmousepos();
sendmouse(0);
}
- (void)windowDidResize:(id)arg
{
getmousepos();
sendmouse(0);
if([win.p inLiveResize])
return;
resize();
}
- (void)windowDidEndLiveResize:(id)arg
{
resize();
}
- (void)windowDidDeminiaturize:(id)arg
{
resize();
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg
{
return YES;
}
+ (void)callservep9p:(id)arg
{
servep9p();
[NSApp terminate:self];
}
+ (void)calldrawimg:(id)arg{ drawimg();}
+ (void)callflushwin:(id)arg{ flushwin();}
- (void)callfullscreen:(id)arg{ fullscreen();}
+ (void)callmakewin:(id)arg{ makewin();}
+ (void)callsetcursor0:(id)arg{ setcursor0();}
@end
static Memimage* makeimg(void);
Memimage*
attachscreen(char *label, char *winsize)
{
static int first = 1;
if(first)
first = 0;
else
panic("attachscreen called twice");
if(label == nil)
label = "gnot a label";
win.rectstr = strdup(winsize);
// Create window in main thread,
// else no cursor change when resizing.
[appdelegate
performSelectorOnMainThread:@selector(callmakewin:)
withObject:nil
waitUntilDone:YES];
// makewin();
kicklabel(label);
return makeimg();
}
@interface appview : NSView
@end
@interface appwin : NSWindow
@end
@implementation appwin
- (NSTimeInterval)animationResizeTime:(NSRect)r
{
return 0;
}
@end
enum
{
Winstyle = NSTitledWindowMask
| NSClosableWindowMask
| NSMiniaturizableWindowMask
| NSResizableWindowMask
};
static void
makewin(void)
{
NSRect r, sr;
NSView *v;
NSWindow *w;
Rectangle wr;
char *s;
int set;
s = win.rectstr;
if(s && *s){
if(parsewinsize(s, &wr, &set) < 0)
sysfatal("%r");
}else{
sr = [[NSScreen mainScreen] frame];
wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
set = 0;
}
// The origin is the left-bottom corner with Cocoa.
r = NSMakeRect(wr.min.x, r.size.height-wr.min.y, Dx(wr), Dy(wr));
v = [appview new];
[v setAcceptsTouchEvents:YES];
w = [[appwin alloc]
initWithContentRect:r
styleMask:Winstyle
backing:NSBackingStoreBuffered
defer:NO];
if(!set)
[w center];
#if OSX_VERSION >= 100700
[w setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
#endif
[w setAcceptsMouseMovedEvents:YES];
[w setContentView:v];
[w setDelegate:[NSApp delegate]];
[w setMinSize:NSMakeSize(128,128)];
[w makeKeyAndOrderFront:nil];
win.content = v;
win.p = w;
}
static Memimage*
makeimg(void)
{
static int first = 1;
Memimage *m;
NSSize size;
Rectangle r;
uint ch;
if(first){
memimageinit();
first = 0;
}
size = [win.content bounds].size;
if(size.width<=0 || size.height<=0){
NSLog(@"bad content size: %.0f %.0f", size.width, size.height);
return nil;
}
r = Rect(0, 0, size.width, size.height);
ch = XBGR32;
m = allocmemimage(r, ch);
if(m == nil)
panic("allocmemimage: %r");
if(m->data == nil)
panic("m->data == nil");
if(win.img)
[win.img release];
win.img = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:&m->data->bdata
pixelsWide:Dx(r)
pixelsHigh:Dy(r)
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:bytesperline(r, 32)
bitsPerPixel:32];
_drawreplacescreenimage(m);
return m;
}
static void
resize(void)
{
makeimg();
sendmouse(1);
}
void
_flushmemscreen(Rectangle r)
{
win.flushrect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
// Call "lockFocusIfCanDraw" from main thread, else
// we deadlock while synchronizing both threads with
// qlock(): main thread must apparently be idle while we call it.
[appdelegate
performSelectorOnMainThread:@selector(calldrawimg:)
withObject:nil
waitUntilDone:YES];
}
static void
drawimg(void)
{
static int first = 1;
NSRect dr, sr;
if(first){
[NSTimer scheduledTimerWithTimeInterval:0.033
target:[appdelegate class]
selector:@selector(callflushwin:) userInfo:nil
repeats:YES];
first = 0;
}
dr = win.flushrect;
sr = [win.content convertRect:dr fromView:nil];
if([win.content lockFocusIfCanDraw]){
[win.img drawInRect:dr fromRect:sr
operation:NSCompositeCopy fraction:1
respectFlipped:YES hints:nil];
[win.content unlockFocus];
win.needflush = 1;
}
}
static void
flushwin(void)
{
if(win.needflush){
[win.p flushWindow];
win.needflush = 0;
}
}
static void getgesture(NSEvent*);
static void getkeyboard(NSEvent*);
static void getmouse(NSEvent*);
static void gettouch(NSEvent*, int);
@implementation appview
- (void)drawRect:(NSRect)r
{
// else no window background
}
- (BOOL)isFlipped
{
return YES; // to have the origin at top left
}
- (BOOL)acceptsFirstResponder
{
return YES; // to receive mouseMoved events
}
- (void)mouseMoved:(NSEvent*)e{ getmouse(e);}
- (void)mouseDown:(NSEvent*)e{ getmouse(e);}
- (void)mouseDragged:(NSEvent*)e{ getmouse(e);}
- (void)mouseUp:(NSEvent*)e{ getmouse(e);}
- (void)otherMouseDown:(NSEvent*)e{ getmouse(e);}
- (void)otherMouseDragged:(NSEvent*)e{ getmouse(e);}
- (void)otherMouseUp:(NSEvent*)e{ getmouse(e);}
- (void)rightMouseDown:(NSEvent*)e{ getmouse(e);}
- (void)rightMouseDragged:(NSEvent*)e{ getmouse(e);}
- (void)rightMouseUp:(NSEvent*)e{ getmouse(e);}
- (void)scrollWheel:(NSEvent*)e{ getmouse(e);}
- (void)keyDown:(NSEvent*)e{ getkeyboard(e);}
- (void)flagsChanged:(NSEvent*)e{ getkeyboard(e);}
- (void)swipeWithEvent:(NSEvent*)e{ getgesture(e);}
- (void)magnifyWithEvent:(NSEvent*)e{ getgesture(e);}
- (void)touchesBeganWithEvent:(NSEvent*)e
{
gettouch(e, NSTouchPhaseBegan);
}
- (void)touchesMovedWithEvent:(NSEvent*)e
{
gettouch(e, NSTouchPhaseMoved);
}
- (void)touchesEndedWithEvent:(NSEvent*)e
{
gettouch(e, NSTouchPhaseEnded);
}
- (void)touchesCancelledWithEvent:(NSEvent*)e
{
gettouch(e, NSTouchPhaseCancelled);
}
@end
struct {
int kalting;
int kbuttons;
int mbuttons;
Point mpos;
int mscroll;
int undo;
} in;
static int keycvt[] =
{
[QZ_IBOOK_ENTER] '\n',
[QZ_RETURN] '\n',
[QZ_ESCAPE] 27,
[QZ_BACKSPACE] '\b',
[QZ_LALT] Kalt,
[QZ_LCTRL] Kctl,
[QZ_LSHIFT] Kshift,
[QZ_F1] KF+1,
[QZ_F2] KF+2,
[QZ_F3] KF+3,
[QZ_F4] KF+4,
[QZ_F5] KF+5,
[QZ_F6] KF+6,
[QZ_F7] KF+7,
[QZ_F8] KF+8,
[QZ_F9] KF+9,
[QZ_F10] KF+10,
[QZ_F11] KF+11,
[QZ_F12] KF+12,
[QZ_INSERT] Kins,
[QZ_DELETE] 0x7F,
[QZ_HOME] Khome,
[QZ_END] Kend,
[QZ_KP_PLUS] '+',
[QZ_KP_MINUS] '-',
[QZ_TAB] '\t',
[QZ_PAGEUP] Kpgup,
[QZ_PAGEDOWN] Kpgdown,
[QZ_UP] Kup,
[QZ_DOWN] Kdown,
[QZ_LEFT] Kleft,
[QZ_RIGHT] Kright,
[QZ_KP_MULTIPLY] '*',
[QZ_KP_DIVIDE] '/',
[QZ_KP_ENTER] '\n',
[QZ_KP_PERIOD] '.',
[QZ_KP0] '0',
[QZ_KP1] '1',
[QZ_KP2] '2',
[QZ_KP3] '3',
[QZ_KP4] '4',
[QZ_KP5] '5',
[QZ_KP6] '6',
[QZ_KP7] '7',
[QZ_KP8] '8',
[QZ_KP9] '9',
};
static void
getkeyboard(NSEvent *e)
{
char c;
int k, m;
uint code;
m = [e modifierFlags];
switch([e type]){
case NSKeyDown:
in.kalting = 0;
c = [[e characters] characterAtIndex:0];
if(m & NSCommandKeyMask){
if(' '<=c && c<='~'){
keystroke(Kcmd+c);
}
return;
}
// to understand
k = c;
code = [e keyCode];
if(code < nelem(keycvt) && keycvt[code])
k = keycvt[code];
if(k == 0)
return;
if(k > 0)
keystroke(k);
else
keystroke(c);
break;
case NSFlagsChanged:
if(in.mbuttons || in.kbuttons){
in.kbuttons = 0;
if(m & NSAlternateKeyMask)
in.kbuttons |= 2;
if(m & NSCommandKeyMask)
in.kbuttons |= 4;
sendmouse(0);
}else
if(m & NSAlternateKeyMask){
in.kalting = 1;
keystroke(Kalt);
}
break;
default:
panic("getkey: unexpected event type");
}
}
static void
getmousepos(void)
{
NSPoint p;
p = [win.p mouseLocationOutsideOfEventStream];
p = [win.content convertPoint:p fromView:nil];
in.mpos = Pt(p.x, p.y);
}
static void
getmouse(NSEvent *e)
{
float d;
int b, m;
getmousepos();
switch([e type]){
case NSLeftMouseDown:
case NSLeftMouseUp:
case NSOtherMouseDown:
case NSOtherMouseUp:
case NSRightMouseDown:
case NSRightMouseUp:
b = [NSEvent pressedMouseButtons];
b = b&~6 | (b&4)>>1 | (b&2)<<1;
b = mouseswap(b);
if(b == 1){
m = [e modifierFlags];
if(m & NSAlternateKeyMask){
b = 2;
// Take the ALT away from the keyboard handler.
if(in.kalting){
in.kalting = 0;
keystroke(Kalt);
}
}else
if(m & NSCommandKeyMask)
b = 4;
}
in.mbuttons = b;
break;
case NSScrollWheel:
#if OSX_VERSION >= 100700
d = [e scrollingDeltaY];
#else
d = [e deltaY];
#endif
if(d>0)
in.mscroll = 8;
else
if(d<0)
in.mscroll = 16;
break;
case NSMouseMoved:
case NSLeftMouseDragged:
case NSRightMouseDragged:
case NSOtherMouseDragged:
break;
default:
panic("getmouse: unexpected event type");
}
sendmouse(0);
}
static void sendswipe(int, int);
static void
getgesture(NSEvent *e)
{
switch([e type]){
case NSEventTypeMagnify:
// if(fabs([e magnification]) > 0.025)
fullscreen();
break;
case NSEventTypeSwipe:
if(reimplementswipe)
break;
sendswipe(-[e deltaX], -[e deltaY]);
break;
}
}
static void sendclick(int);
static void sendchord(int, int);
static void sendcmd(int);
static uint
msec(void)
{
return nsec()/1000000;
}
enum
{
Msec = 1,
Maxtap = 400*Msec,
Maxtouch = 3,
Mindelta = 0,
Minswipe = 15,
};
static void
gettouch(NSEvent *e, int type)
{
static NSPoint delta, odelta;
static NSTouch *toucha[Maxtouch];
static NSTouch *touchb[Maxtouch];
static int done, ntouch, tapping;
static uint taptime;
NSArray *a;
NSPoint d;
NSSet *set;
NSSize s;
int i, p;
if(reimplementswipe==0 && type!=NSTouchPhaseEnded)
return;
switch(type){
case NSTouchPhaseBegan:
p = NSTouchPhaseTouching;
set = [e touchesMatchingPhase:p inView:nil];
if(set.count == 3){
tapping = 1;
taptime = msec();
}else
if(set.count > 3)
tapping = 0;
return;
case NSTouchPhaseMoved:
p = NSTouchPhaseMoved;
set = [e touchesMatchingPhase:p inView:nil];
a = [set allObjects];
if(set.count > Maxtouch)
return;
if(ntouch==0){
ntouch = set.count;
for(i=0; i<ntouch; i++){
assert(toucha[i] == nil);
toucha[i] = [[a objectAtIndex:i] retain];
}
return;
}
if(ntouch != set.count)
break;
if(done)
return;
d = NSMakePoint(0,0);
for(i=0; i<ntouch; i++){
assert(touchb[i] == nil);
touchb[i] = [a objectAtIndex:i];
d.x += touchb[i].normalizedPosition.x;
d.y += touchb[i].normalizedPosition.y;
d.x -= toucha[i].normalizedPosition.x;
d.y -= toucha[i].normalizedPosition.y;
}
s = toucha[0].deviceSize;
d.x = d.x/ntouch * s.width;
d.y = d.y/ntouch * s.height;
if(fabs(d.x)>Mindelta || fabs(d.y)>Mindelta){
tapping = 0;
if(ntouch != 3){
done = 1;
goto Return;
}
delta = NSMakePoint(delta.x+d.x, delta.y+d.y);
d = NSMakePoint(fabs(delta.x), fabs(delta.y));
if(d.x>Minswipe || d.y>Minswipe){
if(d.x > d.y)
delta = NSMakePoint(-copysign(1,delta.x), 0);
else
delta = NSMakePoint(0, copysign(1,delta.y));
if(! NSEqualPoints(delta, odelta)){
// if(ntouch == 3)
sendswipe(-delta.x, -delta.y);
odelta = delta;
}
done = 1;
goto Return;
}
for(i=0; i<ntouch; i++){
[toucha[i] release];
toucha[i] = [touchb[i] retain];
}
}
Return:
for(i=0; i<ntouch; i++)
touchb[i] = nil;
return;
case NSTouchPhaseEnded:
p = NSTouchPhaseTouching;
set = [e touchesMatchingPhase:p inView:nil];
if(set.count == 0){
in.undo = 0;
if(usecopygesture)
if(tapping && msec()-taptime<Maxtap)
sendclick(2);
tapping = 0;
odelta = NSMakePoint(0,0);
}
break;
case NSTouchPhaseCancelled:
break;
default:
panic("gettouch: unexpected event type: %d", type);
}
for(i=0; i<ntouch; i++){
[toucha[i] release];
toucha[i] = nil;
}
for(i=0; i<3; i++){
assert(toucha[i] == nil);
assert(touchb[i] == nil);
}
ntouch = 0;
delta = NSMakePoint(0,0);
done = 0;
}
static void
sendswipe(int dx, int dy)
{
if(dx == -1){
sendcmd('x');
}else
if(dx == +1){
sendcmd('v');
}else
if(dy == -1){
if(usecopygesture)
sendcmd('c');
else
sendclick(2);
}else
if(dy == +1){
sendchord(2,1);
}
}
static void
sendcmd(int c)
{
if(c=='x' || c=='v'){
if(in.undo)
c = 'z';
in.undo = ! in.undo;
}
keystroke(Kcmd+c);
}
static void
sendclick(int b)
{
in.mbuttons = b;
sendmouse(0);
in.mbuttons = 0;
sendmouse(0);
}
static void
sendchord(int b1, int b2)
{
in.mbuttons = b1;
sendmouse(0);
in.mbuttons |= b2;
sendmouse(0);
in.mbuttons = 0;
sendmouse(0);
}
static void
sendmouse(int resized)
{
NSSize size;
int b;
size = [win.img size];
if(resized)
mouseresized = 1;
mouserect = Rect(0, 0, size.width, size.height);
b = in.kbuttons | in.mbuttons | in.mscroll;
mousetrack(in.mpos.x, in.mpos.y, b, msec());
in.mscroll = 0;
}
void
setmouse(Point p)
{
static int first = 1;
NSPoint q;
NSRect r;
if(first){
// try to move Acme's scrollbars without that!
CGSetLocalEventsSuppressionInterval(0);
first = 0;
}
r = [[win.p screen] frame];
q = NSMakePoint(p.x,p.y);
q = [win.content convertPoint:q toView:nil];
q = [win.p convertBaseToScreen:q];
q.y = r.size.height - q.y;
CGWarpMouseCursorPosition(NSPointToCGPoint(q));
// race condition
in.mpos = p;
//NSLog(@"setmouse %d %d", p.x, p.y);
}
static void
fullscreen(void)
{
#if OSX_VERSION >= 100700
if(useoldfullscreen == 0){
[win.p toggleFullScreen:nil];
return;
}
#endif
NSScreen *screen;
int opt;
screen = [win.p screen];
if(NSEqualRects([win.p frame], [screen frame])){
opt = NSApplicationPresentationDefault;
[NSApp setPresentationOptions:opt];
[win.p setStyleMask:Winstyle];
[win.p setFrame:win.lastrect display:YES];
}else{
win.lastrect = [win.p frame];
opt = NSApplicationPresentationAutoHideDock
| NSApplicationPresentationAutoHideMenuBar;
[NSApp setPresentationOptions:opt];
[win.p setStyleMask:NSBorderlessWindowMask];
[win.p setFrame:[screen frame] display:YES];
}
// On OS X Lion, after "setStyleMask", window is activated (the gesture work for example), but no keyboard input until mouse or trackpad pressed
// What I tried without success on OS X Lion:
// [NSApp activateIgnoringOtherApps:YES];
// [win.p makeKeyAndOrderFront:nil];
// implementing canBecomeKeyWindow
// implementing canBecomeMainWindow
// saving/restoring [win.content nextResponder]
// using enterFullScreenMode instead
// What I didn't try:
// using 2 windows instead: one for each mode
qlock(&win.titlel);
[win.p setTitle:win.title];
qunlock(&win.titlel);
}
// Rewrite this function
// See ./osx-delegate.m implementation (NSLocalizedString)
static void
makemenu(void)
{
NSString *title;
NSMenu *menu;
NSMenuItem *appmenu, *item;
menu = [NSMenu new];
appmenu = [NSMenuItem new];
[menu addItem:appmenu];
[NSApp setMenu:menu];
[menu release];
menu = [NSMenu new];
title = @"Full Screen";
item = [[NSMenuItem alloc]
initWithTitle:title
action:@selector(callfullscreen:) keyEquivalent:@"f"];
[menu addItem:item];
[item release];
title = @"Quit";
item = [[NSMenuItem alloc]
initWithTitle:title
action:@selector(terminate:) keyEquivalent:@"q"];
[menu addItem:item];
[item release];
[appmenu setSubmenu:menu];
[appmenu release];
[menu release];
}
static void
makeicon(void)
{
NSData *d;
NSImage *i;
d = [[NSData alloc]
initWithBytes:glenda_png
length:(sizeof glenda_png)];
i = [[NSImage alloc] initWithData:d];
[NSApp setApplicationIconImage:i];
[[NSApp dockTile] display];
[i release];
[d release];
}
QLock snarfl;
char*
getsnarf(void)
{
NSPasteboard *pb;
NSString *s;
pb = [NSPasteboard generalPasteboard];
// use NSPasteboardTypeString instead of NSStringPboardType
qlock(&snarfl);
s = [pb stringForType:NSStringPboardType];
qunlock(&snarfl);
// change the pastebuffer here to see if s is
// altered. Move the lock accordingly.
if(s)
return strdup((char*)[s UTF8String]);
else
return nil;
// "you should periodically drain and create
// autorelease pools (like the Application Kit does
// on the main thread); otherwise, autoreleased
// objects accumulate and your memory footprint
// grows."
// Should we do it here?
// Verify that we need it before.
}
void
putsnarf(char *s)
{
NSArray *t;
NSPasteboard *pb;
NSString *str;
int r;
if(strlen(s) >= SnarfSize)
return;
t = [NSArray arrayWithObject:NSPasteboardTypeString];
pb = [NSPasteboard generalPasteboard];
str = [[NSString alloc] initWithUTF8String:s];
qlock(&snarfl);
[pb declareTypes:t owner:nil];
r = [pb setString:str forType:NSPasteboardTypeString];
qunlock(&snarfl);
if(!r)
NSLog(@"putsnarf: setString failed");
}
void
kicklabel(char *label)
{
if(label == nil)
return;
qlock(&win.titlel);
if(win.title)
[win.title release];
win.title = [[NSString alloc] initWithUTF8String:label];
[win.p setTitle:win.title];
[[NSApp dockTile] setBadgeLabel:win.title];
qunlock(&win.titlel);
}
void
setcursor(Cursor *cursor)
{
win.cursor = cursor;
// cursor change only if main thread
[appdelegate
performSelectorOnMainThread:@selector(callsetcursor0:)
withObject:nil
waitUntilDone:YES];
// setcursor0();
win.cursor = nil;
}
static void
setcursor0(void)
{
Cursor *c;
NSBitmapImageRep *r;
NSCursor *d;
NSImage *i;
NSPoint p;
int b;
uchar *plane[5];
c = win.cursor;
if(c == nil){
[[NSCursor arrowCursor] set];
return;
}
r = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:nil
pixelsWide:16
pixelsHigh:16
bitsPerSample:1
samplesPerPixel:2
hasAlpha:YES
isPlanar:YES
colorSpaceName:NSDeviceBlackColorSpace
bytesPerRow:2
bitsPerPixel:1];
[r getBitmapDataPlanes:plane];
for(b=0; b<2*16; b++){
plane[0][b] = c->set[b];
plane[1][b] = c->clr[b];
}
p = NSMakePoint(-c->offset.x, -c->offset.y);
i = [NSImage new];
[i addRepresentation:r];
d = [[NSCursor alloc] initWithImage:i hotSpot:p];
[d set];
[d release];
[r release];
[i release];
}