screen.c (15169B)
1 #define Point OSXPoint 2 #define Rect OSXRect 3 #define Cursor OSXCursor 4 #include <Carbon/Carbon.h> 5 #include <QuickTime/QuickTime.h> // for full screen 6 #undef Rect 7 #undef Point 8 #undef Cursor 9 #undef offsetof 10 #undef nil 11 12 #include "u.h" 13 #include "lib.h" 14 #include "mem.h" 15 #include "dat.h" 16 #include "fns.h" 17 #include "error.h" 18 #define Image IMAGE /* kernel has its own Image */ 19 #include <draw.h> 20 #include <memdraw.h> 21 #include <keyboard.h> 22 #include <cursor.h> 23 #include "screen.h" 24 #include "mouse.h" 25 #include "keycodes.h" 26 #include "nineball.h" 27 28 struct { 29 Rectangle fullscreenr; 30 Rectangle screenr; 31 Memimage *screenimage; 32 int isfullscreen; 33 ulong fullscreentime; 34 35 Point xy; 36 int buttons; 37 int kbuttons; 38 39 CGDataProviderRef provider; 40 MenuRef wmenu; 41 MenuRef vmenu; 42 WindowRef window; 43 CGImageRef image; 44 PasteboardRef snarf; 45 } osx; 46 47 enum 48 { 49 WindowAttrs = 50 kWindowCloseBoxAttribute | 51 kWindowCollapseBoxAttribute | 52 kWindowResizableAttribute | 53 kWindowStandardHandlerAttribute | 54 kWindowFullZoomAttribute 55 }; 56 57 static void screenproc(void*); 58 static void eresized(int force); 59 static void fullscreen(void); 60 static void seticon(void); 61 62 static OSStatus quithandler(EventHandlerCallRef, EventRef, void*); 63 static OSStatus eventhandler(EventHandlerCallRef, EventRef, void*); 64 static OSStatus cmdhandler(EventHandlerCallRef, EventRef, void*); 65 66 enum 67 { 68 CmdFullScreen = 1, 69 }; 70 71 uchar* 72 attachscreen(Rectangle *r, ulong *chan, int *depth, 73 int *width, int *softscreen, void **X) 74 { 75 Memimage *m; 76 77 if(osx.screenimage == nil){ 78 screeninit(); 79 if(osx.screenimage == nil) 80 panic("cannot create OS X screen"); 81 } 82 m = osx.screenimage; 83 *r = m->r; 84 *chan = m->chan; 85 *depth = m->depth; 86 *width = m->width; 87 *X = nil; 88 *softscreen = 1; 89 return m->data->bdata; 90 } 91 92 void 93 _screeninit(void) 94 { 95 CGRect cgr; 96 OSXRect or; 97 98 _memimageinit(); 99 100 ProcessSerialNumber psn = { 0, kCurrentProcess }; 101 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 102 SetFrontProcess(&psn); 103 104 cgr = CGDisplayBounds(CGMainDisplayID()); 105 osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height); 106 107 InitCursor(); 108 109 // Create minimal menu with full-screen option. 110 ClearMenuBar(); 111 CreateStandardWindowMenu(0, &osx.wmenu); 112 InsertMenu(osx.wmenu, 0); 113 MenuItemIndex ix; 114 CreateNewMenu(1004, 0, &osx.vmenu); // XXX 1004? 115 SetMenuTitleWithCFString(osx.vmenu, CFSTR("View")); 116 AppendMenuItemTextWithCFString(osx.vmenu, 117 CFSTR("Full Screen"), 0, CmdFullScreen, &ix); 118 SetMenuItemCommandKey(osx.vmenu, ix, 0, 'F'); 119 InsertMenu(osx.vmenu, GetMenuID(osx.wmenu)); 120 DrawMenuBar(); 121 122 // Create the window. 123 or.left = 0; 124 or.top = 50; 125 or.bottom = Dy(osx.fullscreenr) - 200; 126 or.right = Dx(osx.fullscreenr); 127 CreateNewWindow(kDocumentWindowClass, WindowAttrs, &or, &osx.window); 128 SetWindowTitleWithCFString(osx.window, CFSTR("Plan 9 VX")); 129 seticon(); 130 131 // Set up the clip board. 132 if(PasteboardCreate(kPasteboardClipboard, &osx.snarf) != noErr) 133 panic("pasteboard create"); 134 135 // Explain in great detail which events we want to handle. 136 // Why can't we just have one handler? 137 const EventTypeSpec quits[] = { 138 { kEventClassApplication, kEventAppQuit } 139 }; 140 const EventTypeSpec cmds[] = { 141 { kEventClassWindow, kEventWindowClosed }, 142 { kEventClassWindow, kEventWindowBoundsChanged }, 143 { kEventClassCommand, kEventCommandProcess } 144 }; 145 const EventTypeSpec events[] = { 146 { kEventClassKeyboard, kEventRawKeyDown }, 147 { kEventClassKeyboard, kEventRawKeyModifiersChanged }, 148 { kEventClassKeyboard, kEventRawKeyRepeat }, 149 { kEventClassMouse, kEventMouseDown }, 150 { kEventClassMouse, kEventMouseUp }, 151 { kEventClassMouse, kEventMouseMoved }, 152 { kEventClassMouse, kEventMouseDragged }, 153 { kEventClassMouse, kEventMouseWheelMoved }, 154 }; 155 156 InstallApplicationEventHandler( 157 NewEventHandlerUPP(quithandler), 158 nelem(quits), quits, nil, nil); 159 160 InstallApplicationEventHandler( 161 NewEventHandlerUPP(eventhandler), 162 nelem(events), events, nil, nil); 163 164 InstallWindowEventHandler(osx.window, 165 NewEventHandlerUPP(cmdhandler), 166 nelem(cmds), cmds, osx.window, nil); 167 168 // Finally, put the window on the screen. 169 ShowWindow(osx.window); 170 ShowMenuBar(); 171 eresized(1); 172 SelectWindow(osx.window); 173 174 InitCursor(); 175 } 176 177 static Psleep scr; 178 179 void 180 screeninit(void) 181 { 182 plock(&scr); 183 kproc("*screen*", screenproc, nil); 184 while(osx.window == nil) 185 psleep(&scr); 186 punlock(&scr); 187 } 188 189 static void 190 screenproc(void *v) 191 { 192 plock(&scr); 193 _screeninit(); 194 pwakeup(&scr); 195 punlock(&scr); 196 RunApplicationEventLoop(); 197 iprint("screenproc exited!\n"); 198 } 199 200 static OSStatus kbdevent(EventRef); 201 static OSStatus mouseevent(EventRef); 202 203 static OSStatus 204 cmdhandler(EventHandlerCallRef next, EventRef event, void *arg) 205 { 206 return eventhandler(next, event, arg); 207 } 208 209 static OSStatus 210 quithandler(EventHandlerCallRef next, EventRef event, void *arg) 211 { 212 restoretty(); // XXX: should we? 213 exit(0); 214 return 0; 215 } 216 217 static OSStatus 218 eventhandler(EventHandlerCallRef next, EventRef event, void *arg) 219 { 220 OSStatus result; 221 222 result = CallNextEventHandler(next, event); 223 224 switch(GetEventClass(event)){ 225 case kEventClassKeyboard: 226 return kbdevent(event); 227 228 case kEventClassMouse: 229 return mouseevent(event); 230 231 case kEventClassCommand:; 232 HICommand cmd; 233 GetEventParameter(event, kEventParamDirectObject, 234 typeHICommand, nil, sizeof cmd, nil, &cmd); 235 switch(cmd.commandID){ 236 case kHICommandQuit: 237 restoretty(); // XXX: should we? 238 exit(0); 239 240 case CmdFullScreen: 241 fullscreen(); 242 break; 243 244 default: 245 return eventNotHandledErr; 246 } 247 break; 248 249 case kEventClassWindow:; 250 switch(GetEventKind(event)){ 251 case kEventWindowClosed: 252 restoretty(); // XXX: should we? 253 exit(0); 254 255 case kEventWindowBoundsChanged: 256 eresized(0); 257 break; 258 259 default: 260 return eventNotHandledErr; 261 } 262 break; 263 } 264 265 return result; 266 } 267 268 static OSStatus 269 mouseevent(EventRef event) 270 { 271 int wheel; 272 OSXPoint op; 273 274 GetEventParameter(event, kEventParamMouseLocation, 275 typeQDPoint, 0, sizeof op, 0, &op); 276 277 osx.xy = subpt(Pt(op.h, op.v), osx.screenr.min); 278 wheel = 0; 279 280 switch(GetEventKind(event)){ 281 case kEventMouseWheelMoved:; 282 SInt32 delta; 283 GetEventParameter(event, kEventParamMouseWheelDelta, 284 typeSInt32, 0, sizeof delta, 0, &delta); 285 if(delta > 0) 286 wheel = 8; 287 else 288 wheel = 16; 289 break; 290 291 case kEventMouseDown: 292 case kEventMouseUp:; 293 UInt32 but, mod; 294 GetEventParameter(event, kEventParamMouseChord, 295 typeUInt32, 0, sizeof but, 0, &but); 296 GetEventParameter(event, kEventParamKeyModifiers, 297 typeUInt32, 0, sizeof mod, 0, &mod); 298 299 // OS X swaps button 2 and 3 300 but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1); 301 302 // Apply keyboard modifiers and pretend it was a real mouse button. 303 // (Modifiers typed while holding the button go into kbuttons, 304 // but this one does not.) 305 if(but == 1){ 306 if(mod & optionKey) 307 but = 2; 308 else if(mod & cmdKey) 309 but = 4; 310 } 311 osx.buttons = but; 312 break; 313 314 case kEventMouseMoved: 315 case kEventMouseDragged: 316 break; 317 318 default: 319 return eventNotHandledErr; 320 } 321 322 mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec()); 323 return noErr; 324 } 325 326 static int keycvt[] = 327 { 328 [QZ_IBOOK_ENTER] '\n', 329 [QZ_RETURN] '\n', 330 [QZ_ESCAPE] 27, 331 [QZ_BACKSPACE] '\b', 332 [QZ_LALT] Kalt, 333 [QZ_LCTRL] Kctl, 334 [QZ_LSHIFT] Kshift, 335 [QZ_F1] KF+1, 336 [QZ_F2] KF+2, 337 [QZ_F3] KF+3, 338 [QZ_F4] KF+4, 339 [QZ_F5] KF+5, 340 [QZ_F6] KF+6, 341 [QZ_F7] KF+7, 342 [QZ_F8] KF+8, 343 [QZ_F9] KF+9, 344 [QZ_F10] KF+10, 345 [QZ_F11] KF+11, 346 [QZ_F12] KF+12, 347 [QZ_INSERT] Kins, 348 [QZ_DELETE] 0x7F, 349 [QZ_HOME] Khome, 350 [QZ_END] Kend, 351 [QZ_KP_PLUS] '+', 352 [QZ_KP_MINUS] '-', 353 [QZ_TAB] '\t', 354 [QZ_PAGEUP] Kpgup, 355 [QZ_PAGEDOWN] Kpgdown, 356 [QZ_UP] Kup, 357 [QZ_DOWN] Kdown, 358 [QZ_LEFT] Kleft, 359 [QZ_RIGHT] Kright, 360 [QZ_KP_MULTIPLY] '*', 361 [QZ_KP_DIVIDE] '/', 362 [QZ_KP_ENTER] '\n', 363 [QZ_KP_PERIOD] '.', 364 [QZ_KP0] '0', 365 [QZ_KP1] '1', 366 [QZ_KP2] '2', 367 [QZ_KP3] '3', 368 [QZ_KP4] '4', 369 [QZ_KP5] '5', 370 [QZ_KP6] '6', 371 [QZ_KP7] '7', 372 [QZ_KP8] '8', 373 [QZ_KP9] '9', 374 }; 375 376 static void 377 kputc(int c) 378 { 379 kbdputc(kbdq, c); 380 } 381 382 static OSStatus 383 kbdevent(EventRef event) 384 { 385 char ch; 386 UInt32 code; 387 UInt32 mod; 388 int k; 389 390 GetEventParameter(event, kEventParamKeyMacCharCodes, 391 typeChar, nil, sizeof ch, nil, &ch); 392 GetEventParameter(event, kEventParamKeyCode, 393 typeUInt32, nil, sizeof code, nil, &code); 394 GetEventParameter(event, kEventParamKeyModifiers, 395 typeUInt32, nil, sizeof mod, nil, &mod); 396 397 switch(GetEventKind(event)){ 398 case kEventRawKeyDown: 399 case kEventRawKeyRepeat: 400 if(mod == cmdKey){ 401 if(ch == 'F' || ch == 'f'){ 402 if(osx.isfullscreen && msec() - osx.fullscreentime > 500) 403 fullscreen(); 404 return noErr; 405 } 406 return eventNotHandledErr; 407 } 408 k = ch; 409 if(code < nelem(keycvt) && keycvt[code]) 410 k = keycvt[code]; 411 if(k >= 0) 412 latin1putc(k, kputc); 413 else{ 414 UniChar uc; 415 OSStatus s; 416 417 s = GetEventParameter(event, kEventParamKeyUnicodes, 418 typeUnicodeText, nil, sizeof uc, nil, &uc); 419 if(s == noErr) 420 kputc(uc); 421 } 422 break; 423 424 case kEventRawKeyModifiersChanged: 425 if(!osx.buttons && !osx.kbuttons){ 426 if(mod == optionKey) 427 latin1putc(Kalt, kputc); 428 break; 429 } 430 431 // If the mouse button is being held down, treat 432 // changes in the keyboard modifiers as changes 433 // in the mouse buttons. 434 osx.kbuttons = 0; 435 if(mod & optionKey) 436 osx.kbuttons |= 2; 437 if(mod & cmdKey) 438 osx.kbuttons |= 4; 439 mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec()); 440 break; 441 } 442 return noErr; 443 } 444 445 static void 446 eresized(int force) 447 { 448 Memimage *m; 449 OSXRect or; 450 ulong chan; 451 Rectangle r; 452 int bpl; 453 CGDataProviderRef provider; 454 CGImageRef image; 455 456 GetWindowBounds(osx.window, kWindowContentRgn, &or); 457 r = Rect(or.left, or.top, or.right, or.bottom); 458 if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr) && !force){ 459 // No need to make new image. 460 osx.screenr = r; 461 return; 462 } 463 464 chan = XBGR32; 465 m = allocmemimage(Rect(0, 0, Dx(r), Dy(r)), chan); 466 if(m == nil) 467 panic("allocmemimage: %r"); 468 if(m->data == nil) 469 panic("m->data == nil"); 470 bpl = bytesperline(r, 32); 471 provider = CGDataProviderCreateWithData(0, 472 m->data->bdata, Dy(r)*bpl, 0); 473 image = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl, 474 CGColorSpaceCreateDeviceRGB(), 475 kCGImageAlphaNoneSkipLast, 476 provider, 0, 0, kCGRenderingIntentDefault); 477 CGDataProviderRelease(provider); // CGImageCreate did incref 478 479 mouserect = m->r; 480 if(osx.image) 481 CGImageRelease(osx.image); 482 osx.image = image; 483 osx.screenr = r; 484 osx.screenimage = m; 485 termreplacescreenimage(m); 486 drawreplacescreenimage(m); // frees old osx.screenimage if any 487 } 488 489 void 490 flushmemscreen(Rectangle r) 491 { 492 CGRect cgr; 493 CGContextRef context; 494 CGImageRef subimg; 495 496 QDBeginCGContext(GetWindowPort(osx.window), &context); 497 498 cgr.origin.x = r.min.x; 499 cgr.origin.y = r.min.y; 500 cgr.size.width = Dx(r); 501 cgr.size.height = Dy(r); 502 subimg = CGImageCreateWithImageInRect(osx.image, cgr); 503 cgr.origin.y = Dy(osx.screenr) - r.max.y; // XXX how does this make any sense? 504 CGContextDrawImage(context, cgr, subimg); 505 CGContextFlush(context); 506 CGImageRelease(subimg); 507 508 QDEndCGContext(GetWindowPort(osx.window), &context); 509 } 510 511 void 512 fullscreen(void) 513 { 514 static Ptr restore; 515 static WindowRef oldwindow; 516 GDHandle device; 517 518 if(osx.isfullscreen){ 519 EndFullScreen(restore, 0); 520 osx.window = oldwindow; 521 ShowWindow(osx.window); 522 osx.isfullscreen = 0; 523 }else{ 524 HideWindow(osx.window); 525 oldwindow = osx.window; 526 GetWindowGreatestAreaDevice(osx.window, kWindowTitleBarRgn, &device, nil); 527 BeginFullScreen(&restore, device, 0, 0, &osx.window, 0, 0); 528 osx.isfullscreen = 1; 529 osx.fullscreentime = msec(); 530 } 531 eresized(1); 532 } 533 534 void 535 setmouse(Point p) 536 { 537 CGPoint cgp; 538 539 cgp.x = p.x + osx.screenr.min.x; 540 cgp.y = p.y + osx.screenr.min.y; 541 CGWarpMouseCursorPosition(cgp); 542 } 543 544 void 545 setcursor(struct Cursor *c) 546 { 547 OSXCursor oc; 548 int i; 549 550 // SetCursor is deprecated, but what replaces it? 551 for(i=0; i<16; i++){ 552 oc.data[i] = ((ushort*)c->set)[i]; 553 oc.mask[i] = oc.data[i] | ((ushort*)c->clr)[i]; 554 } 555 oc.hotSpot.h = - c->offset.x; 556 oc.hotSpot.v = - c->offset.y; 557 SetCursor(&oc); 558 } 559 560 void 561 getcolor(ulong i, ulong *r, ulong *g, ulong *b) 562 { 563 ulong v; 564 565 v = 0; 566 *r = (v>>16)&0xFF; 567 *g = (v>>8)&0xFF; 568 *b = v&0xFF; 569 } 570 571 int 572 setcolor(ulong i, ulong r, ulong g, ulong b) 573 { 574 /* no-op */ 575 return 0; 576 } 577 578 579 int 580 hwdraw(Memdrawparam *p) 581 { 582 return 0; 583 } 584 585 struct { 586 QLock lk; 587 char buf[SnarfSize]; 588 Rune rbuf[SnarfSize]; 589 PasteboardRef apple; 590 } clip; 591 592 char* 593 getsnarf(void) 594 { 595 char *s, *t; 596 CFArrayRef flavors; 597 CFDataRef data; 598 CFIndex nflavor, ndata, j; 599 CFStringRef type; 600 ItemCount nitem; 601 PasteboardItemID id; 602 PasteboardSyncFlags flags; 603 UInt32 i; 604 605 /* fprint(2, "applegetsnarf\n"); */ 606 qlock(&clip.lk); 607 clip.apple = osx.snarf; 608 if(clip.apple == nil){ 609 if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){ 610 fprint(2, "apple pasteboard create failed\n"); 611 qunlock(&clip.lk); 612 return nil; 613 } 614 } 615 flags = PasteboardSynchronize(clip.apple); 616 if(flags&kPasteboardClientIsOwner){ 617 s = strdup(clip.buf); 618 qunlock(&clip.lk); 619 return s; 620 } 621 if(PasteboardGetItemCount(clip.apple, &nitem) != noErr){ 622 fprint(2, "apple pasteboard get item count failed\n"); 623 qunlock(&clip.lk); 624 return nil; 625 } 626 for(i=1; i<=nitem; i++){ 627 if(PasteboardGetItemIdentifier(clip.apple, i, &id) != noErr) 628 continue; 629 if(PasteboardCopyItemFlavors(clip.apple, id, &flavors) != noErr) 630 continue; 631 nflavor = CFArrayGetCount(flavors); 632 for(j=0; j<nflavor; j++){ 633 type = (CFStringRef)CFArrayGetValueAtIndex(flavors, j); 634 if(!UTTypeConformsTo(type, CFSTR("public.utf16-plain-text"))) 635 continue; 636 if(PasteboardCopyItemFlavorData(clip.apple, id, type, &data) != noErr) 637 continue; 638 ndata = CFDataGetLength(data); 639 qunlock(&clip.lk); 640 s = smprint("%.*S", ndata/2, (Rune*)CFDataGetBytePtr(data)); 641 CFRelease(flavors); 642 CFRelease(data); 643 for(t=s; *t; t++) 644 if(*t == '\r') 645 *t = '\n'; 646 return s; 647 } 648 CFRelease(flavors); 649 } 650 qunlock(&clip.lk); 651 return nil; 652 } 653 654 void 655 putsnarf(char *s) 656 { 657 CFDataRef cfdata; 658 PasteboardSyncFlags flags; 659 660 /* fprint(2, "appleputsnarf\n"); */ 661 662 if(strlen(s) >= SnarfSize) 663 return; 664 qlock(&clip.lk); 665 strcpy(clip.buf, s); 666 runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s); 667 clip.apple = osx.snarf; 668 if(PasteboardClear(clip.apple) != noErr){ 669 fprint(2, "apple pasteboard clear failed\n"); 670 qunlock(&clip.lk); 671 return; 672 } 673 flags = PasteboardSynchronize(clip.apple); 674 if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){ 675 fprint(2, "apple pasteboard cannot assert ownership\n"); 676 qunlock(&clip.lk); 677 return; 678 } 679 cfdata = CFDataCreate(kCFAllocatorDefault, 680 (uchar*)clip.rbuf, runestrlen(clip.rbuf)*2); 681 if(cfdata == nil){ 682 fprint(2, "apple pasteboard cfdatacreate failed\n"); 683 qunlock(&clip.lk); 684 return; 685 } 686 if(PasteboardPutItemFlavor(clip.apple, (PasteboardItemID)1, 687 CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){ 688 fprint(2, "apple pasteboard putitem failed\n"); 689 CFRelease(cfdata); 690 qunlock(&clip.lk); 691 return; 692 } 693 /* CFRelease(cfdata); ??? */ 694 qunlock(&clip.lk); 695 } 696 697 static void 698 seticon(void) 699 { 700 CGImageRef im; 701 CGDataProviderRef d; 702 703 d = CGDataProviderCreateWithData(nil, nineball_png, sizeof nineball_png, nil); 704 im = CGImageCreateWithPNGDataProvider(d, nil, true, kCGRenderingIntentDefault); 705 if(im) 706 SetApplicationDockTileImage(im); 707 CGImageRelease(im); 708 CGDataProviderRelease(d); 709 } 710