xref: /inferno-os/emu/MacOSX/win.c (revision 4eb166cf184c1f102fb79e31b1465ea3e2021c39)
1 // in this file, _Rect is os x Rect,
2 // _Point is os x Point
3 #define Point _Point
4 #define Rect _Rect
5 
6 #include <Carbon/Carbon.h>
7 #include <QuickTime/QuickTime.h> // for full screen
8 
9 #undef Rect
10 #undef Point
11 
12 #undef nil
13 
14 #include "dat.h"
15 #include "fns.h"
16 #undef log2
17 #include <draw.h>
18 #include <memdraw.h>
19 #include "cursor.h"
20 #include "keyboard.h"
21 #include "keycodes.h"
22 
23 #define	Kup	Up
24 #define	Kleft	Left
25 #define	Kdown	Down
26 #define	Kright	Right
27 #define	Kalt	LAlt
28 #define	Kctl	LCtrl
29 #define	Kshift	LShift
30 #define	Kpgup	Pgup
31 #define	Kpgdown	Pgdown
32 #define	Khome	Home
33 #define	Kins	Ins
34 #define	Kend	End
35 
36 #define rWindowResource  128
37 
38 extern	void		flushmemscreen(Rectangle);
39 
40 Memimage	*gscreen;
41 
42 static int readybit;
43 static Rendez	rend;
44 static int triedscreen;
45 
46 ///
47 // menu
48 //
49 static MenuRef windMenu;
50 static MenuRef viewMenu;
51 
52 enum {
53 	kQuitCmd = 1,
54 	kFullScreenCmd = 2,
55 };
56 
57 static WindowGroupRef winGroup = NULL;
58 static WindowRef theWindow = NULL;
59 static CGContextRef context;
60 static CGDataProviderRef dataProviderRef;
61 static CGImageRef fullScreenImage;
62 static CGRect devRect;
63 static CGRect bounds;
64 static PasteboardRef appleclip;
65 static _Rect winRect;
66 
67 static Boolean altPressed = false;
68 static Boolean button2 = false;
69 static Boolean button3 = false;
70 
71 static Boolean needflush = false;
72 
73 
74 static int
75 isready(void*a)
76 {
77 	return readybit;
78 }
79 
80 CGContextRef QuartzContext;
81 
82 static OSStatus MainWindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
83 static OSStatus MainWindowCommandHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
84 
85 static void winproc(void *a);
86 static void flushproc(void *a);
87 
88 void
89 screeninit(void)
90 {
91 	int fmt;
92 	int dx, dy;
93 	ProcessSerialNumber psn = { 0, kCurrentProcess };
94 	TransformProcessType(&psn, kProcessTransformToForegroundApplication);
95 	SetFrontProcess(&psn);
96 
97 	fmt = XBGR32; //XRGB32;
98 
99 	devRect = CGDisplayBounds(CGMainDisplayID());
100 //	devRect.origin.x = 0;
101 //	devRect.origin.y = 0;
102 //	devRect.size.width = 1024;
103 //	devRect.size.height = 768;
104 	dx = devRect.size.width;
105 	dy = devRect.size.height;
106 
107 	if(1){	/* TO DO: new dev draw for changing screen size */
108 		dx = Xsize;
109 		dy = Ysize;
110 	}
111 
112 	gscreen = allocmemimage(Rect(0,0,dx,dy), fmt);
113 	dataProviderRef = CGDataProviderCreateWithData(0, gscreen->data->bdata,
114 						dx * dy * 4, 0);
115 	fullScreenImage = CGImageCreate(dx, dy, 8, 32, dx * 4,
116 				CGColorSpaceCreateDeviceRGB(),
117 				kCGImageAlphaNoneSkipLast,
118 				dataProviderRef, 0, 0, kCGRenderingIntentDefault);
119 
120 	kproc("osxscreen", winproc, nil, 0);
121 	kproc("osxflush", flushproc, nil, 0);
122 	Sleep(&rend, isready, nil);
123 }
124 
125 void
126 window_resized(void)
127 {
128 	GetWindowBounds(theWindow, kWindowContentRgn, &winRect);
129 
130 	bounds = CGRectMake(0, 0, winRect.right-winRect.left, winRect.bottom - winRect.top);
131 }
132 
133 static void
134 flushproc(void *a)
135 {
136 	for(;;) {
137 		if(needflush) {
138 			drawqlock();
139 			QDBeginCGContext(GetWindowPort(theWindow), &context);
140 			CGContextFlush(context);
141 			QDEndCGContext(GetWindowPort(theWindow), &context);
142 			needflush = false;
143  			drawqunlock();
144 		}
145 		usleep(33333);
146 	}
147 }
148 
149 static void
150 winproc(void *a)
151 {
152 	MenuItemIndex index;
153 	int dx, dy;
154 
155 	winRect.left = 30;
156 	winRect.top = 60;
157 	dx = devRect.size.width*0.75;	/* devRect is full screen; take only most of it */
158 	dy = devRect.size.height*0.75;
159 	if(1){	/* TO DO */
160 		dx = Xsize;
161 		dy = Ysize;
162 	}
163 	winRect.bottom = winRect.top + dy;
164 	winRect.right = winRect.left + dx;
165 
166 	ClearMenuBar();
167 	InitCursor();
168 
169 	CreateStandardWindowMenu(0, &windMenu);
170 	InsertMenu(windMenu, 0);
171 
172 	CreateNewMenu(1004, 0, &viewMenu);
173 	SetMenuTitleWithCFString(viewMenu, CFSTR("View"));
174 	AppendMenuItemTextWithCFString(viewMenu, CFSTR("Full Screen"), 0,
175 			kFullScreenCmd, &index);
176 	SetMenuItemCommandKey(viewMenu, index, 0, 'F');
177 	AppendMenuItemTextWithCFString(viewMenu, CFSTR("ctrl-opt to return"),
178 			kMenuItemAttrDisabled,
179 			kFullScreenCmd, &index);
180 	InsertMenu(viewMenu, GetMenuID(windMenu));
181 
182 	DrawMenuBar();
183 	uint32_t windowAttrs = 0
184 				| kWindowCloseBoxAttribute
185 				| kWindowCollapseBoxAttribute
186 //				| kWindowResizableAttribute		// TO DO
187 				| kWindowStandardHandlerAttribute
188 //				| kWindowFullZoomAttribute		// TO DO
189 		;
190 
191 	CreateNewWindow(kDocumentWindowClass, windowAttrs, &winRect, &theWindow);
192 	CreateWindowGroup(0, &winGroup);
193 	SetWindowGroup(theWindow, winGroup);
194 
195 	SetWindowTitleWithCFString(theWindow, CFSTR("Inferno"));
196 
197 	if(PasteboardCreate(kPasteboardClipboard, &appleclip) != noErr)
198 		sysfatal("pasteboard create failed");
199 
200 	const EventTypeSpec commands[] = {
201 		{ kEventClassWindow, kEventWindowClosed },
202 		{ kEventClassWindow, kEventWindowBoundsChanged },
203 		{ kEventClassCommand, kEventCommandProcess }
204 	};
205 	const EventTypeSpec events[] = {
206 		{ kEventClassKeyboard, kEventRawKeyDown },
207 		{ kEventClassKeyboard, kEventRawKeyModifiersChanged },
208 		{ kEventClassKeyboard, kEventRawKeyRepeat },
209 		{ kEventClassMouse, kEventMouseDown },
210 		{ kEventClassMouse, kEventMouseUp },
211 		{ kEventClassMouse, kEventMouseMoved },
212 		{ kEventClassMouse, kEventMouseDragged },
213 		{ kEventClassMouse, kEventMouseWheelMoved },
214 	};
215 
216  	InstallApplicationEventHandler (
217  								NewEventHandlerUPP (MainWindowEventHandler),
218 								GetEventTypeCount(events),
219 								events,
220 								NULL,
221 								NULL);
222 	InstallWindowEventHandler (
223 								theWindow,
224 								NewEventHandlerUPP (MainWindowCommandHandler),
225 								GetEventTypeCount(commands),
226 								commands,
227 								theWindow,
228 								NULL);
229 
230 	ShowWindow(theWindow);
231 	ShowMenuBar();
232 	window_resized();
233 	SelectWindow(theWindow);
234 	// Run the event loop
235 	readybit = 1;
236 	Wakeup(&rend);
237 	RunApplicationEventLoop();
238 
239 }
240 
241 static int
242 convert_key(UInt32 key, UInt32 charcode)
243 {
244 	switch(key) {
245 	case QZ_IBOOK_ENTER:
246 	case QZ_RETURN: return '\n';
247 	case QZ_ESCAPE: return 27;
248 	case QZ_BACKSPACE: return '\b';
249 	case QZ_LALT: return Kalt;
250 	case QZ_LCTRL: return Kctl;
251 	case QZ_LSHIFT: return Kshift;
252 	case QZ_F1: return KF+1;
253 	case QZ_F2: return KF+2;
254 	case QZ_F3: return KF+3;
255 	case QZ_F4: return KF+4;
256 	case QZ_F5: return KF+5;
257 	case QZ_F6: return KF+6;
258 	case QZ_F7: return KF+7;
259 	case QZ_F8: return KF+8;
260 	case QZ_F9: return KF+9;
261 	case QZ_F10: return KF+10;
262 	case QZ_F11: return KF+11;
263 	case QZ_F12: return KF+12;
264 	case QZ_INSERT: return Kins;
265 	case QZ_DELETE: return 0x7F;
266 	case QZ_HOME: return Khome;
267 	case QZ_END: return Kend;
268 	case QZ_KP_PLUS: return '+';
269 	case QZ_KP_MINUS: return '-';
270 	case QZ_TAB: return '\t';
271 	case QZ_PAGEUP: return Kpgup;
272 	case QZ_PAGEDOWN: return Kpgdown;
273 	case QZ_UP: return Kup;
274 	case QZ_DOWN: return Kdown;
275 	case QZ_LEFT: return Kleft;
276 	case QZ_RIGHT: return Kright;
277 	case QZ_KP_MULTIPLY: return '*';
278 	case QZ_KP_DIVIDE: return '/';
279 	case QZ_KP_ENTER: return '\n';
280 	case QZ_KP_PERIOD: return '.';
281 	case QZ_KP0: return '0';
282 	case QZ_KP1: return '1';
283 	case QZ_KP2: return '2';
284 	case QZ_KP3: return '3';
285 	case QZ_KP4: return '4';
286 	case QZ_KP5: return '5';
287 	case QZ_KP6: return '6';
288 	case QZ_KP7: return '7';
289 	case QZ_KP8: return '8';
290 	case QZ_KP9: return '9';
291 	default: return charcode;
292 	}
293 }
294 
295 void
296 sendbuttons(int b, int x, int y)
297 {
298 	mousetrack(b, x, y, 0);
299 }
300 
301 static Ptr fullScreenRestore;
302 static int amFullScreen = 0;
303 static WindowRef oldWindow = NULL;
304 
305 static void
306 leave_full_screen(void)
307 {
308 	if (0 && amFullScreen) {
309 		EndFullScreen(fullScreenRestore, 0);
310 		theWindow = oldWindow;
311 		ShowWindow(theWindow);
312 		amFullScreen = 0;
313 		window_resized();
314 		Rectangle rect =  { { 0, 0 }, { bounds.size.width, bounds.size.height} };
315 		drawqlock();
316  		flushmemscreen(rect);
317  		drawqunlock();
318 	}
319 }
320 
321 static void
322 full_screen(void)
323 {
324 	if (0 && !amFullScreen) {
325 		oldWindow = theWindow;
326 		HideWindow(theWindow);
327 		BeginFullScreen(&fullScreenRestore, 0, 0, 0, &theWindow, 0, 0);
328 		amFullScreen = 1;
329 		window_resized();
330 		Rectangle rect =  { { 0, 0 },
331  							{ bounds.size.width,
332  							  bounds.size.height} };
333 		drawqlock();
334  		flushmemscreen(rect);
335  		drawqunlock();
336 	}
337 }
338 
339 static OSStatus
340 MainWindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
341 {
342 	OSStatus result = noErr;
343 	result = CallNextEventHandler(nextHandler, event);
344 	UInt32 class = GetEventClass (event);
345 	UInt32 kind = GetEventKind (event);
346 	static uint32_t mousebuttons = 0; // bitmask of buttons currently down
347 	static uint32_t mouseX = 0;
348 	static uint32_t mouseY = 0;
349 
350 	if(class == kEventClassKeyboard) {
351 		char macCharCodes;
352 		UInt32 macKeyCode;
353 		UInt32 macKeyModifiers;
354 
355 		GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar,
356 							NULL, sizeof(macCharCodes), NULL, &macCharCodes);
357 		GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL,
358 							sizeof(macKeyCode), NULL, &macKeyCode);
359 		GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL,
360 							sizeof(macKeyModifiers), NULL, &macKeyModifiers);
361         switch(kind) {
362 		case kEventRawKeyModifiersChanged:
363 			if (macKeyModifiers == (controlKey | optionKey)) leave_full_screen();
364 
365 			switch(macKeyModifiers & (optionKey | cmdKey)) {
366 			case (optionKey | cmdKey):
367 				/* due to chording we need to handle the case when both
368 				 * modifier keys are pressed at the same time.
369 				 * currently it's only 2-3 snarf and the 3-2 noop
370 				 */
371 				altPressed = true;
372 				if(mousebuttons & 1 || mousebuttons & 2 || mousebuttons & 4) {
373 					mousebuttons |= 2;	/* set button 2 */
374 					mousebuttons |= 4;	/* set button 3 */
375 					button2 = true;
376 					button3 = true;
377 					sendbuttons(mousebuttons, mouseX, mouseY);
378 				}
379 				break;
380 			case optionKey:
381 				altPressed = true;
382 				if(mousebuttons & 1 || mousebuttons & 4) {
383 					mousebuttons |= 2;	/* set button 2 */
384 					button2 = true;
385 					sendbuttons(mousebuttons, mouseX, mouseY);
386 				}
387 				break;
388 			case cmdKey:
389 				if(mousebuttons & 1 || mousebuttons & 2) {
390 					mousebuttons |= 4;	/* set button 3 */
391 					button3 = true;
392 					sendbuttons(mousebuttons, mouseX, mouseY);
393 				}else
394 					gkbdputc(gkbdq, Latin);
395 				break;
396 			case 0:
397 			default:
398 				if(button2 || button3) {
399 					if(button2) {
400 						mousebuttons &= ~2;	/* clear button 2 */
401 						button2 = false;
402 						altPressed = false;
403 					}
404 					if(button3) {
405 						mousebuttons &= ~4;	/* clear button 3 */
406 						button3 = false;
407 					}
408 					sendbuttons(mousebuttons, mouseX, mouseY);
409 				}
410 				if(altPressed) {
411 					gkbdputc(gkbdq, Kalt);
412 					altPressed = false;
413 				}
414 				break;
415 			}
416 			break;
417 		case kEventRawKeyDown:
418 		case kEventRawKeyRepeat:
419 			if(macKeyModifiers != cmdKey) {
420 				int key;
421 				key = convert_key(macKeyCode, macCharCodes);
422 				if(key != -1)
423 					gkbdputc(gkbdq, key);
424 			}else
425 				result = eventNotHandledErr;
426 			break;
427 		default:
428 			break;
429 		}
430 	}
431 	else if(class == kEventClassMouse) {
432 		_Point mousePos;
433 
434 		GetEventParameter(event, kEventParamMouseLocation, typeQDPoint,
435 							0, sizeof mousePos, 0, &mousePos);
436 
437 		switch(kind) {
438 		case kEventMouseWheelMoved:
439 		{
440 		    int32_t wheeldelta;
441 			GetEventParameter(event,kEventParamMouseWheelDelta,typeSInt32,
442 								0,sizeof(wheeldelta), 0, &wheeldelta);
443 			mouseX = mousePos.h - winRect.left;
444 			mouseY = mousePos.v - winRect.top;
445 			sendbuttons(wheeldelta>0 ? 8 : 16, mouseX, mouseY);
446 			break;
447 		}
448 		case kEventMouseUp:
449 		case kEventMouseDown:
450 		{
451 			uint32_t buttons;
452 			uint32_t modifiers;
453 			GetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
454 								0, sizeof(modifiers), 0, &modifiers);
455 			GetEventParameter(event, kEventParamMouseChord, typeUInt32,
456 								0, sizeof buttons, 0, &buttons);
457 			/* simulate other buttons via alt/apple key. like x11 */
458 			if(modifiers & optionKey) {
459 				mousebuttons = ((buttons & 1) ? 2 : 0);
460 				altPressed = false;
461 			} else if(modifiers & cmdKey)
462 				mousebuttons = ((buttons & 1) ? 4 : 0);
463 			else
464 				mousebuttons = (buttons & 1);
465 
466 			mousebuttons |= ((buttons & 2)<<1);
467 			mousebuttons |= ((buttons & 4)>>1);
468 
469 		} /* Fallthrough */
470 		case kEventMouseMoved:
471 		case kEventMouseDragged:
472 			mouseX = mousePos.h - winRect.left;
473 			mouseY = mousePos.v - winRect.top;
474 			sendbuttons(mousebuttons, mouseX, mouseY);
475 			break;
476 		default:
477 			result = eventNotHandledErr;
478 			break;
479 		}
480 	}
481 	return result;
482 }
483 
484 
485 //default window command handler (from menus)
486 static OSStatus
487 MainWindowCommandHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
488 {
489 	OSStatus result = noErr;
490 	UInt32 class = GetEventClass (event);
491 	UInt32 kind = GetEventKind (event);
492 
493 	result = CallNextEventHandler(nextHandler, event);
494 
495 	if(class == kEventClassCommand) {
496 		HICommand theHICommand;
497 		GetEventParameter(event, kEventParamDirectObject, typeHICommand,
498 							NULL, sizeof(HICommand), NULL, &theHICommand);
499 
500 		switch(theHICommand.commandID) {
501 		case kHICommandQuit:
502 			cleanexit(0);
503 			break;
504 
505 		case kFullScreenCmd:
506 			full_screen();
507 			break;
508 
509 		default:
510 			result = eventNotHandledErr;
511 			break;
512 		}
513 	} else if(class == kEventClassWindow) {
514 		WindowRef     window;
515 		_Rect          rectPort = {0,0,0,0};
516 
517 		GetEventParameter(event, kEventParamDirectObject, typeWindowRef,
518 							NULL, sizeof(WindowRef), NULL, &window);
519 
520 		if(window)
521 			GetPortBounds(GetWindowPort(window), &rectPort);
522 
523 		switch(kind) {
524 		case kEventWindowClosed:
525 			theWindow = NULL;
526 			cleanexit(0); // only one window
527 			break;
528 
529 		//resize window
530 		case kEventWindowBoundsChanged:
531 			window_resized();
532 			Rectangle rect =  { { 0, 0 },
533  									{ bounds.size.width,
534  									  bounds.size.height} };
535 			drawqlock();
536  			flushmemscreen(rect);
537  			drawqunlock();
538 			break;
539 
540 		default:
541 			result = eventNotHandledErr;
542 			break;
543 		}
544 	}
545 
546 	return result;
547 }
548 
549 void
550 flushmemscreen(Rectangle r)
551 {
552 	CGRect rbounds;
553 
554 	// sanity check.  Trips from the initial "terminal"
555 	if (r.max.x < r.min.x || r.max.y < r.min.y)
556 		return;
557 
558 	rbounds.size.width = r.max.x - r.min.x;
559 	rbounds.size.height = r.max.y - r.min.y;
560 	rbounds.origin.x = r.min.x;
561 	rbounds.origin.y = r.min.y;
562 
563 	if(rbounds.size.width <= 0 || rbounds.size.height <= 0)
564 		return;
565 
566 	QDBeginCGContext(GetWindowPort(theWindow), &context);
567 
568 	// The sub-image is relative to our whole screen image.
569 	CGImageRef subimg = CGImageCreateWithImageInRect(fullScreenImage, rbounds);
570 
571 	// Drawing the sub-image is relative to the window.
572 	rbounds.origin.y = winRect.bottom - winRect.top - r.min.y - rbounds.size.height;
573 	CGContextDrawImage(context, rbounds, subimg);
574 	CGImageRelease(subimg);
575 	QDEndCGContext(GetWindowPort(theWindow), &context);
576 
577 	needflush = true;
578 }
579 
580 uchar*
581 attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
582 {
583 	if(!triedscreen) {
584 		triedscreen = 1;
585 		screeninit();	/* TO DO: call this elsewhere? */
586 	}
587 	*r = gscreen->r;
588 	*chan = gscreen->chan;
589 	*depth = gscreen->depth;
590 	*width = gscreen->width;
591 	*softscreen = 1;
592 
593 	return gscreen->data->bdata;
594 }
595 
596 // PAL - no palette handling.  Don't intend to either.
597 void
598 getcolor(ulong i, ulong *r, ulong *g, ulong *b)
599 {
600 
601 // PAL: Certainly wrong to return a grayscale.
602 	 *r = i;
603 	 *g = i;
604 	 *b = i;
605 }
606 
607 void
608 setcolor(ulong index, ulong r, ulong g, ulong b)
609 {
610 	USED(index); USED(r); USED(g); USED(b);
611 }
612 
613 enum{
614 	SnarfSize=	100*1024
615 };
616 
617 static char snarf[3*SnarfSize+1];
618 static Rune rsnarf[SnarfSize+1];
619 
620 char*
621 clipread(void)
622 {
623 	CFDataRef cfdata;
624 	OSStatus err = noErr;
625 	ItemCount nitems;
626 	int i;
627 	char *s;
628 
629 	// Wow.  This is ridiculously complicated.
630 	PasteboardSynchronize(appleclip);
631 	if((err = PasteboardGetItemCount(appleclip, &nitems)) != noErr) {
632 		fprint(2, "apple pasteboard GetItemCount failed - Error %d\n", err);
633 		return 0;
634 	}
635 
636 	// Yes, based at 1.  Silly API.
637 	for(i = 1; i <= nitems; i++) {
638 		PasteboardItemID itemID;
639 		CFArrayRef flavorTypeArray;
640 		CFIndex flavorCount;
641 
642 		if((err = PasteboardGetItemIdentifier(appleclip, i, &itemID)) != noErr){
643 			fprint(2, "Can't get pasteboard item identifier: %d\n", err);
644 			return 0;
645 		}
646 
647 		if((err = PasteboardCopyItemFlavors(appleclip, itemID, &flavorTypeArray))!=noErr){
648 			fprint(2, "Can't copy pasteboard item flavors: %d\n", err);
649 			return 0;
650 		}
651 
652 		flavorCount = CFArrayGetCount(flavorTypeArray);
653 		CFIndex flavorIndex;
654 		for(flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex){
655 			CFStringRef flavorType;
656 			flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);
657 			if (UTTypeConformsTo(flavorType, CFSTR("public.utf16-plain-text"))){
658 				if((err = PasteboardCopyItemFlavorData(appleclip, itemID,
659 					CFSTR("public.utf16-plain-text"), &cfdata)) != noErr){
660 					fprint(2, "apple pasteboard CopyItem failed - Error %d\n", err);
661 					return 0;
662 				}
663 				CFIndex length = CFDataGetLength(cfdata);
664 				if (length > sizeof rsnarf) length = sizeof rsnarf;
665 				CFDataGetBytes(cfdata, CFRangeMake(0, length), (uint8_t *)rsnarf);
666 				snprint(snarf, sizeof snarf, "%.*S", length/sizeof(Rune), rsnarf);
667 				for(s = snarf; *s; s++)
668 					if(*s == '\r')
669 						*s = '\n';
670 				CFRelease(cfdata);
671 				return strdup(snarf);
672 			}
673 		}
674 	}
675 	return 0;
676 }
677 
678 int
679 clipwrite(char *snarf)
680 {
681 	CFDataRef cfdata;
682 	PasteboardSyncFlags flags;
683 
684 	runeseprint(rsnarf, rsnarf+nelem(rsnarf), "%s", snarf);
685 	if(PasteboardClear(appleclip) != noErr){
686 		fprint(2, "apple pasteboard clear failed\n");
687 		return 0;
688 	}
689 	flags = PasteboardSynchronize(appleclip);
690 	if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
691 		fprint(2, "apple pasteboard cannot assert ownership\n");
692 		return 0;
693 	}
694 	cfdata = CFDataCreate(kCFAllocatorDefault, (uchar*)rsnarf, runestrlen(rsnarf)*2);
695 	if(cfdata == nil){
696 		fprint(2, "apple pasteboard cfdatacreate failed\n");
697 		return 0;
698 	}
699 	if(PasteboardPutItemFlavor(appleclip, (PasteboardItemID)1,
700 		CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
701 		fprint(2, "apple pasteboard putitem failed\n");
702 		CFRelease(cfdata);
703 		return 0;
704 	}
705 	CFRelease(cfdata);
706 	return 1;
707 }
708 
709 void
710 setpointer(int x, int y)
711 {
712 	CGPoint pnt;
713 
714 	pnt.x = x + winRect.left;
715 	pnt.y = y + winRect.top;
716 	CGWarpMouseCursorPosition(pnt);
717 }
718 
719 void
720 drawcursor(Drawcursor* c)
721 {
722 	Cursor crsr;
723 	uchar *bc, *bs, *ps, *pm;
724 	int i, j, h, w, bpl;
725 
726 	if(c->data == nil || c->minx >= c->maxx){
727 		InitCursor();
728 		return;
729 	}
730 	memset(crsr.data, 0, sizeof(crsr.data));
731 	memset(crsr.mask, 0, sizeof(crsr.mask));
732 	ps = (uchar*)crsr.data;
733 	pm = (uchar*)crsr.mask;
734 	h = (c->maxy - c->miny)/2;	/* bounds include both masks, strangely */
735 	bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1);
736 	if((w = bpl) > 2)
737 		w = 2;
738 	bc = c->data;
739 	bs = c->data + h*bpl;
740 	if(h > 16)
741 		h = 16;
742 	for(i = 0; i < h; i++){
743 		for(j = 0; j < w; j++){
744 			ps[j] = bs[j];
745 			pm[j] = bs[j] | bc[j];
746 		}
747 		bs += bpl;
748 		bc += bpl;
749 		ps += 2;
750 		pm += 2;
751 	}
752 	crsr.hotSpot.h = -c->hotx;
753 	crsr.hotSpot.v = -c->hoty;
754 	SetCursor(&crsr);
755 }
756