blob: d41e9d10deef6b369588393e4e9eda063ee6592a [file] [log] [blame]
#define Point OSXPoint
#define Rect OSXRect
#define Cursor OSXCursor
#include <Carbon/Carbon.h>
#import <Foundation/Foundation.h>
#ifdef MULTITOUCH
#include <IOKit/IOKitLib.h>
#include <IOKit/hidsystem/IOHIDShared.h>
#endif
#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)
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; 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);
}