Blob


1 #define Point OSXPoint
2 #define Rect OSXRect
3 #define Cursor OSXCursor
4 #include <Carbon/Carbon.h>
5 #import <Foundation/Foundation.h>
6 #ifdef MULTITOUCH
7 #include <IOKit/IOKitLib.h>
8 #include <IOKit/hidsystem/IOHIDShared.h>
9 #endif
10 #undef Rect
11 #undef Point
12 #undef Cursor
13 #undef offsetof
14 #undef nil
16 #include "u.h"
17 #include "libc.h"
18 #include <thread.h>
19 #include <draw.h>
20 #include <memdraw.h>
21 #include <keyboard.h>
22 #include "mouse.h"
23 #include <cursor.h>
24 #include "osx-screen.h"
25 #include "osx-keycodes.h"
26 #include "devdraw.h"
27 #include "glendapng.h"
29 AUTOFRAMEWORK(Carbon)
30 AUTOFRAMEWORK(Cocoa)
32 #ifdef MULTITOUCH
33 AUTOFRAMEWORK(MultitouchSupport)
34 AUTOFRAMEWORK(IOKit)
35 #endif
37 #define panic sysfatal
39 extern Rectangle mouserect;
41 struct {
42 char *label;
43 char *winsize;
44 QLock labellock;
46 Rectangle fullscreenr;
47 Rectangle screenr;
48 Memimage *screenimage;
49 int isfullscreen;
50 ulong fullscreentime;
52 Point xy;
53 int buttons;
54 int kbuttons;
56 CGDataProviderRef provider;
57 MenuRef wmenu;
58 MenuRef vmenu;
59 WindowRef window;
60 CGImageRef image;
61 CGContextRef windowctx;
62 PasteboardRef snarf;
63 int needflush;
64 QLock flushlock;
65 int active;
66 int infullscreen;
67 int kalting; // last keystroke was Kalt
68 int touched; // last mouse event was touchCallback
69 int collapsed; // parked in dock
70 int flushing; // flushproc has started
71 NSMutableArray* devicelist;
72 } osx;
74 /*
75 These structs are required, in order to handle some parameters returned from the
76 Support.framework
77 */
78 typedef struct {
79 float x;
80 float y;
81 }mtPoint;
83 typedef struct {
84 mtPoint position;
85 mtPoint velocity;
86 }mtReadout;
88 /*
89 Some reversed engineered informations from MultiTouchSupport.framework
90 */
91 typedef struct
92 {
93 int frame; //the current frame
94 double timestamp; //event timestamp
95 int identifier; //identifier guaranteed unique for life of touch per device
96 int state; //the current state (not sure what the values mean)
97 int unknown1; //no idea what this does
98 int unknown2; //no idea what this does either
99 mtReadout normalized; //the normalized position and vector of the touch (0,0 to 1,1)
100 float size; //the size of the touch (the area of your finger being tracked)
101 int unknown3; //no idea what this does
102 float angle; //the angle of the touch -|
103 float majorAxis; //the major axis of the touch -|-- an ellipsoid. you can track the angle of each finger!
104 float minorAxis; //the minor axis of the touch -|
105 mtReadout unknown4; //not sure what this is for
106 int unknown5[2]; //no clue
107 float unknown6; //no clue
108 }Touch;
110 //a reference pointer for the multitouch device
111 typedef void *MTDeviceRef;
113 //the prototype for the callback function
114 typedef int (*MTContactCallbackFunction)(int,Touch*,int,double,int);
116 //returns a pointer to the default device (the trackpad?)
117 MTDeviceRef MTDeviceCreateDefault(void);
119 //returns a CFMutableArrayRef array of all multitouch devices
120 CFMutableArrayRef MTDeviceCreateList(void);
122 //registers a device's frame callback to your callback function
123 void MTRegisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction);
124 void MTUnregisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction);
126 //start sending events
127 void MTDeviceStart(MTDeviceRef, int);
128 void MTDeviceStop(MTDeviceRef);
130 MTDeviceRef MTDeviceCreateFromService(io_service_t);
131 io_service_t MTDeviceGetService(MTDeviceRef);
133 #define kNTracks 10
134 struct TouchTrack {
135 int id;
136 float firstThreshTime;
137 mtPoint pos;
138 } tracks[kNTracks];
140 #define kSizeSensitivity 1.25f
141 #define kTimeSensitivity 0.03f /* seconds */
142 #define kButtonLimit 0.6f /* percentage from base of pad */
144 int
145 findTrack(int id)
147 int i;
148 for(i = 0; i < kNTracks; ++i)
149 if(tracks[i].id == id)
150 return i;
151 return -1;
154 #define kMoveSensitivity 0.05f
156 int
157 moved(mtPoint a, mtPoint b)
159 if(fabs(a.x - b.x) > kMoveSensitivity)
160 return 1;
161 if(fabs(a.y - b.y) > kMoveSensitivity)
162 return 1;
163 return 0;
166 int
167 classifyTouch(Touch *t)
169 mtPoint p;
170 int i;
172 p = t->normalized.position;
174 i = findTrack(t->identifier);
175 if(i == -1) {
176 i = findTrack(-1);
177 if(i == -1)
178 return 0; // No empty tracks.
179 tracks[i].id = t->identifier;
180 tracks[i].firstThreshTime = t->timestamp;
181 tracks[i].pos = p;
182 // we don't have a touch yet - we wait kTimeSensitivity before reporting it.
183 return 0;
186 if(t->size == 0) { // lost touch
187 tracks[i].id = -1;
188 return 0;
190 if(t->size < kSizeSensitivity) {
191 tracks[i].firstThreshTime = t->timestamp;
193 if((t->timestamp - tracks[i].firstThreshTime) < kTimeSensitivity) {
194 return 0;
196 if(p.y > kButtonLimit && t->size > kSizeSensitivity) {
197 if(p.x < 0.35)
198 return 1;
199 if(p.x > 0.65)
200 return 4;
201 if(p.x > 0.35 && p.x < 0.65)
202 return 2;
204 return 0;
207 static ulong msec(void);
209 int
210 touchCallback(int device, Touch *data, int nFingers, double timestamp, int frame)
212 #ifdef MULTITOUCH
213 int buttons, delta, i;
214 static int obuttons;
215 CGPoint p;
216 CGEventRef e;
218 p.x = osx.xy.x+osx.screenr.min.x;
219 p.y = osx.xy.y+osx.screenr.min.y;
220 if(!ptinrect(Pt(p.x, p.y), osx.screenr))
221 return 0;
222 osx.touched = 1;
223 buttons = 0;
224 for(i = 0; i < nFingers; ++i)
225 buttons |= classifyTouch(data+i);
226 delta = buttons ^ obuttons;
227 obuttons = buttons;
228 if(delta & 1) {
229 e = CGEventCreateMouseEvent(NULL,
230 (buttons & 1) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp,
231 p,
232 29);
233 CGEventPost(kCGSessionEventTap, e);
234 CFRelease(e);
236 if(delta & 2) {
237 e = CGEventCreateMouseEvent(NULL,
238 (buttons & 2) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp,
239 p,
240 30);
241 CGEventPost(kCGSessionEventTap, e);
242 CFRelease(e);
244 if(delta & 4){
245 e = CGEventCreateMouseEvent(NULL,
246 (buttons & 4) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp,
247 p,
248 31);
249 CGEventPost(kCGSessionEventTap, e);
250 CFRelease(e);
252 return delta != 0;
253 #else
254 return 0;
255 #endif
258 extern int multitouch;
260 enum
262 WindowAttrs =
263 kWindowCloseBoxAttribute |
264 kWindowCollapseBoxAttribute |
265 kWindowResizableAttribute |
266 kWindowStandardHandlerAttribute |
267 kWindowFullZoomAttribute
268 };
270 enum
272 P9PEventLabelUpdate = 1
273 };
275 static void screenproc(void*);
276 static void eresized(int);
277 static void fullscreen(int);
278 static void seticon(void);
279 static void activated(int);
281 static OSStatus quithandler(EventHandlerCallRef, EventRef, void*);
282 static OSStatus eventhandler(EventHandlerCallRef, EventRef, void*);
283 static OSStatus cmdhandler(EventHandlerCallRef, EventRef, void*);
285 enum
287 CmdFullScreen = 1,
288 };
290 void screeninit(void);
291 void _flushmemscreen(Rectangle r);
293 #ifdef MULTITOUCH
294 static void
295 RegisterMultitouch(void *ctx, io_iterator_t iter)
297 io_object_t io;
298 MTDeviceRef dev;
300 while((io = IOIteratorNext(iter)) != 0){
301 dev = MTDeviceCreateFromService(io);
302 if (dev != nil){
303 MTRegisterContactFrameCallback(dev, touchCallback);
304 [osx.devicelist addObject:dev];
305 if(osx.active)
306 MTDeviceStart(dev, 0);
309 IOObjectRelease(io);
313 static void
314 UnregisterMultitouch(void *ctx, io_iterator_t iter)
316 io_object_t io;
317 MTDeviceRef dev;
318 int i;
320 while((io = IOIteratorNext(iter)) != 0){
321 for(i = 0; i < [osx.devicelist count]; i++){
322 dev = [osx.devicelist objectAtIndex:i];
323 if(IOObjectIsEqualTo(MTDeviceGetService(dev), io)){
324 if(osx.active)
325 MTDeviceStop(dev);
326 MTUnregisterContactFrameCallback(dev, touchCallback);
327 [osx.devicelist removeObjectAtIndex:i];
328 break;
332 IOObjectRelease(io);
336 #endif /*MULTITOUCH*/
338 static void
339 InitMultiTouch()
341 #ifdef MULTITOUCH
342 IONotificationPortRef port;
343 CFRunLoopSourceRef source;
344 io_iterator_t iter;
345 kern_return_t kr;
346 io_object_t obj;
347 int i;
349 if(!multitouch)
350 return;
352 osx.devicelist = [[NSMutableArray alloc] init];
354 for(i = 0; i < kNTracks; ++i)
355 tracks[i].id = -1;
357 port = IONotificationPortCreate(kIOMasterPortDefault);
358 if(port == nil){
359 fprint(2, "failed to get an IO notification port\n");
360 return;
363 source = IONotificationPortGetRunLoopSource(port);
364 if(source == nil){
365 fprint(2, "failed to get loop source for port");
366 return;
369 CFRunLoopAddSource(
370 (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop()),
371 source,
372 kCFRunLoopDefaultMode);
374 kr = IOServiceAddMatchingNotification(
375 port, kIOTerminatedNotification,
376 IOServiceMatching("AppleMultitouchDevice"),
377 &UnregisterMultitouch,
378 nil, &iter);
380 if(kr != KERN_SUCCESS){
381 fprint(2, "failed to add termination notification\n");
382 return;
385 /* Arm the notification */
386 while((obj = IOIteratorNext(iter)) != 0)
387 IOObjectRelease(obj);
389 kr = IOServiceAddMatchingNotification(
390 port, kIOMatchedNotification,
391 IOServiceMatching("AppleMultitouchDevice"),
392 &RegisterMultitouch,
393 nil, &iter);
395 if(kr != KERN_SUCCESS){
396 fprint(2, "failed to add matching notification\n");
397 return;
400 RegisterMultitouch(nil, iter);
401 #endif
404 Memimage*
405 attachscreen(char *label, char *winsize)
407 if(label == nil)
408 label = "gnot a label";
409 osx.label = strdup(label);
410 osx.winsize = winsize;
411 if(osx.screenimage == nil){
412 screeninit();
413 if(osx.screenimage == nil)
414 panic("cannot create OS X screen");
416 return osx.screenimage;
419 extern int multitouch;
421 void
422 _screeninit(void)
424 CGRect cgr;
425 OSXRect or;
426 Rectangle r;
427 int havemin;
429 memimageinit();
431 ProcessSerialNumber psn = { 0, kCurrentProcess };
432 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
433 SetFrontProcess(&psn);
435 cgr = CGDisplayBounds(CGMainDisplayID());
436 osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height);
438 InitCursor();
440 // Create minimal menu with full-screen option.
441 ClearMenuBar();
442 CreateStandardWindowMenu(0, &osx.wmenu);
443 InsertMenu(osx.wmenu, 0);
444 MenuItemIndex ix;
445 CreateNewMenu(1004, 0, &osx.vmenu); // XXX 1004?
446 SetMenuTitleWithCFString(osx.vmenu, CFSTR("View"));
447 AppendMenuItemTextWithCFString(osx.vmenu,
448 CFSTR("Full Screen"), 0, CmdFullScreen, &ix);
449 SetMenuItemCommandKey(osx.vmenu, ix, 0, 'F');
450 AppendMenuItemTextWithCFString(osx.vmenu,
451 CFSTR("Cmd-F exits full screen"),
452 kMenuItemAttrDisabled, CmdFullScreen, &ix);
453 InsertMenu(osx.vmenu, GetMenuID(osx.wmenu));
454 DrawMenuBar();
456 // Create the window.
457 r = Rect(0, 0, Dx(osx.fullscreenr)*2/3, Dy(osx.fullscreenr)*2/3);
458 havemin = 0;
459 if(osx.winsize && osx.winsize[0]){
460 if(parsewinsize(osx.winsize, &r, &havemin) < 0)
461 sysfatal("%r");
463 if(!havemin)
464 r = rectaddpt(r, Pt((Dx(osx.fullscreenr)-Dx(r))/2, (Dy(osx.fullscreenr)-Dy(r))/2));
465 or.left = r.min.x;
466 or.top = r.min.y;
467 or.right = r.max.x;
468 or.bottom = r.max.y;
469 CreateNewWindow(kDocumentWindowClass, WindowAttrs, &or, &osx.window);
470 setlabel(osx.label);
471 seticon();
473 // Set up the clip board.
474 if(PasteboardCreate(kPasteboardClipboard, &osx.snarf) != noErr)
475 panic("pasteboard create");
477 // Explain in great detail which events we want to handle.
478 // Why can't we just have one handler?
479 const EventTypeSpec quits[] = {
480 { kEventClassApplication, kEventAppQuit }
481 };
482 const EventTypeSpec cmds[] = {
483 { kEventClassWindow, kEventWindowClosed },
484 { kEventClassWindow, kEventWindowBoundsChanged },
485 { kEventClassWindow, kEventWindowDrawContent },
486 { kEventClassCommand, kEventCommandProcess },
487 { kEventClassWindow, kEventWindowActivated },
488 { kEventClassWindow, kEventWindowDeactivated },
489 { kEventClassWindow, kEventWindowCollapsed },
490 { kEventClassWindow, kEventWindowExpanded },
491 };
492 const EventTypeSpec events[] = {
493 { kEventClassApplication, kEventAppShown },
494 { kEventClassKeyboard, kEventRawKeyDown },
495 { kEventClassKeyboard, kEventRawKeyModifiersChanged },
496 { kEventClassKeyboard, kEventRawKeyRepeat },
497 { kEventClassMouse, kEventMouseDown },
498 { kEventClassMouse, kEventMouseUp },
499 { kEventClassMouse, kEventMouseMoved },
500 { kEventClassMouse, kEventMouseDragged },
501 { kEventClassMouse, kEventMouseWheelMoved },
502 { 'P9PE', P9PEventLabelUpdate}
503 };
505 InstallApplicationEventHandler(
506 NewEventHandlerUPP(quithandler),
507 nelem(quits), quits, nil, nil);
509 InstallApplicationEventHandler(
510 NewEventHandlerUPP(eventhandler),
511 nelem(events), events, nil, nil);
513 InstallWindowEventHandler(osx.window,
514 NewEventHandlerUPP(cmdhandler),
515 nelem(cmds), cmds, osx.window, nil);
517 // Finally, put the window on the screen.
518 ShowWindow(osx.window);
519 ShowMenuBar();
520 eresized(0);
521 SelectWindow(osx.window);
523 if(multitouch)
524 InitMultiTouch();
526 // CoreGraphics pins mouse events to the destination point of a
527 // CGWarpMouseCursorPosition (see setmouse) for an interval of time
528 // following the move. Disable this by setting the interval to zero
529 // seconds.
530 CGSetLocalEventsSuppressionInterval(0.0);
532 InitCursor();
535 static Rendez scr;
536 static QLock slock;
538 void
539 screeninit(void)
541 scr.l = &slock;
542 qlock(scr.l);
543 proccreate(screenproc, nil, 256*1024);
544 while(osx.window == nil)
545 rsleep(&scr);
546 qunlock(scr.l);
549 static void
550 screenproc(void *v)
552 qlock(scr.l);
553 _screeninit();
554 rwakeup(&scr);
555 qunlock(scr.l);
556 RunApplicationEventLoop();
559 static OSStatus kbdevent(EventRef);
560 static OSStatus mouseevent(EventRef);
562 static OSStatus
563 cmdhandler(EventHandlerCallRef next, EventRef event, void *arg)
565 return eventhandler(next, event, arg);
568 static OSStatus
569 quithandler(EventHandlerCallRef next, EventRef event, void *arg)
571 exit(0);
572 return 0;
575 static OSStatus
576 eventhandler(EventHandlerCallRef next, EventRef event, void *arg)
578 OSStatus result;
580 result = CallNextEventHandler(next, event);
582 switch(GetEventClass(event)){
584 case 'P9PE':
585 if(GetEventKind(event) == P9PEventLabelUpdate) {
586 qlock(&osx.labellock);
587 setlabel(osx.label);
588 qunlock(&osx.labellock);
589 return noErr;
590 } else
591 return eventNotHandledErr;
593 case kEventClassApplication:;
594 Rectangle r = Rect(0, 0, Dx(osx.screenr), Dy(osx.screenr));
595 _flushmemscreen(r);
596 return eventNotHandledErr;
598 case kEventClassKeyboard:
599 return kbdevent(event);
601 case kEventClassMouse:
602 return mouseevent(event);
604 case kEventClassCommand:;
605 HICommand cmd;
606 GetEventParameter(event, kEventParamDirectObject,
607 typeHICommand, nil, sizeof cmd, nil, &cmd);
608 switch(cmd.commandID){
609 case kHICommandQuit:
610 exit(0);
612 case CmdFullScreen:
613 fullscreen(1);
614 break;
616 default:
617 return eventNotHandledErr;
619 break;
621 case kEventClassWindow:
622 switch(GetEventKind(event)){
623 case kEventWindowClosed:
624 exit(0);
626 case kEventWindowBoundsChanged:;
627 // We see kEventWindowDrawContent
628 // if we grow a window but not if we shrink it.
629 UInt32 flags;
630 GetEventParameter(event, kEventParamAttributes,
631 typeUInt32, 0, sizeof flags, 0, &flags);
632 int new = (flags & kWindowBoundsChangeSizeChanged) != 0;
633 eresized(new);
634 break;
636 case kEventWindowDrawContent:
637 // Tried using just flushmemimage here, but
638 // it causes an odd artifact in which making a window
639 // bigger in both width and height can then only draw
640 // on the new border: it's like the old window is stuck
641 // floating on top. Doing a full "get a new window"
642 // seems to solve the problem.
643 eresized(1);
644 break;
646 case kEventWindowActivated:
647 if(!osx.collapsed)
648 activated(1);
649 return eventNotHandledErr;
651 case kEventWindowDeactivated:
652 activated(0);
653 return eventNotHandledErr;
655 case kEventWindowCollapsed:
656 osx.collapsed = 1;
657 activated(0);
658 return eventNotHandledErr;
660 case kEventWindowExpanded:
661 osx.collapsed = 0;
662 activated(1);
663 return eventNotHandledErr;
665 default:
666 return eventNotHandledErr;
668 break;
671 return result;
674 static ulong
675 msec(void)
677 return nsec()/1000000;
680 static OSStatus
681 mouseevent(EventRef event)
683 int wheel;
684 OSXPoint op;
686 GetEventParameter(event, kEventParamMouseLocation,
687 typeQDPoint, 0, sizeof op, 0, &op);
689 osx.xy = subpt(Pt(op.h, op.v), osx.screenr.min);
690 wheel = 0;
692 switch(GetEventKind(event)){
693 case kEventMouseWheelMoved:;
694 SInt32 delta;
695 GetEventParameter(event, kEventParamMouseWheelDelta,
696 typeSInt32, 0, sizeof delta, 0, &delta);
698 // if I have any active touches in my region, I need to ignore the wheel motion.
699 //int i;
700 //for(i = 0; i < kNTracks; ++i) {
701 // if(tracks[i].id != -1 && tracks[i].pos.y > kButtonLimit) break;
702 //}
703 //if(i == kNTracks) { // No active touches, go ahead and scroll.
704 if(delta > 0)
705 wheel = 8;
706 else
707 wheel = 16;
708 //}
709 break;
711 case kEventMouseDown:
712 case kEventMouseUp:;
713 UInt32 but, mod;
714 GetEventParameter(event, kEventParamMouseChord,
715 typeUInt32, 0, sizeof but, 0, &but);
716 GetEventParameter(event, kEventParamKeyModifiers,
717 typeUInt32, 0, sizeof mod, 0, &mod);
719 // OS X swaps button 2 and 3
720 but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1);
721 but = (but & ~((1<<10)-1)) | mouseswap(but & ((1<<10)-1));
722 if(osx.touched) {
723 // in multitouch we use the clicks down to enable our
724 // virtual buttons.
725 if(but & 0x7) {
726 if(but>>29)
727 but = but >> 29;
728 } else
729 but = 0;
730 osx.touched = 0;
733 // Apply keyboard modifiers and pretend it was a real mouse button.
734 // (Modifiers typed while holding the button go into kbuttons,
735 // but this one does not.)
736 if(but == 1){
737 if(mod & optionKey) {
738 // Take the ALT away from the keyboard handler.
739 if(osx.kalting) {
740 osx.kalting = 0;
741 keystroke(Kalt);
743 but = 2;
745 else if(mod & cmdKey)
746 but = 4;
748 osx.buttons = but;
749 break;
751 case kEventMouseMoved:
752 case kEventMouseDragged:
753 break;
755 default:
756 return eventNotHandledErr;
759 mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec());
760 return noErr;
763 static int keycvt[] =
765 [QZ_IBOOK_ENTER] '\n',
766 [QZ_RETURN] '\n',
767 [QZ_ESCAPE] 27,
768 [QZ_BACKSPACE] '\b',
769 [QZ_LALT] Kalt,
770 [QZ_LCTRL] Kctl,
771 [QZ_LSHIFT] Kshift,
772 [QZ_F1] KF+1,
773 [QZ_F2] KF+2,
774 [QZ_F3] KF+3,
775 [QZ_F4] KF+4,
776 [QZ_F5] KF+5,
777 [QZ_F6] KF+6,
778 [QZ_F7] KF+7,
779 [QZ_F8] KF+8,
780 [QZ_F9] KF+9,
781 [QZ_F10] KF+10,
782 [QZ_F11] KF+11,
783 [QZ_F12] KF+12,
784 [QZ_INSERT] Kins,
785 [QZ_DELETE] 0x7F,
786 [QZ_HOME] Khome,
787 [QZ_END] Kend,
788 [QZ_KP_PLUS] '+',
789 [QZ_KP_MINUS] '-',
790 [QZ_TAB] '\t',
791 [QZ_PAGEUP] Kpgup,
792 [QZ_PAGEDOWN] Kpgdown,
793 [QZ_UP] Kup,
794 [QZ_DOWN] Kdown,
795 [QZ_LEFT] Kleft,
796 [QZ_RIGHT] Kright,
797 [QZ_KP_MULTIPLY] '*',
798 [QZ_KP_DIVIDE] '/',
799 [QZ_KP_ENTER] '\n',
800 [QZ_KP_PERIOD] '.',
801 [QZ_KP0] '0',
802 [QZ_KP1] '1',
803 [QZ_KP2] '2',
804 [QZ_KP3] '3',
805 [QZ_KP4] '4',
806 [QZ_KP5] '5',
807 [QZ_KP6] '6',
808 [QZ_KP7] '7',
809 [QZ_KP8] '8',
810 [QZ_KP9] '9',
811 };
813 static OSStatus
814 kbdevent(EventRef event)
816 char ch;
817 UInt32 code;
818 UInt32 mod;
819 int k;
821 GetEventParameter(event, kEventParamKeyMacCharCodes,
822 typeChar, nil, sizeof ch, nil, &ch);
823 GetEventParameter(event, kEventParamKeyCode,
824 typeUInt32, nil, sizeof code, nil, &code);
825 GetEventParameter(event, kEventParamKeyModifiers,
826 typeUInt32, nil, sizeof mod, nil, &mod);
828 switch(GetEventKind(event)){
829 case kEventRawKeyDown:
830 case kEventRawKeyRepeat:
831 osx.kalting = 0;
832 if(mod == cmdKey){
833 if(ch == 'F' || ch == 'f'){
834 if(osx.isfullscreen && msec() - osx.fullscreentime > 500)
835 fullscreen(0);
836 return noErr;
839 // Pass most Cmd keys through as Kcmd + ch.
840 // OS X interprets a few no matter what we do,
841 // so it is useless to pass them through as keystrokes too.
842 switch(ch) {
843 case 'm': // minimize window
844 case 'h': // hide window
845 case 'H': // hide others
846 case 'q': // quit
847 return eventNotHandledErr;
849 if(' ' <= ch && ch <= '~') {
850 keystroke(Kcmd + ch);
851 return noErr;
853 return eventNotHandledErr;
855 k = ch;
856 if(code < nelem(keycvt) && keycvt[code])
857 k = keycvt[code];
858 if(k == 0)
859 return noErr;
860 if(k > 0)
861 keystroke(k);
862 else{
863 UniChar uc;
864 OSStatus s;
866 s = GetEventParameter(event, kEventParamKeyUnicodes,
867 typeUnicodeText, nil, sizeof uc, nil, &uc);
868 if(s == noErr)
869 keystroke(uc);
871 break;
873 case kEventRawKeyModifiersChanged:
874 if(!osx.buttons && !osx.kbuttons){
875 if(mod == optionKey) {
876 osx.kalting = 1;
877 keystroke(Kalt);
879 break;
882 // If the mouse button is being held down, treat
883 // changes in the keyboard modifiers as changes
884 // in the mouse buttons.
885 osx.kbuttons = 0;
886 if(mod & optionKey)
887 osx.kbuttons |= 2;
888 if(mod & cmdKey)
889 osx.kbuttons |= 4;
890 mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
891 break;
893 return noErr;
896 static void
897 eresized(int new)
899 Memimage *m;
900 OSXRect or;
901 ulong chan;
902 Rectangle r;
903 int bpl;
904 CGDataProviderRef provider;
905 CGImageRef image;
906 CGColorSpaceRef cspace;
908 GetWindowBounds(osx.window, kWindowContentRgn, &or);
909 r = Rect(or.left, or.top, or.right, or.bottom);
910 if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr) && !new){
911 // No need to make new image.
912 osx.screenr = r;
913 return;
916 chan = XBGR32;
917 m = allocmemimage(Rect(0, 0, Dx(r), Dy(r)), chan);
918 if(m == nil)
919 panic("allocmemimage: %r");
920 if(m->data == nil)
921 panic("m->data == nil");
922 bpl = bytesperline(r, 32);
923 provider = CGDataProviderCreateWithData(0,
924 m->data->bdata, Dy(r)*bpl, 0);
925 //cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
926 cspace = CGColorSpaceCreateDeviceRGB();
927 image = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
928 cspace,
929 kCGImageAlphaNoneSkipLast,
930 provider, 0, 0, kCGRenderingIntentDefault);
931 CGColorSpaceRelease(cspace);
932 CGDataProviderRelease(provider); // CGImageCreate did incref
934 mouserect = m->r;
935 if(new){
936 mouseresized = 1;
937 mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
939 // termreplacescreenimage(m);
940 _drawreplacescreenimage(m); // frees old osx.screenimage if any
941 if(osx.image)
942 CGImageRelease(osx.image);
943 osx.image = image;
944 osx.screenimage = m;
945 osx.screenr = r;
947 if(new){
948 qlock(&osx.flushlock);
949 QDEndCGContext(GetWindowPort(osx.window), &osx.windowctx);
950 osx.windowctx = nil;
951 qunlock(&osx.flushlock);
955 void
956 flushproc(void *v)
958 for(;;){
959 if(osx.needflush && osx.windowctx && canqlock(&osx.flushlock)){
960 if(osx.windowctx){
961 CGContextFlush(osx.windowctx);
962 osx.needflush = 0;
964 qunlock(&osx.flushlock);
966 usleep(33333);
970 void
971 _flushmemscreen(Rectangle r)
973 CGRect cgr;
974 CGImageRef subimg;
976 qlock(&osx.flushlock);
977 if(osx.windowctx == nil){
978 QDBeginCGContext(GetWindowPort(osx.window), &osx.windowctx);
979 if(!osx.flushing) {
980 proccreate(flushproc, nil, 256*1024);
981 osx.flushing = 1;
985 cgr.origin.x = r.min.x;
986 cgr.origin.y = r.min.y;
987 cgr.size.width = Dx(r);
988 cgr.size.height = Dy(r);
989 subimg = CGImageCreateWithImageInRect(osx.image, cgr);
990 cgr.origin.y = Dy(osx.screenr) - r.max.y; // XXX how does this make any sense?
991 CGContextDrawImage(osx.windowctx, cgr, subimg);
992 osx.needflush = 1;
993 qunlock(&osx.flushlock);
994 CGImageRelease(subimg);
997 void
998 activated(int active)
1000 #ifdef MULTITOUCH
1001 int i;
1002 if(active) {
1003 for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices
1004 MTDeviceStart([osx.devicelist objectAtIndex:i], 0); //start sending events
1006 } else {
1007 osx.xy.x = -10000;
1008 for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices
1009 MTDeviceStop([osx.devicelist objectAtIndex:i]); //stop sending events
1011 for(i = 0; i<kNTracks; ++i) {
1012 tracks[i].id = -1;
1015 #endif
1016 osx.active = active;
1019 void
1020 fullscreen(int wascmd)
1022 static OSXRect oldrect;
1023 GDHandle device;
1024 OSXRect dr;
1026 if(!wascmd)
1027 return;
1029 if(!osx.isfullscreen){
1030 GetWindowGreatestAreaDevice(osx.window,
1031 kWindowTitleBarRgn, &device, nil);
1032 dr = (*device)->gdRect;
1033 if(dr.top == 0 && dr.left == 0)
1034 HideMenuBar();
1035 GetWindowBounds(osx.window, kWindowContentRgn, &oldrect);
1036 ChangeWindowAttributes(osx.window,
1037 kWindowNoTitleBarAttribute,
1038 kWindowResizableAttribute);
1039 MoveWindow(osx.window, 0, 0, 1);
1040 MoveWindow(osx.window, dr.left, dr.top, 0);
1041 SizeWindow(osx.window,
1042 dr.right - dr.left,
1043 dr.bottom - dr.top, 0);
1044 osx.isfullscreen = 1;
1045 }else{
1046 ShowMenuBar();
1047 ChangeWindowAttributes(osx.window,
1048 kWindowResizableAttribute,
1049 kWindowNoTitleBarAttribute);
1050 SizeWindow(osx.window,
1051 oldrect.right - oldrect.left,
1052 oldrect.bottom - oldrect.top, 0);
1053 MoveWindow(osx.window, oldrect.left, oldrect.top, 0);
1054 osx.isfullscreen = 0;
1056 eresized(1);
1059 void
1060 setmouse(Point p)
1062 CGPoint cgp;
1064 cgp.x = p.x + osx.screenr.min.x;
1065 cgp.y = p.y + osx.screenr.min.y;
1066 CGWarpMouseCursorPosition(cgp);
1067 osx.xy = p;
1070 void
1071 setcursor(Cursor *c)
1073 OSXCursor oc;
1074 int i;
1076 if(c == nil){
1077 InitCursor();
1078 return;
1081 // SetCursor is deprecated, but what replaces it?
1082 for(i=0; i<16; i++){
1083 oc.data[i] = ((ushort*)c->set)[i];
1084 oc.mask[i] = oc.data[i] | ((ushort*)c->clr)[i];
1086 oc.hotSpot.h = - c->offset.x;
1087 oc.hotSpot.v = - c->offset.y;
1088 SetCursor(&oc);
1091 void
1092 getcolor(ulong i, ulong *r, ulong *g, ulong *b)
1094 ulong v;
1096 v = 0;
1097 *r = (v>>16)&0xFF;
1098 *g = (v>>8)&0xFF;
1099 *b = v&0xFF;
1102 int
1103 setcolor(ulong i, ulong r, ulong g, ulong b)
1105 /* no-op */
1106 return 0;
1110 int
1111 hwdraw(Memdrawparam *p)
1113 return 0;
1116 struct {
1117 QLock lk;
1118 char buf[SnarfSize];
1119 Rune rbuf[SnarfSize];
1120 PasteboardRef apple;
1121 } clip;
1123 char*
1124 getsnarf(void)
1126 char *s;
1127 CFArrayRef flavors;
1128 CFDataRef data;
1129 CFIndex nflavor, ndata, j;
1130 CFStringRef type;
1131 ItemCount nitem;
1132 PasteboardItemID id;
1133 PasteboardSyncFlags flags;
1134 UInt32 i;
1135 u16int *u;
1136 Fmt fmt;
1137 Rune r;
1139 /* fprint(2, "applegetsnarf\n"); */
1140 qlock(&clip.lk);
1141 clip.apple = osx.snarf;
1142 if(clip.apple == nil){
1143 if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){
1144 fprint(2, "apple pasteboard create failed\n");
1145 qunlock(&clip.lk);
1146 return nil;
1149 flags = PasteboardSynchronize(clip.apple);
1150 if(flags&kPasteboardClientIsOwner){
1151 s = strdup(clip.buf);
1152 qunlock(&clip.lk);
1153 return s;
1155 if(PasteboardGetItemCount(clip.apple, &nitem) != noErr){
1156 fprint(2, "apple pasteboard get item count failed\n");
1157 qunlock(&clip.lk);
1158 return nil;
1160 for(i=1; i<=nitem; i++){
1161 if(PasteboardGetItemIdentifier(clip.apple, i, &id) != noErr)
1162 continue;
1163 if(PasteboardCopyItemFlavors(clip.apple, id, &flavors) != noErr)
1164 continue;
1165 nflavor = CFArrayGetCount(flavors);
1166 for(j=0; j<nflavor; j++){
1167 type = (CFStringRef)CFArrayGetValueAtIndex(flavors, j);
1168 if(!UTTypeConformsTo(type, CFSTR("public.utf16-plain-text")))
1169 continue;
1170 if(PasteboardCopyItemFlavorData(clip.apple, id, type, &data) != noErr)
1171 continue;
1172 qunlock(&clip.lk);
1173 ndata = CFDataGetLength(data)/2;
1174 u = (u16int*)CFDataGetBytePtr(data);
1175 fmtstrinit(&fmt);
1176 // decode utf-16. what was apple thinking?
1177 for(i=0; i<ndata; i++) {
1178 r = u[i];
1179 if(0xd800 <= r && r < 0xdc00 && i+1 < ndata && 0xdc00 <= u[i+1] && u[i+1] < 0xe000) {
1180 r = (((r - 0xd800)<<10) | (u[i+1] - 0xdc00)) + 0x10000;
1181 i++;
1183 else if(0xd800 <= r && r < 0xe000)
1184 r = Runeerror;
1185 if(r == '\r')
1186 r = '\n';
1187 fmtrune(&fmt, r);
1189 CFRelease(flavors);
1190 CFRelease(data);
1191 return fmtstrflush(&fmt);
1193 CFRelease(flavors);
1195 qunlock(&clip.lk);
1196 return nil;
1199 void
1200 putsnarf(char *s)
1202 CFDataRef cfdata;
1203 PasteboardSyncFlags flags;
1204 u16int *u, *p;
1205 Rune r;
1206 int i;
1208 /* fprint(2, "appleputsnarf\n"); */
1210 if(strlen(s) >= SnarfSize)
1211 return;
1212 qlock(&clip.lk);
1213 strcpy(clip.buf, s);
1214 runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s);
1215 clip.apple = osx.snarf;
1216 if(PasteboardClear(clip.apple) != noErr){
1217 fprint(2, "apple pasteboard clear failed\n");
1218 qunlock(&clip.lk);
1219 return;
1221 flags = PasteboardSynchronize(clip.apple);
1222 if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
1223 fprint(2, "apple pasteboard cannot assert ownership\n");
1224 qunlock(&clip.lk);
1225 return;
1227 u = malloc(runestrlen(clip.rbuf)*4);
1228 p = u;
1229 for(i=0; clip.rbuf[i]; i++) {
1230 r = clip.rbuf[i];
1231 // convert to utf-16
1232 if(0xd800 <= r && r < 0xe000)
1233 r = Runeerror;
1234 if(r >= 0x10000) {
1235 r -= 0x10000;
1236 *p++ = 0xd800 + (r>>10);
1237 *p++ = 0xdc00 + (r & ((1<<10)-1));
1238 } else
1239 *p++ = r;
1241 cfdata = CFDataCreate(kCFAllocatorDefault,
1242 (uchar*)u, (p-u)*2);
1243 free(u);
1244 if(cfdata == nil){
1245 fprint(2, "apple pasteboard cfdatacreate failed\n");
1246 qunlock(&clip.lk);
1247 return;
1249 if(PasteboardPutItemFlavor(clip.apple, (PasteboardItemID)1,
1250 CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
1251 fprint(2, "apple pasteboard putitem failed\n");
1252 CFRelease(cfdata);
1253 qunlock(&clip.lk);
1254 return;
1256 CFRelease(cfdata);
1257 qunlock(&clip.lk);
1260 void
1261 setlabel(char *label)
1263 CFStringRef cs;
1265 cs = CFStringCreateWithBytes(nil, (uchar*)label, strlen(label), kCFStringEncodingUTF8, false);
1266 SetWindowTitleWithCFString(osx.window, cs);
1267 CFRelease(cs);
1270 void
1271 kicklabel(char *label)
1273 char *p;
1274 EventRef e;
1276 p = strdup(label);
1277 if(p == nil)
1278 return;
1279 qlock(&osx.labellock);
1280 free(osx.label);
1281 osx.label = p;
1282 qunlock(&osx.labellock);
1284 CreateEvent(nil, 'P9PE', P9PEventLabelUpdate, 0, kEventAttributeUserEvent, &e);
1285 PostEventToQueue(GetMainEventQueue(), e, kEventPriorityStandard);
1289 static void
1290 seticon(void)
1292 CGImageRef im;
1293 CGDataProviderRef d;
1295 d = CGDataProviderCreateWithData(nil, glenda_png, sizeof glenda_png, nil);
1296 im = CGImageCreateWithPNGDataProvider(d, nil, true, kCGRenderingIntentDefault);
1297 if(im)
1298 SetApplicationDockTileImage(im);
1299 CGImageRelease(im);
1300 CGDataProviderRelease(d);