blob: 7b1da4f6802b940ddfa94fb6505aec423db2cff6 [file] [log] [blame]
#define Point OSXPoint
#define Rect OSXRect
#define Cursor OSXCursor
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#undef Rect
#undef Point
#undef Cursor
#undef offsetof
#undef nil
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <mouse.h>
#include <cursor.h>
#include "osx-screen.h"
#include "osx-keycodes.h"
#include "devdraw.h"
#include "glendapng.h"
AUTOFRAMEWORK(Cocoa)
#define panic sysfatal
extern Rectangle mouserect;
struct {
char *label;
char *winsize;
QLock labellock;
Rectangle fullscreenr;
Rectangle screenr;
Memimage *screenimage;
int isfullscreen;
ulong fullscreentime;
Point xy;
int buttons;
int kbuttons;
CGDataProviderRef provider;
NSWindow *window;
CGImageRef image;
CGContextRef windowctx;
int needflush;
QLock flushlock;
int active;
int infullscreen;
int kalting; // last keystroke was Kalt
} osx;
enum
{
WindowAttrs = NSClosableWindowMask |
NSTitledWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
};
static void screenproc(void*);
void eresized(int);
void fullscreen(int);
void seticon(void);
static void activated(int);
enum
{
CmdFullScreen = 1,
};
@interface P9View : NSView
{}
@end
@implementation P9View
- (BOOL)acceptsFirstResponder
{
return YES;
}
@end
void screeninit(void);
void _flushmemscreen(Rectangle r);
Memimage*
attachscreen(char *label, char *winsize)
{
if(label == nil)
label = "gnot a label";
osx.label = strdup(label);
osx.winsize = winsize;
if(osx.screenimage == nil){
screeninit();
if(osx.screenimage == nil)
panic("cannot create OS X screen");
}
return osx.screenimage;
}
void
_screeninit(void)
{
CGRect cgr;
NSRect or;
Rectangle r;
int havemin;
memimageinit();
cgr = CGDisplayBounds(CGMainDisplayID());
osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height);
// Create the window.
r = Rect(0, 0, Dx(osx.fullscreenr)*2/3, Dy(osx.fullscreenr)*2/3);
havemin = 0;
if(osx.winsize && osx.winsize[0]){
if(parsewinsize(osx.winsize, &r, &havemin) < 0)
sysfatal("%r");
}
if(!havemin)
r = rectaddpt(r, Pt((Dx(osx.fullscreenr)-Dx(r))/2, (Dy(osx.fullscreenr)-Dy(r))/2));
or = NSMakeRect(r.min.x, r.min.y, r.max.x, r.max.y);
osx.window = [[NSWindow alloc] initWithContentRect:or styleMask:WindowAttrs
backing:NSBackingStoreBuffered defer:NO screen:[NSScreen mainScreen]];
[osx.window setDelegate:[NSApp delegate]];
[osx.window setAcceptsMouseMovedEvents:YES];
P9View *view = [[P9View alloc] initWithFrame:or];
[osx.window setContentView:view];
[view release];
setlabel(osx.label);
seticon();
// Finally, put the window on the screen.
eresized(0);
[osx.window makeKeyAndOrderFront:nil];
[NSCursor unhide];
}
static Rendez scr;
static QLock slock;
void
screeninit(void)
{
scr.l = &slock;
qlock(scr.l);
// proccreate(screenproc, nil, 256*1024);
screenproc(NULL);
while(osx.window == nil)
rsleep(&scr);
qunlock(scr.l);
}
static void
screenproc(void *v)
{
qlock(scr.l);
_screeninit();
rwakeup(&scr);
qunlock(scr.l);
}
static ulong
msec(void)
{
return nsec()/1000000;
}
//static void
void
mouseevent(NSEvent *event)
{
int wheel;
NSPoint op;
op = [event locationInWindow];
osx.xy = subpt(Pt(op.x, op.y), osx.screenr.min);
wheel = 0;
switch([event type]){
case NSScrollWheel:;
CGFloat delta = [event deltaY];
if(delta > 0)
wheel = 8;
else
wheel = 16;
break;
case NSLeftMouseDown:
case NSRightMouseDown:
case NSOtherMouseDown:
case NSLeftMouseUp:
case NSRightMouseUp:
case NSOtherMouseUp:;
NSInteger but;
NSUInteger mod;
but = [event buttonNumber];
mod = [event modifierFlags];
// OS X swaps button 2 and 3
but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1);
but = mouseswap(but);
// Apply keyboard modifiers and pretend it was a real mouse button.
// (Modifiers typed while holding the button go into kbuttons,
// but this one does not.)
if(but == 1){
if(mod & NSAlternateKeyMask) {
// Take the ALT away from the keyboard handler.
if(osx.kalting) {
osx.kalting = 0;
keystroke(Kalt);
}
but = 2;
}
else if(mod & NSCommandKeyMask)
but = 4;
}
osx.buttons = but;
break;
case NSMouseMoved:
case NSLeftMouseDragged:
case NSRightMouseDragged:
case NSOtherMouseDragged:
break;
default:
return;
}
mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec());
}
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
void
kbdevent(NSEvent *event)
{
char ch;
UInt32 code;
UInt32 mod;
int k;
ch = [[event characters] characterAtIndex:0];
code = [event keyCode];
mod = [event modifierFlags];
switch([event type]){
case NSKeyDown:
osx.kalting = 0;
if(mod == NSCommandKeyMask){
if(ch == 'F' || ch == 'f'){
if(osx.isfullscreen && msec() - osx.fullscreentime > 500)
fullscreen(0);
return;
}
// Pass most Cmd keys through as Kcmd + ch.
// OS X interprets a few no matter what we do,
// so it is useless to pass them through as keystrokes too.
switch(ch) {
case 'm': // minimize window
case 'h': // hide window
case 'H': // hide others
case 'q': // quit
return;
}
if(' ' <= ch && ch <= '~') {
keystroke(Kcmd + ch);
return;
}
return;
}
k = ch;
if(code < nelem(keycvt) && keycvt[code])
k = keycvt[code];
if(k >= 0)
keystroke(k);
else{
keystroke(ch);
}
break;
case NSFlagsChanged:
if(!osx.buttons && !osx.kbuttons){
if(mod == NSAlternateKeyMask) {
osx.kalting = 1;
keystroke(Kalt);
}
break;
}
// If the mouse button is being held down, treat
// changes in the keyboard modifiers as changes
// in the mouse buttons.
osx.kbuttons = 0;
if(mod & NSAlternateKeyMask)
osx.kbuttons |= 2;
if(mod & NSCommandKeyMask)
osx.kbuttons |= 4;
mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
break;
}
return;
}
//static void
void
eresized(int new)
{
Memimage *m;
NSRect or;
ulong chan;
Rectangle r;
int bpl;
CGDataProviderRef provider;
CGImageRef image;
CGColorSpaceRef cspace;
or = [[osx.window contentView] bounds];
r = Rect(or.origin.x, or.origin.y, or.size.width, or.size.height);
if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr)){
// No need to make new image.
osx.screenr = r;
return;
}
chan = XBGR32;
m = allocmemimage(Rect(0, 0, Dx(r), Dy(r)), chan);
if(m == nil)
panic("allocmemimage: %r");
if(m->data == nil)
panic("m->data == nil");
bpl = bytesperline(r, 32);
provider = CGDataProviderCreateWithData(0,
m->data->bdata, Dy(r)*bpl, 0);
//cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
cspace = CGColorSpaceCreateDeviceRGB();
image = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
cspace,
kCGImageAlphaNoneSkipLast,
provider, 0, 0, kCGRenderingIntentDefault);
CGColorSpaceRelease(cspace);
CGDataProviderRelease(provider); // CGImageCreate did incref
mouserect = m->r;
if(new){
mouseresized = 1;
mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
}
// termreplacescreenimage(m);
_drawreplacescreenimage(m); // frees old osx.screenimage if any
if(osx.image)
CGImageRelease(osx.image);
osx.image = image;
osx.screenimage = m;
osx.screenr = r;
}
void
flushproc(void *v)
{
for(;;){
if(osx.needflush && osx.windowctx && canqlock(&osx.flushlock)){
if(osx.windowctx){
CGContextFlush(osx.windowctx);
osx.needflush = 0;
}
qunlock(&osx.flushlock);
}
usleep(33333);
}
}
void
_flushmemscreen(Rectangle r)
{
CGRect cgr;
CGImageRef subimg;
qlock(&osx.flushlock);
if(osx.windowctx == nil){
osx.windowctx = [[osx.window graphicsContext] graphicsPort];
// [osx.window flushWindow];
// proccreate(flushproc, nil, 256*1024);
}
cgr.origin.x = r.min.x;
cgr.origin.y = r.min.y;
cgr.size.width = Dx(r);
cgr.size.height = Dy(r);
subimg = CGImageCreateWithImageInRect(osx.image, cgr);
cgr.origin.y = Dy(osx.screenr) - r.max.y; // XXX how does this make any sense?
CGContextDrawImage(osx.windowctx, cgr, subimg);
osx.needflush = 1;
qunlock(&osx.flushlock);
CGImageRelease(subimg);
}
void
activated(int active)
{
osx.active = active;
}
void
fullscreen(int wascmd)
{
NSView *view = [osx.window contentView];
if(osx.isfullscreen){
[view exitFullScreenModeWithOptions:nil];
osx.isfullscreen = 0;
}else{
[view enterFullScreenMode:[osx.window screen] withOptions:nil];
osx.isfullscreen = 1;
osx.fullscreentime = msec();
}
eresized(1);
}
void
setmouse(Point p)
{
CGPoint cgp;
cgp.x = p.x + osx.screenr.min.x;
cgp.y = p.y + osx.screenr.min.y;
CGWarpMouseCursorPosition(cgp);
}
void
setcursor(Cursor *c)
{
NSImage *image;
NSBitmapImageRep *bitmap;
NSCursor *nsc;
unsigned char *planes[5];
int i;
if(c == nil){
[NSCursor pop];
return;
}
image = [[NSImage alloc] initWithSize:NSMakeSize(16.0, 16.0)];
bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:16
pixelsHigh:16
bitsPerSample:1
samplesPerPixel:2
hasAlpha:YES
isPlanar:YES
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:2
bitsPerPixel:1];
[bitmap getBitmapDataPlanes:planes];
for(i=0; i<16; i++){
planes[0][i] = ((ushort*)c->set)[i];
planes[1][i] = planes[0][i] | ((ushort*)c->clr)[i];
}
[image addRepresentation:bitmap];
nsc = [[NSCursor alloc] initWithImage:image
hotSpot:NSMakePoint(c->offset.x, c->offset.y)];
[nsc push];
[image release];
[bitmap release];
[nsc release];
}
void
getcolor(ulong i, ulong *r, ulong *g, ulong *b)
{
ulong v;
v = 0;
*r = (v>>16)&0xFF;
*g = (v>>8)&0xFF;
*b = v&0xFF;
}
int
setcolor(ulong i, ulong r, ulong g, ulong b)
{
/* no-op */
return 0;
}
int
hwdraw(Memdrawparam *p)
{
return 0;
}
struct {
QLock lk;
char buf[SnarfSize];
Rune rbuf[SnarfSize];
NSPasteboard *apple;
} clip;
char*
getsnarf(void)
{
char *s, *t;
NSArray *types;
NSString *string;
NSData * data;
NSUInteger ndata;
/* fprint(2, "applegetsnarf\n"); */
qlock(&clip.lk);
clip.apple = [NSPasteboard generalPasteboard];
types = [clip.apple types];
string = [clip.apple stringForType:NSStringPboardType];
if(string == nil){
fprint(2, "apple pasteboard get item type failed\n");
qunlock(&clip.lk);
return nil;
}
data = [string dataUsingEncoding:NSUnicodeStringEncoding];
if(data != nil){
ndata = [data length];
qunlock(&clip.lk);
s = smprint("%.*S", ndata/2, (Rune*)[data bytes]);
for(t=s; *t; t++)
if(*t == '\r')
*t = '\n';
return s;
}
qunlock(&clip.lk);
return nil;
}
void
putsnarf(char *s)
{
NSArray *pboardTypes;
NSString *string;
/* fprint(2, "appleputsnarf\n"); */
if(strlen(s) >= SnarfSize)
return;
qlock(&clip.lk);
strcpy(clip.buf, s);
runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s);
pboardTypes = [NSArray arrayWithObject:NSStringPboardType];
clip.apple = [NSPasteboard generalPasteboard];
[clip.apple declareTypes:pboardTypes owner:nil];
assert(sizeof(clip.rbuf[0]) == 2);
string = [NSString stringWithCharacters:clip.rbuf length:runestrlen(clip.rbuf)*2];
if(string == nil){
fprint(2, "apple pasteboard data create failed\n");
qunlock(&clip.lk);
return;
}
if(![clip.apple setString:string forType:NSStringPboardType]){
fprint(2, "apple pasteboard putitem failed\n");
qunlock(&clip.lk);
return;
}
qunlock(&clip.lk);
}
void
setlabel(char *label)
{
CFStringRef cs;
cs = CFStringCreateWithBytes(nil, (uchar*)label, strlen(osx.label), kCFStringEncodingUTF8, false);
[osx.window setTitle:(NSString*)cs];
CFRelease(cs);
}
void
kicklabel(char *label)
{
char *p;
p = strdup(label);
if(p == nil)
return;
qlock(&osx.labellock);
free(osx.label);
osx.label = p;
qunlock(&osx.labellock);
setlabel(label);
}
// static void
void
seticon(void)
{
NSImage *im;
NSData *d;
d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
im = [[NSImage alloc] initWithData:d];
if(im){
NSLog(@"here");
[NSApp setApplicationIconImage:im];
[[NSApp dockTile] setShowsApplicationBadge:YES];
[[NSApp dockTile] display];
}
[d release];
[im release];
}