blob: 03f681712db9ef2e4da2f217dbf3e2da30a10f7f [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"
#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"
#define DEBUG if(0)NSLog
AUTOFRAMEWORK(Cocoa)
#define panic sysfatal
struct {
NSWindow *obj;
NSString *label;
char *winsize;
int ispositioned;
NSImage *img;
Memimage *imgbuf;
NSSize imgsize;
QLock lock;
Rendez meeting;
NSRect flushr;
int osxdrawing;
int p9pflushing;
int isresizing;
} win;
@interface appdelegate : NSObject
+(void)callmakewin:(id)arg; @end
@interface appthreads : NSObject
+(void)callservep9p:(id)arg; @end
@interface appview : NSView @end
int chatty;
int multitouch = 1;
void
usage(void)
{
fprint(2, "usage: devdraw (don't run directly)\n");
exits("usage");
}
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);
ARGBEGIN{
case 'D':
chatty++;
break;
case 'M':
multitouch = 0;
break;
default:
usage();
}ARGEND
/*
* Ignore arguments. They're only for good ps -a listings.
*/
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setDelegate:[appdelegate new]];
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
}
static void eresized(int);
static void getmousepos(void);
static void makemenu(NSString*);
static void makewin();
static void seticon(NSString*);
@implementation appdelegate
- (void)applicationDidFinishLaunching:(id)arg
{
[NSApplication detachDrawingThread:@selector(callservep9p:)
toTarget:[appthreads class] withObject:nil];
}
+ (void)callmakewin:(id)arg
{
makewin();
}
- (void)windowDidResize:(id)arg
{
eresized(1);
}
- (void)windowDidBecomeKey:(id)arg
{
getmousepos();
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg
{
return YES;
}
@end
@implementation appthreads
+(void)callservep9p:(id)arg
{
servep9p();
[NSApp terminate:self];
}
@end
Memimage*
attachscreen(char *label, char *winsize)
{
static int first = 1;
if(! first--)
panic("attachscreen called twice");
if(label == nil)
label = "gnot a label";
win.label = [[NSString alloc] initWithUTF8String:label];
win.meeting.l = &win.lock;
win.winsize = strdup(winsize);
makemenu(win.label);
// make NSWindow in the main thread,
// else no resize cursor when resizing.
[appdelegate
performSelectorOnMainThread:@selector(callmakewin:)
withObject:nil
waitUntilDone:YES];
// makewin();
seticon(win.label);
eresized(0);
return win.imgbuf;
}
void
makewin(id winsize)
{
char *s;
int style;
NSWindow *w;
NSRect r, sr;
Rectangle wr;
s = win.winsize;
if(s && *s){
if(parsewinsize(s, &wr, &win.ispositioned) < 0)
sysfatal("%r");
}else{
sr = [[NSScreen mainScreen] frame];
wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
}
// The origin is the left-bottom corner with Cocoa.
// Does the following work with any rectangles?
r = NSMakeRect(wr.min.x, r.size.height-wr.min.y, Dx(wr), Dy(wr));
style = NSTitledWindowMask
| NSClosableWindowMask
| NSResizableWindowMask
| NSMiniaturizableWindowMask;
w = [[NSWindow alloc]
initWithContentRect:r
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[w setAcceptsMouseMovedEvents:YES];
#if OSX_VERSION >= 100700
[w setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
#endif
[w setContentView:[appview new]];
[w setDelegate:[NSApp delegate]];
[w setMinSize:NSMakeSize(128,128)];
[[w contentView] setAcceptsTouchEvents:YES];
if(win.ispositioned == 0)
[w center];
[w setTitle:win.label];
[w makeKeyAndOrderFront:nil];
win.obj = w;
}
static void sendmouse(int);
static void
eresized(int new)
{
static int first = 1;
uint ch;
NSSize size;
Rectangle r;
Memimage *m;
int bpl;
if(first--)
memimageinit();
size = [[win.obj contentView] bounds].size;
DEBUG(@"eresized called new=%d, [%.0f %.0f] -> [%.0f %.0f]", new,
win.imgsize.width, win.imgsize.height, size.width, size.height);
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");
bpl = bytesperline(r, 32);
CGDataProviderRef dp;
CGImageRef i;
CGColorSpaceRef cs;
dp = CGDataProviderCreateWithData(0, m->data->bdata, Dy(r)*bpl, 0);
cs = CGColorSpaceCreateDeviceRGB();
i = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
cs, kCGImageAlphaNone, dp, 0, 0, kCGRenderingIntentDefault);
_drawreplacescreenimage(m);
if(win.img)
[win.img release];
win.img = [[NSImage alloc] initWithCGImage:i size:size];
win.imgbuf = m;
win.imgsize = size;
CGColorSpaceRelease(cs);
CGDataProviderRelease(dp);
CGImageRelease(i);
if(new){
win.isresizing = 1; // to call before mousetrack
sendmouse(1);
}
DEBUG(@"eresized exit");
}
static void getgesture(NSEvent*);
static void getkeyboard(NSEvent*);
static void getmouse(NSEvent*);
@implementation appview
- (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)magnifyWithEvent:(NSEvent*)e{ DEBUG(@"magnifyWithEvent"); getgesture(e);}
- (void)swipeWithEvent:(NSEvent*)e{ DEBUG(@"swipeWithEvent"); getgesture(e);}
- (void)touchesEndedWithEvent:(NSEvent*)e{ DEBUG(@"touchesEndedWithEvent"); getgesture(e);}
- (BOOL)acceptsFirstResponder{ return YES; } // to receive mouseMoved events
- (BOOL)isFlipped{ return YES; }
- (BOOL)isOpaque{ return YES; } // to disable background painting before drawRect calls
- (void)drawRect:(NSRect)r
{
NSRect sr;
NSView *v;
v = [win.obj contentView];
DEBUG(@"drawRect called [%.0f %.0f] [%.0f %.0f]",
r.origin.x, r.origin.y, r.size.width, r.size.height);
if(! NSEqualSizes([v bounds].size, win.imgsize)){
DEBUG(@"drawRect: contentview & img don't correspond: [%.0f %.0f] [%.0f %.0f]",
[v bounds].size.width, [v bounds].size.height,
win.imgsize.width, win.imgsize.height);
return;
}
qlock(win.meeting.l);
if(win.isresizing){
if(! NSEqualRects(r, [v bounds])){
DEBUG(@"drawRect reject osx");
goto Return;
}
win.isresizing = 0;
DEBUG(@"drawRect serve osx");
}else{
if(! NSEqualRects(r, win.flushr)){
DEBUG(@"drawRect reject p9p");
goto Return;
}
DEBUG(@"drawRect serve p9p");
}
win.flushr = r;
win.osxdrawing = 1;
rwakeup(&win.meeting);
DEBUG(@"drawRect rsleep for p9pflushing=1");
while(win.p9pflushing == 0)
rsleep(&win.meeting);
DEBUG(@"drawRect drawInRect [%.0f %.0f] [%.0f %.0f]",
r.origin.x, r.origin.y, r.size.width, r.size.height);
sr = [v convertRect:r fromView:nil];
[win.img drawInRect:r fromRect:sr
operation:NSCompositeCopy fraction:1
respectFlipped:YES hints:nil];
[win.obj flushWindow];
win.osxdrawing = 0;
rwakeup(&win.meeting);
Return:
DEBUG(@"drawRect exit");
qunlock(win.meeting.l);
}
@end
void
_flushmemscreen(Rectangle r)
{
NSRect rect;
NSView *v;
v = [win.obj contentView];
rect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
DEBUG(@"_flushmemscreen called [%.0f %.0f] [%.0f %.0f]",
rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
qlock(win.meeting.l);
if(win.osxdrawing == 0){
DEBUG(@"_flushmemscreen setNeedsDisplayInRect");
[v setNeedsDisplayInRect:rect];
win.flushr = rect;
DEBUG(@"_flushmemscreen rsleep for osxdrawing=1");
while(win.osxdrawing == 0)
rsleep(&win.meeting);
}
if(! NSEqualRects(rect, win.flushr)){
qunlock(win.meeting.l);
DEBUG(@"_flushmemscreen bad rectangle");
return;
}
win.flushr = NSMakeRect(0,0,0,0);
win.p9pflushing = 1;
rwakeup(&win.meeting);
DEBUG(@"_flushmemscreen rsleep for osxdrawing=0");
while(win.osxdrawing)
rsleep(&win.meeting);
win.p9pflushing = 0;
DEBUG(@"_flushmemscreen exit");
qunlock(win.meeting.l);
}
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',
};
int kalting;
int kbuttons;
int mbuttons;
Point mpos;
int scroll;
static void
getkeyboard(NSEvent *e)
{
uint code;
int k, m;
char c;
m = [e modifierFlags];
switch([e type]){
case NSKeyDown:
kalting = 0;
c = [[e characters] characterAtIndex:0];
if(m & NSCommandKeyMask){
// If I add cmd+h in the menu, does the combination
// appear here? If it doesn't, remove the following
//
// // OS X interprets a few no matter what we do,
// switch(c) {
// case 'm': // minimize window
// case 'h': // hide window
// case 'H': // hide others
// case 'q': // quit
// return;
// }
if(' '<=c && c<='~') {
keystroke(Kcmd+c);
return;
}
return;
}
// to undersand
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(mbuttons || kbuttons){
kbuttons = 0;
if(m & NSAlternateKeyMask)
kbuttons |= 2;
if(m & NSCommandKeyMask)
kbuttons |= 4;
sendmouse(0);
}else
if(m & NSAlternateKeyMask) {
kalting = 1;
keystroke(Kalt);
}
break;
default:
panic("getkey: unexpected event type");
}
}
static void
getmousepos(void)
{
NSPoint p;
p = [win.obj mouseLocationOutsideOfEventStream];
p = [[win.obj contentView] convertPoint:p fromView:nil];
// DEBUG(@"getmousepos: %0.f %0.f", p.x, p.y);
mpos = Pt(p.x, p.y);
}
static void
getmouse(NSEvent *e)
{
int b, m;
float d;
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(kalting) {
kalting = 0;
keystroke(Kalt);
}
}else
if(m & NSCommandKeyMask)
b = 4;
}
mbuttons = b;
break;
case NSScrollWheel:
#if OSX_VERSION >= 100700
d = [e scrollingDeltaY];
#else
d = [e deltaY];
#endif
if(d>0)
scroll = 8;
else if(d<0)
scroll = 16;
break;
case NSMouseMoved:
case NSLeftMouseDragged:
case NSRightMouseDragged:
case NSOtherMouseDragged:
break;
default:
panic("getmouse: unexpected event type");
}
sendmouse(0);
}
static void sendexec(int);
static void sendcmd(int, int*);
static void
getgesture(NSEvent *e)
{
static int undo;
int dx, dy;
switch([e type]){
case NSEventTypeMagnify:
#if OSX_VERSION >= 100700
[win.obj toggleFullScreen:nil];
#endif
break;
case NSEventTypeSwipe:
dx = - [e deltaX];
dy = - [e deltaY];
if(dx == -1)
sendcmd('x', &undo);
else
if(dx == +1)
sendcmd('v', &undo);
else
if(dy == -1)
sendexec(0);
else
if(dy == +1)
sendexec(1);
else // fingers lifted
undo = 0;
break;
// When I lift the fingers from the trackpad, I
// receive 1, 2, or 3 events "touchesEndedWithEvent".
// Their type is either generic (NSEventTypeGesture)
// or specific (NSEventTypeSwipe for example). I
// always receive at least 1 event of specific type.
// I sometimes receive NSEventTypeEndGesture
// apparently, even without implementing
// "endGestureWithEvent"
// I even received a NSEventTypeBeginGesture once.
case NSEventTypeBeginGesture:
break;
case NSEventTypeGesture:
case NSEventTypeEndGesture:
// do a undo here? because 2 times I had the impression undo was still 1
// after having lifted my fingers
undo = 0;
break;
default:
DEBUG(@"getgesture: unexpected event type: %d", [e type]);
}
}
static void
sendcmd(int c, int *undo)
{
if(*undo)
c = 'z';
*undo = ! *undo;
keystroke(Kcmd+c);
}
static void
sendexec(int giveargs)
{
mbuttons = 2;
sendmouse(0);
if(giveargs){
mbuttons |= 1;
sendmouse(0);
}
mbuttons = 0;
sendmouse(0);
}
static uint
msec(void)
{
return nsec()/1000000;
}
static void
sendmouse(int resized)
{
if(resized)
mouseresized = 1;
mouserect = win.imgbuf->r;
mousetrack(mpos.x, mpos.y, kbuttons|mbuttons|scroll, msec());
scroll = 0;
}
void
setmouse(Point p)
{
NSPoint q;
NSRect r;
r = [[NSScreen mainScreen] frame];
q = NSMakePoint(p.x,p.y);
q = [[win.obj contentView] convertPoint:q toView:nil];
q = [win.obj convertBaseToScreen:q];
q.y = r.size.height - q.y;
CGWarpMouseCursorPosition(q);
// race condition
mpos = p;
}
// setBadgeLabel don't have to be in this function.
// Remove seticon's argument too.
static void
seticon(NSString *s)
{
NSData *d;
NSImage *i;
d = [[NSData alloc]
initWithBytes:glenda_png
length:(sizeof glenda_png)];
i = [[NSImage alloc] initWithData:d];
if(i){
[NSApp setApplicationIconImage:i];
[[NSApp dockTile] display];
[[NSApp dockTile] setBadgeLabel:s];
}
[d release];
[i release];
}
// Menu should be called during app creation, not window creation.
// See ./osx-delegate.m implementation.
// If an application supports fullscreen, it should
// add an "Enter Full Screen" menu item to the View
// menu. The menu item is now available through
// Xcode 4. You can also add the item
// programmatically, with toggleFullScreen: as the
// action, nil as the target, and cmd-ctrl-f as the
// key equivalent. AppKit will automatically update
// the menu item title as part of its menu item
// validation.
static void
makemenu(NSString *s)
{
NSString *title;
NSMenu *menu;
NSMenuItem *appmenu, *item;
menu = [NSMenu new];
appmenu = [NSMenuItem new];
[menu addItem:appmenu];
[NSApp setMenu:menu];
[menu release];
title = [@"Quit " stringByAppendingString:win.label];
item = [[NSMenuItem alloc]
initWithTitle:title
action:@selector(terminate:) keyEquivalent:@"q"];
menu = [NSMenu new];
[menu addItem:item];
[item release];
[appmenu setSubmenu:menu];
[appmenu release];
[menu release];
}
QLock snarfl;
char*
getsnarf(void)
{
NSString *s;
NSPasteboard *pb;
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;
// should I call autorelease here for example?
}
void
putsnarf(char *s)
{
NSArray *t;
NSString *str;
NSPasteboard *pb;
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)
DEBUG(@"putsnarf: setString failed");
}
void
kicklabel(char *c)
{
}
void
setcursor(Cursor *c)
{
}