#define Point OSXPoint #define Rect OSXRect #define Cursor OSXCursor #include #import #ifdef MULTITOUCH #include #include #endif #undef Rect #undef Point #undef Cursor #undef offsetof #undef nil #include "u.h" #include "libc.h" #include #include #include #include #include "mouse.h" #include #include "osx-screen.h" #include "osx-keycodes.h" #include "devdraw.h" #include "glendapng.h" AUTOFRAMEWORK(Carbon) AUTOFRAMEWORK(Cocoa) #ifdef MULTITOUCH AUTOFRAMEWORK(MultitouchSupport) AUTOFRAMEWORK(IOKit) #endif #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; MenuRef wmenu; MenuRef vmenu; WindowRef window; CGImageRef image; CGContextRef windowctx; PasteboardRef snarf; int needflush; QLock flushlock; int active; int infullscreen; int kalting; // last keystroke was Kalt int touched; // last mouse event was touchCallback int collapsed; // parked in dock int flushing; // flushproc has started NSMutableArray* devicelist; } osx; /* These structs are required, in order to handle some parameters returned from the Support.framework */ typedef struct { float x; float y; }mtPoint; typedef struct { mtPoint position; mtPoint velocity; }mtReadout; /* Some reversed engineered informations from MultiTouchSupport.framework */ typedef struct { int frame; //the current frame double timestamp; //event timestamp int identifier; //identifier guaranteed unique for life of touch per device int state; //the current state (not sure what the values mean) int unknown1; //no idea what this does int unknown2; //no idea what this does either mtReadout normalized; //the normalized position and vector of the touch (0,0 to 1,1) float size; //the size of the touch (the area of your finger being tracked) int unknown3; //no idea what this does float angle; //the angle of the touch -| float majorAxis; //the major axis of the touch -|-- an ellipsoid. you can track the angle of each finger! float minorAxis; //the minor axis of the touch -| mtReadout unknown4; //not sure what this is for int unknown5[2]; //no clue float unknown6; //no clue }Touch; //a reference pointer for the multitouch device typedef void *MTDeviceRef; //the prototype for the callback function typedef int (*MTContactCallbackFunction)(int,Touch*,int,double,int); //returns a pointer to the default device (the trackpad?) MTDeviceRef MTDeviceCreateDefault(void); //returns a CFMutableArrayRef array of all multitouch devices CFMutableArrayRef MTDeviceCreateList(void); //registers a device's frame callback to your callback function void MTRegisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction); void MTUnregisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction); //start sending events void MTDeviceStart(MTDeviceRef, int); void MTDeviceStop(MTDeviceRef); MTDeviceRef MTDeviceCreateFromService(io_service_t); io_service_t MTDeviceGetService(MTDeviceRef); #define kNTracks 10 struct TouchTrack { int id; float firstThreshTime; mtPoint pos; } tracks[kNTracks]; #define kSizeSensitivity 1.25f #define kTimeSensitivity 0.03f /* seconds */ #define kButtonLimit 0.6f /* percentage from base of pad */ int findTrack(int id) { int i; for(i = 0; i < kNTracks; ++i) if(tracks[i].id == id) return i; return -1; } #define kMoveSensitivity 0.05f int moved(mtPoint a, mtPoint b) { if(fabs(a.x - b.x) > kMoveSensitivity) return 1; if(fabs(a.y - b.y) > kMoveSensitivity) return 1; return 0; } int classifyTouch(Touch *t) { mtPoint p; int i; p = t->normalized.position; i = findTrack(t->identifier); if(i == -1) { i = findTrack(-1); if(i == -1) return 0; // No empty tracks. tracks[i].id = t->identifier; tracks[i].firstThreshTime = t->timestamp; tracks[i].pos = p; // we don't have a touch yet - we wait kTimeSensitivity before reporting it. return 0; } if(t->size == 0) { // lost touch tracks[i].id = -1; return 0; } if(t->size < kSizeSensitivity) { tracks[i].firstThreshTime = t->timestamp; } if((t->timestamp - tracks[i].firstThreshTime) < kTimeSensitivity) { return 0; } if(p.y > kButtonLimit && t->size > kSizeSensitivity) { if(p.x < 0.35) return 1; if(p.x > 0.65) return 4; if(p.x > 0.35 && p.x < 0.65) return 2; } return 0; } static ulong msec(void); int touchCallback(int device, Touch *data, int nFingers, double timestamp, int frame) { #ifdef MULTITOUCH int buttons, delta, i; static int obuttons; CGPoint p; CGEventRef e; p.x = osx.xy.x+osx.screenr.min.x; p.y = osx.xy.y+osx.screenr.min.y; if(!ptinrect(Pt(p.x, p.y), osx.screenr)) return 0; osx.touched = 1; buttons = 0; for(i = 0; i < nFingers; ++i) buttons |= classifyTouch(data+i); delta = buttons ^ obuttons; obuttons = buttons; if(delta & 1) { e = CGEventCreateMouseEvent(NULL, (buttons & 1) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp, p, 29); CGEventPost(kCGSessionEventTap, e); CFRelease(e); } if(delta & 2) { e = CGEventCreateMouseEvent(NULL, (buttons & 2) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp, p, 30); CGEventPost(kCGSessionEventTap, e); CFRelease(e); } if(delta & 4){ e = CGEventCreateMouseEvent(NULL, (buttons & 4) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp, p, 31); CGEventPost(kCGSessionEventTap, e); CFRelease(e); } return delta != 0; #else return 0; #endif } extern int multitouch; enum { WindowAttrs = kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowResizableAttribute | kWindowStandardHandlerAttribute | kWindowFullZoomAttribute }; enum { P9PEventLabelUpdate = 1 }; static void screenproc(void*); static void eresized(int); static void fullscreen(int); static void seticon(void); static void activated(int); static OSStatus quithandler(EventHandlerCallRef, EventRef, void*); static OSStatus eventhandler(EventHandlerCallRef, EventRef, void*); static OSStatus cmdhandler(EventHandlerCallRef, EventRef, void*); enum { CmdFullScreen = 1, }; void screeninit(void); void _flushmemscreen(Rectangle r); #ifdef MULTITOUCH static void RegisterMultitouch(void *ctx, io_iterator_t iter) { io_object_t io; MTDeviceRef dev; while((io = IOIteratorNext(iter)) != 0){ dev = MTDeviceCreateFromService(io); if (dev != nil){ MTRegisterContactFrameCallback(dev, touchCallback); [osx.devicelist addObject:dev]; if(osx.active) MTDeviceStart(dev, 0); } IOObjectRelease(io); } } static void UnregisterMultitouch(void *ctx, io_iterator_t iter) { io_object_t io; MTDeviceRef dev; int i; while((io = IOIteratorNext(iter)) != 0){ for(i = 0; i < [osx.devicelist count]; i++){ dev = [osx.devicelist objectAtIndex:i]; if(IOObjectIsEqualTo(MTDeviceGetService(dev), io)){ if(osx.active) MTDeviceStop(dev); MTUnregisterContactFrameCallback(dev, touchCallback); [osx.devicelist removeObjectAtIndex:i]; break; } } IOObjectRelease(io); } } #endif /*MULTITOUCH*/ static void InitMultiTouch() { #ifdef MULTITOUCH IONotificationPortRef port; CFRunLoopSourceRef source; io_iterator_t iter; kern_return_t kr; io_object_t obj; int i; if(!multitouch) return; osx.devicelist = [[NSMutableArray alloc] init]; for(i = 0; i < kNTracks; ++i) tracks[i].id = -1; port = IONotificationPortCreate(kIOMasterPortDefault); if(port == nil){ fprint(2, "failed to get an IO notification port\n"); return; } source = IONotificationPortGetRunLoopSource(port); if(source == nil){ fprint(2, "failed to get loop source for port"); return; } CFRunLoopAddSource( (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop()), source, kCFRunLoopDefaultMode); kr = IOServiceAddMatchingNotification( port, kIOTerminatedNotification, IOServiceMatching("AppleMultitouchDevice"), &UnregisterMultitouch, nil, &iter); if(kr != KERN_SUCCESS){ fprint(2, "failed to add termination notification\n"); return; } /* Arm the notification */ while((obj = IOIteratorNext(iter)) != 0) IOObjectRelease(obj); kr = IOServiceAddMatchingNotification( port, kIOMatchedNotification, IOServiceMatching("AppleMultitouchDevice"), &RegisterMultitouch, nil, &iter); if(kr != KERN_SUCCESS){ fprint(2, "failed to add matching notification\n"); return; } RegisterMultitouch(nil, iter); #endif } 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; } extern int multitouch; void _screeninit(void) { CGRect cgr; OSXRect or; Rectangle r; int havemin; memimageinit(); ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, kProcessTransformToForegroundApplication); SetFrontProcess(&psn); cgr = CGDisplayBounds(CGMainDisplayID()); osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height); InitCursor(); // Create minimal menu with full-screen option. ClearMenuBar(); CreateStandardWindowMenu(0, &osx.wmenu); InsertMenu(osx.wmenu, 0); MenuItemIndex ix; CreateNewMenu(1004, 0, &osx.vmenu); // XXX 1004? SetMenuTitleWithCFString(osx.vmenu, CFSTR("View")); AppendMenuItemTextWithCFString(osx.vmenu, CFSTR("Full Screen"), 0, CmdFullScreen, &ix); SetMenuItemCommandKey(osx.vmenu, ix, 0, 'F'); AppendMenuItemTextWithCFString(osx.vmenu, CFSTR("Cmd-F exits full screen"), kMenuItemAttrDisabled, CmdFullScreen, &ix); InsertMenu(osx.vmenu, GetMenuID(osx.wmenu)); DrawMenuBar(); // 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.left = r.min.x; or.top = r.min.y; or.right = r.max.x; or.bottom = r.max.y; CreateNewWindow(kDocumentWindowClass, WindowAttrs, &or, &osx.window); setlabel(osx.label); seticon(); // Set up the clip board. if(PasteboardCreate(kPasteboardClipboard, &osx.snarf) != noErr) panic("pasteboard create"); // Explain in great detail which events we want to handle. // Why can't we just have one handler? const EventTypeSpec quits[] = { { kEventClassApplication, kEventAppQuit } }; const EventTypeSpec cmds[] = { { kEventClassWindow, kEventWindowClosed }, { kEventClassWindow, kEventWindowBoundsChanged }, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassCommand, kEventCommandProcess }, { kEventClassWindow, kEventWindowActivated }, { kEventClassWindow, kEventWindowDeactivated }, { kEventClassWindow, kEventWindowCollapsed }, { kEventClassWindow, kEventWindowExpanded }, }; const EventTypeSpec events[] = { { kEventClassApplication, kEventAppShown }, { kEventClassKeyboard, kEventRawKeyDown }, { kEventClassKeyboard, kEventRawKeyModifiersChanged }, { kEventClassKeyboard, kEventRawKeyRepeat }, { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseUp }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassMouse, kEventMouseWheelMoved }, { 'P9PE', P9PEventLabelUpdate} }; InstallApplicationEventHandler( NewEventHandlerUPP(quithandler), nelem(quits), quits, nil, nil); InstallApplicationEventHandler( NewEventHandlerUPP(eventhandler), nelem(events), events, nil, nil); InstallWindowEventHandler(osx.window, NewEventHandlerUPP(cmdhandler), nelem(cmds), cmds, osx.window, nil); // Finally, put the window on the screen. ShowWindow(osx.window); ShowMenuBar(); eresized(0); SelectWindow(osx.window); if(multitouch) InitMultiTouch(); // CoreGraphics pins mouse events to the destination point of a // CGWarpMouseCursorPosition (see setmouse) for an interval of time // following the move. Disable this by setting the interval to zero // seconds. CGSetLocalEventsSuppressionInterval(0.0); InitCursor(); } static Rendez scr; static QLock slock; void screeninit(void) { scr.l = &slock; qlock(scr.l); proccreate(screenproc, nil, 256*1024); while(osx.window == nil) rsleep(&scr); qunlock(scr.l); } static void screenproc(void *v) { qlock(scr.l); _screeninit(); rwakeup(&scr); qunlock(scr.l); RunApplicationEventLoop(); } static OSStatus kbdevent(EventRef); static OSStatus mouseevent(EventRef); static OSStatus cmdhandler(EventHandlerCallRef next, EventRef event, void *arg) { return eventhandler(next, event, arg); } static OSStatus quithandler(EventHandlerCallRef next, EventRef event, void *arg) { exit(0); return 0; } static OSStatus eventhandler(EventHandlerCallRef next, EventRef event, void *arg) { OSStatus result; result = CallNextEventHandler(next, event); switch(GetEventClass(event)){ case 'P9PE': if(GetEventKind(event) == P9PEventLabelUpdate) { qlock(&osx.labellock); setlabel(osx.label); qunlock(&osx.labellock); return noErr; } else return eventNotHandledErr; case kEventClassApplication:; Rectangle r = Rect(0, 0, Dx(osx.screenr), Dy(osx.screenr)); _flushmemscreen(r); return eventNotHandledErr; case kEventClassKeyboard: return kbdevent(event); case kEventClassMouse: return mouseevent(event); case kEventClassCommand:; HICommand cmd; GetEventParameter(event, kEventParamDirectObject, typeHICommand, nil, sizeof cmd, nil, &cmd); switch(cmd.commandID){ case kHICommandQuit: exit(0); case CmdFullScreen: fullscreen(1); break; default: return eventNotHandledErr; } break; case kEventClassWindow: switch(GetEventKind(event)){ case kEventWindowClosed: exit(0); case kEventWindowBoundsChanged:; // We see kEventWindowDrawContent // if we grow a window but not if we shrink it. UInt32 flags; GetEventParameter(event, kEventParamAttributes, typeUInt32, 0, sizeof flags, 0, &flags); int new = (flags & kWindowBoundsChangeSizeChanged) != 0; eresized(new); break; case kEventWindowDrawContent: // Tried using just flushmemimage here, but // it causes an odd artifact in which making a window // bigger in both width and height can then only draw // on the new border: it's like the old window is stuck // floating on top. Doing a full "get a new window" // seems to solve the problem. eresized(1); break; case kEventWindowActivated: if(!osx.collapsed) activated(1); return eventNotHandledErr; case kEventWindowDeactivated: activated(0); return eventNotHandledErr; case kEventWindowCollapsed: osx.collapsed = 1; activated(0); return eventNotHandledErr; case kEventWindowExpanded: osx.collapsed = 0; activated(1); return eventNotHandledErr; default: return eventNotHandledErr; } break; } return result; } static ulong msec(void) { return nsec()/1000000; } static OSStatus mouseevent(EventRef event) { int wheel; OSXPoint op; GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, 0, sizeof op, 0, &op); osx.xy = subpt(Pt(op.h, op.v), osx.screenr.min); wheel = 0; switch(GetEventKind(event)){ case kEventMouseWheelMoved:; SInt32 delta; GetEventParameter(event, kEventParamMouseWheelDelta, typeSInt32, 0, sizeof delta, 0, &delta); // if I have any active touches in my region, I need to ignore the wheel motion. //int i; //for(i = 0; i < kNTracks; ++i) { // if(tracks[i].id != -1 && tracks[i].pos.y > kButtonLimit) break; //} //if(i == kNTracks) { // No active touches, go ahead and scroll. if(delta > 0) wheel = 8; else wheel = 16; //} break; case kEventMouseDown: case kEventMouseUp:; UInt32 but, mod; GetEventParameter(event, kEventParamMouseChord, typeUInt32, 0, sizeof but, 0, &but); GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, sizeof mod, 0, &mod); // OS X swaps button 2 and 3 but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1); but = (but & ~((1<<10)-1)) | mouseswap(but & ((1<<10)-1)); if(osx.touched) { // in multitouch we use the clicks down to enable our // virtual buttons. if(but & 0x7) { if(but>>29) but = but >> 29; } else but = 0; osx.touched = 0; } // 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 & optionKey) { // Take the ALT away from the keyboard handler. if(osx.kalting) { osx.kalting = 0; keystroke(Kalt); } but = 2; } else if(mod & cmdKey) but = 4; } osx.buttons = but; break; case kEventMouseMoved: case kEventMouseDragged: break; default: return eventNotHandledErr; } mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec()); return noErr; } 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 OSStatus kbdevent(EventRef event) { char ch; UInt32 code; UInt32 mod; int k; GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar, nil, sizeof ch, nil, &ch); GetEventParameter(event, kEventParamKeyCode, typeUInt32, nil, sizeof code, nil, &code); GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, nil, sizeof mod, nil, &mod); switch(GetEventKind(event)){ case kEventRawKeyDown: case kEventRawKeyRepeat: osx.kalting = 0; if(mod == cmdKey){ if(ch == 'F' || ch == 'f'){ if(osx.isfullscreen && msec() - osx.fullscreentime > 500) fullscreen(0); return noErr; } // 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 eventNotHandledErr; } if(' ' <= ch && ch <= '~') { keystroke(Kcmd + ch); return noErr; } return eventNotHandledErr; } k = ch; if(code < nelem(keycvt) && keycvt[code]) k = keycvt[code]; if(k == 0) return noErr; if(k > 0) keystroke(k); else{ UniChar uc; OSStatus s; s = GetEventParameter(event, kEventParamKeyUnicodes, typeUnicodeText, nil, sizeof uc, nil, &uc); if(s == noErr) keystroke(uc); } break; case kEventRawKeyModifiersChanged: if(!osx.buttons && !osx.kbuttons){ if(mod == optionKey) { 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 & optionKey) osx.kbuttons |= 2; if(mod & cmdKey) osx.kbuttons |= 4; mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec()); break; } return noErr; } static void eresized(int new) { Memimage *m; OSXRect or; ulong chan; Rectangle r; int bpl; CGDataProviderRef provider; CGImageRef image; CGColorSpaceRef cspace; GetWindowBounds(osx.window, kWindowContentRgn, &or); r = Rect(or.left, or.top, or.right, or.bottom); if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr) && !new){ // 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; if(new){ qlock(&osx.flushlock); QDEndCGContext(GetWindowPort(osx.window), &osx.windowctx); osx.windowctx = nil; qunlock(&osx.flushlock); } } 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){ QDBeginCGContext(GetWindowPort(osx.window), &osx.windowctx); if(!osx.flushing) { proccreate(flushproc, nil, 256*1024); osx.flushing = 1; } } 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) { #ifdef MULTITOUCH int i; if(active) { for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices MTDeviceStart([osx.devicelist objectAtIndex:i], 0); //start sending events } } else { osx.xy.x = -10000; for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices MTDeviceStop([osx.devicelist objectAtIndex:i]); //stop sending events } for(i = 0; igdRect; if(dr.top == 0 && dr.left == 0) HideMenuBar(); GetWindowBounds(osx.window, kWindowContentRgn, &oldrect); ChangeWindowAttributes(osx.window, kWindowNoTitleBarAttribute, kWindowResizableAttribute); MoveWindow(osx.window, 0, 0, 1); MoveWindow(osx.window, dr.left, dr.top, 0); SizeWindow(osx.window, dr.right - dr.left, dr.bottom - dr.top, 0); osx.isfullscreen = 1; }else{ ShowMenuBar(); ChangeWindowAttributes(osx.window, kWindowResizableAttribute, kWindowNoTitleBarAttribute); SizeWindow(osx.window, oldrect.right - oldrect.left, oldrect.bottom - oldrect.top, 0); MoveWindow(osx.window, oldrect.left, oldrect.top, 0); osx.isfullscreen = 0; } 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); osx.xy = p; } void setcursor(Cursor *c) { OSXCursor oc; int i; if(c == nil){ InitCursor(); return; } // SetCursor is deprecated, but what replaces it? for(i=0; i<16; i++){ oc.data[i] = ((ushort*)c->set)[i]; oc.mask[i] = oc.data[i] | ((ushort*)c->clr)[i]; } oc.hotSpot.h = - c->offset.x; oc.hotSpot.v = - c->offset.y; SetCursor(&oc); } 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]; PasteboardRef apple; } clip; char* getsnarf(void) { char *s; CFArrayRef flavors; CFDataRef data; CFIndex nflavor, ndata, j; CFStringRef type; ItemCount nitem; PasteboardItemID id; PasteboardSyncFlags flags; UInt32 i; u16int *u; Fmt fmt; Rune r; /* fprint(2, "applegetsnarf\n"); */ qlock(&clip.lk); clip.apple = osx.snarf; if(clip.apple == nil){ if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){ fprint(2, "apple pasteboard create failed\n"); qunlock(&clip.lk); return nil; } } flags = PasteboardSynchronize(clip.apple); if(flags&kPasteboardClientIsOwner){ s = strdup(clip.buf); qunlock(&clip.lk); return s; } if(PasteboardGetItemCount(clip.apple, &nitem) != noErr){ fprint(2, "apple pasteboard get item count failed\n"); qunlock(&clip.lk); return nil; } for(i=1; i<=nitem; i++){ if(PasteboardGetItemIdentifier(clip.apple, i, &id) != noErr) continue; if(PasteboardCopyItemFlavors(clip.apple, id, &flavors) != noErr) continue; nflavor = CFArrayGetCount(flavors); for(j=0; j= SnarfSize) return; qlock(&clip.lk); strcpy(clip.buf, s); runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s); clip.apple = osx.snarf; if(PasteboardClear(clip.apple) != noErr){ fprint(2, "apple pasteboard clear failed\n"); qunlock(&clip.lk); return; } flags = PasteboardSynchronize(clip.apple); if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){ fprint(2, "apple pasteboard cannot assert ownership\n"); qunlock(&clip.lk); return; } u = malloc(runestrlen(clip.rbuf)*4); p = u; for(i=0; clip.rbuf[i]; i++) { r = clip.rbuf[i]; // convert to utf-16 if(0xd800 <= r && r < 0xe000) r = Runeerror; if(r >= 0x10000) { r -= 0x10000; *p++ = 0xd800 + (r>>10); *p++ = 0xdc00 + (r & ((1<<10)-1)); } else *p++ = r; } cfdata = CFDataCreate(kCFAllocatorDefault, (uchar*)u, (p-u)*2); free(u); if(cfdata == nil){ fprint(2, "apple pasteboard cfdatacreate failed\n"); qunlock(&clip.lk); return; } if(PasteboardPutItemFlavor(clip.apple, (PasteboardItemID)1, CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){ fprint(2, "apple pasteboard putitem failed\n"); CFRelease(cfdata); qunlock(&clip.lk); return; } CFRelease(cfdata); qunlock(&clip.lk); } void setlabel(char *label) { CFStringRef cs; cs = CFStringCreateWithBytes(nil, (uchar*)label, strlen(label), kCFStringEncodingUTF8, false); SetWindowTitleWithCFString(osx.window, cs); CFRelease(cs); } void kicklabel(char *label) { char *p; EventRef e; p = strdup(label); if(p == nil) return; qlock(&osx.labellock); free(osx.label); osx.label = p; qunlock(&osx.labellock); CreateEvent(nil, 'P9PE', P9PEventLabelUpdate, 0, kEventAttributeUserEvent, &e); PostEventToQueue(GetMainEventQueue(), e, kEventPriorityStandard); } static void seticon(void) { CGImageRef im; CGDataProviderRef d; d = CGDataProviderCreateWithData(nil, glenda_png, sizeof glenda_png, nil); im = CGImageCreateWithPNGDataProvider(d, nil, true, kCGRenderingIntentDefault); if(im) SetApplicationDockTileImage(im); CGImageRelease(im); CGDataProviderRelease(d); }