xref: /inferno-os/emu/MacOSX/win.c (revision aaab9bcca9a6fd14bd8496059b80b984906db6bc)
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(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(!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  	if(appleclip == NULL)
630  		return nil;
631  	// Wow.  This is ridiculously complicated.
632  	PasteboardSynchronize(appleclip);
633  	if((err = PasteboardGetItemCount(appleclip, &nitems)) != noErr) {
634  		fprint(2, "apple pasteboard GetItemCount failed - Error %d\n", err);
635  		return 0;
636  	}
637  
638  	// Yes, based at 1.  Silly API.
639  	for(i = 1; i <= nitems; i++) {
640  		PasteboardItemID itemID;
641  		CFArrayRef flavorTypeArray;
642  		CFIndex flavorCount;
643  
644  		if((err = PasteboardGetItemIdentifier(appleclip, i, &itemID)) != noErr){
645  			fprint(2, "Can't get pasteboard item identifier: %d\n", err);
646  			return 0;
647  		}
648  
649  		if((err = PasteboardCopyItemFlavors(appleclip, itemID, &flavorTypeArray))!=noErr){
650  			fprint(2, "Can't copy pasteboard item flavors: %d\n", err);
651  			return 0;
652  		}
653  
654  		flavorCount = CFArrayGetCount(flavorTypeArray);
655  		CFIndex flavorIndex;
656  		for(flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex){
657  			CFStringRef flavorType;
658  			flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);
659  			if (UTTypeConformsTo(flavorType, CFSTR("public.utf16-plain-text"))){
660  				if((err = PasteboardCopyItemFlavorData(appleclip, itemID,
661  					CFSTR("public.utf16-plain-text"), &cfdata)) != noErr){
662  					fprint(2, "apple pasteboard CopyItem failed - Error %d\n", err);
663  					return 0;
664  				}
665  				CFIndex length = CFDataGetLength(cfdata);
666  				if (length > sizeof rsnarf) length = sizeof rsnarf;
667  				CFDataGetBytes(cfdata, CFRangeMake(0, length), (uint8_t *)rsnarf);
668  				snprint(snarf, sizeof snarf, "%.*S", length/sizeof(Rune), rsnarf);
669  				for(s = snarf; *s; s++)
670  					if(*s == '\r')
671  						*s = '\n';
672  				CFRelease(cfdata);
673  				return strdup(snarf);
674  			}
675  		}
676  	}
677  	return 0;
678  }
679  
680  int
681  clipwrite(char *snarf)
682  {
683  	CFDataRef cfdata;
684  	PasteboardSyncFlags flags;
685  
686  	if(appleclip == NULL)
687  		return 0;
688  	runeseprint(rsnarf, rsnarf+nelem(rsnarf), "%s", snarf);
689  	if(PasteboardClear(appleclip) != noErr){
690  		fprint(2, "apple pasteboard clear failed\n");
691  		return 0;
692  	}
693  	flags = PasteboardSynchronize(appleclip);
694  	if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
695  		fprint(2, "apple pasteboard cannot assert ownership\n");
696  		return 0;
697  	}
698  	cfdata = CFDataCreate(kCFAllocatorDefault, (uchar*)rsnarf, runestrlen(rsnarf)*2);
699  	if(cfdata == nil){
700  		fprint(2, "apple pasteboard cfdatacreate failed\n");
701  		return 0;
702  	}
703  	if(PasteboardPutItemFlavor(appleclip, (PasteboardItemID)1,
704  		CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
705  		fprint(2, "apple pasteboard putitem failed\n");
706  		CFRelease(cfdata);
707  		return 0;
708  	}
709  	CFRelease(cfdata);
710  	return 1;
711  }
712  
713  void
714  setpointer(int x, int y)
715  {
716  	CGPoint pnt;
717  
718  	pnt.x = x + winRect.left;
719  	pnt.y = y + winRect.top;
720  	CGWarpMouseCursorPosition(pnt);
721  }
722  
723  void
724  drawcursor(Drawcursor* c)
725  {
726  	Cursor crsr;
727  	uchar *bc, *bs, *ps, *pm;
728  	int i, j, h, w, bpl;
729  
730  	if(c->data == nil || c->minx >= c->maxx){
731  		InitCursor();
732  		return;
733  	}
734  	memset(crsr.data, 0, sizeof(crsr.data));
735  	memset(crsr.mask, 0, sizeof(crsr.mask));
736  	ps = (uchar*)crsr.data;
737  	pm = (uchar*)crsr.mask;
738  	h = (c->maxy - c->miny)/2;	/* bounds include both masks, strangely */
739  	bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1);
740  	if((w = bpl) > 2)
741  		w = 2;
742  	bc = c->data;
743  	bs = c->data + h*bpl;
744  	if(h > 16)
745  		h = 16;
746  	for(i = 0; i < h; i++){
747  		for(j = 0; j < w; j++){
748  			ps[j] = bs[j];
749  			pm[j] = bs[j] | bc[j];
750  		}
751  		bs += bpl;
752  		bc += bpl;
753  		ps += 2;
754  		pm += 2;
755  	}
756  	crsr.hotSpot.h = -c->hotx;
757  	crsr.hotSpot.v = -c->hoty;
758  	SetCursor(&crsr);
759  }
760