aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/devdraw/osx-screen-carbon.m
diff options
context:
space:
mode:
authorRuss Cox <rsc@swtch.com>2010-01-04 10:23:35 -0800
committerRuss Cox <rsc@swtch.com>2010-01-04 10:23:35 -0800
commit100ec44e5170878bbd7fd28f7f68d884d0618173 (patch)
treeaa68d15a8470925872e202a8e1385b9699da8a5e /src/cmd/devdraw/osx-screen-carbon.m
parentb968422f51732c492ff4081786b713ace99835c1 (diff)
downloadplan9port-100ec44e5170878bbd7fd28f7f68d884d0618173.tar.gz
plan9port-100ec44e5170878bbd7fd28f7f68d884d0618173.tar.bz2
plan9port-100ec44e5170878bbd7fd28f7f68d884d0618173.zip
devdraw: add multitouch code from Paul Lalonde
Various tweaks to avoid breaking standard mice, but probably needs tweaks to work with multitouch mice again. Still, it's a start. R=rsc CC=plalonde, r http://codereview.appspot.com/181124
Diffstat (limited to 'src/cmd/devdraw/osx-screen-carbon.m')
-rw-r--r--src/cmd/devdraw/osx-screen-carbon.m1161
1 files changed, 1161 insertions, 0 deletions
diff --git a/src/cmd/devdraw/osx-screen-carbon.m b/src/cmd/devdraw/osx-screen-carbon.m
new file mode 100644
index 00000000..20bdb4ca
--- /dev/null
+++ b/src/cmd/devdraw/osx-screen-carbon.m
@@ -0,0 +1,1161 @@
+#define Point OSXPoint
+#define Rect OSXRect
+#define Cursor OSXCursor
+#include <Carbon/Carbon.h>
+#undef Rect
+#undef Point
+#undef Cursor
+#undef offsetof
+#undef nil
+
+#include "u.h"
+#include "libc.h"
+#include <thread.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(Carbon)
+AUTOFRAMEWORK(Cocoa)
+
+#ifdef MULTITOUCH
+AUTOFRAMEWORK(MultiTouchSupport)
+#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
+ 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);
+
+//start sending events
+void MTDeviceStart(MTDeviceRef, int);
+void MTDeviceStop(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;
+
+ osx.touched = 1;
+ buttons = 0;
+ for(i = 0; i < nFingers; ++i)
+ buttons |= classifyTouch(data+i);
+ delta = buttons ^ obuttons;
+ obuttons = buttons;
+ p.x = osx.xy.x+osx.screenr.min.x;
+ p.y = osx.xy.y+osx.screenr.min.y;
+ 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);
+
+
+static void
+InitMultiTouch(void)
+{
+#ifdef MULTITOUCH
+ int i;
+
+ /*
+ * Setup multitouch queues
+ */
+ if(!multitouch)
+ return;
+
+ for(i = 0; i<kNTracks; ++i)
+ tracks[i].id = -1;
+
+ osx.devicelist = (NSMutableArray*)MTDeviceCreateList(); //grab our device list
+ for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices
+ MTRegisterContactFrameCallback([osx.devicelist objectAtIndex:i], touchCallback); //assign callback for device
+ }
+#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 },
+ { kEventClassCommand, kEventCommandProcess },
+ { kEventClassWindow, kEventWindowActivated },
+ { kEventClassWindow, kEventWindowDeactivated },
+ };
+ 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();
+
+ 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:
+ eresized(1);
+ break;
+
+ case kEventWindowActivated:
+ activated(1);
+ return eventNotHandledErr;
+
+ case kEventWindowDeactivated:
+ activated(0);
+ 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);
+
+ if(osx.touched) {
+ // in multitouch we use the clicks down to enable our
+ // virtual buttons.
+ if(but & 0x3)
+ but = but >> 29;
+ else
+ but = 0;
+ osx.touched = 0;
+ } else {
+ // 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 & 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)){
+ // 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;
+
+ // I'm not 100% sure why this is necessary
+ // but otherwise some resizes (esp. vertical ones)
+ // stop updating the screen.
+ 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);
+ 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)
+{
+#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 {
+ for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices
+ MTDeviceStop([osx.devicelist objectAtIndex:i]); //stop sending events
+ }
+ for(i = 0; i<kNTracks; ++i) {
+ tracks[i].id = -1;
+ }
+ }
+#endif
+ osx.active = active;
+}
+
+void
+fullscreen(int wascmd)
+{
+ static OSXRect oldrect;
+ GDHandle device;
+ OSXRect dr;
+
+ if(!wascmd)
+ return;
+
+ if(!osx.isfullscreen){
+ GetWindowGreatestAreaDevice(osx.window,
+ kWindowTitleBarRgn, &device, nil);
+ dr = (*device)->gdRect;
+ 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<nflavor; j++){
+ type = (CFStringRef)CFArrayGetValueAtIndex(flavors, j);
+ if(!UTTypeConformsTo(type, CFSTR("public.utf16-plain-text")))
+ continue;
+ if(PasteboardCopyItemFlavorData(clip.apple, id, type, &data) != noErr)
+ continue;
+ qunlock(&clip.lk);
+ ndata = CFDataGetLength(data)/2;
+ u = (u16int*)CFDataGetBytePtr(data);
+ fmtstrinit(&fmt);
+ // decode utf-16. what was apple thinking?
+ for(i=0; i<ndata; i++) {
+ r = u[i];
+ if(0xd800 <= r && r < 0xdc00 && i+1 < ndata && 0xdc00 <= u[i+1] && u[i+1] < 0xe000) {
+ r = (((r - 0xd800)<<10) | (u[i+1] - 0xdc00)) + 0x10000;
+ i++;
+ }
+ else if(0xd800 <= r && r < 0xe000)
+ r = Runeerror;
+ if(r == '\r')
+ r = '\n';
+ fmtrune(&fmt, r);
+ }
+ CFRelease(flavors);
+ CFRelease(data);
+ return fmtstrflush(&fmt);
+ }
+ CFRelease(flavors);
+ }
+ qunlock(&clip.lk);
+ return nil;
+}
+
+void
+putsnarf(char *s)
+{
+ CFDataRef cfdata;
+ PasteboardSyncFlags flags;
+ u16int *u, *p;
+ Rune r;
+ int i;
+
+/* fprint(2, "appleputsnarf\n"); */
+
+ if(strlen(s) >= 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);
+}
+