mac-screen.m (35815B)
1 #define Cursor OSXCursor 2 #define Point OSXPoint 3 #define Rect OSXRect 4 5 #import <Cocoa/Cocoa.h> 6 #import <Metal/Metal.h> 7 #import <QuartzCore/CAMetalLayer.h> 8 9 #undef Cursor 10 #undef Point 11 #undef Rect 12 13 #include <u.h> 14 #include <libc.h> 15 #include <thread.h> 16 #include <draw.h> 17 #include <memdraw.h> 18 #include <memlayer.h> 19 #include <mouse.h> 20 #include <cursor.h> 21 #include <keyboard.h> 22 #include <drawfcall.h> 23 #include "devdraw.h" 24 #include "bigarrow.h" 25 #include "glendapng.h" 26 27 AUTOFRAMEWORK(Cocoa) 28 AUTOFRAMEWORK(Metal) 29 AUTOFRAMEWORK(QuartzCore) 30 AUTOFRAMEWORK(CoreFoundation) 31 32 #define LOG if(0)NSLog 33 34 // TODO: Maintain list of views for dock menu. 35 36 static void setprocname(const char*); 37 static uint keycvt(uint); 38 static uint msec(void); 39 40 static void rpc_resizeimg(Client*); 41 static void rpc_resizewindow(Client*, Rectangle); 42 static void rpc_setcursor(Client*, Cursor*, Cursor2*); 43 static void rpc_setlabel(Client*, char*); 44 static void rpc_setmouse(Client*, Point); 45 static void rpc_topwin(Client*); 46 static void rpc_bouncemouse(Client*, Mouse); 47 static void rpc_flush(Client*, Rectangle); 48 49 static ClientImpl macimpl = { 50 rpc_resizeimg, 51 rpc_resizewindow, 52 rpc_setcursor, 53 rpc_setlabel, 54 rpc_setmouse, 55 rpc_topwin, 56 rpc_bouncemouse, 57 rpc_flush 58 }; 59 60 @class DrawView; 61 @class DrawLayer; 62 63 @interface AppDelegate : NSObject<NSApplicationDelegate> 64 @end 65 66 static AppDelegate *myApp = NULL; 67 68 void 69 gfx_main(void) 70 { 71 if(client0) 72 setprocname(argv0); 73 74 @autoreleasepool{ 75 [NSApplication sharedApplication]; 76 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 77 myApp = [AppDelegate new]; 78 [NSApp setDelegate:myApp]; 79 [NSApp run]; 80 } 81 } 82 83 84 void 85 rpc_shutdown(void) 86 { 87 [NSApp terminate:myApp]; 88 } 89 90 @implementation AppDelegate 91 - (void)applicationDidFinishLaunching:(id)arg 92 { 93 NSMenu *m, *sm; 94 NSData *d; 95 NSImage *i; 96 97 LOG(@"applicationDidFinishLaunching"); 98 99 sm = [NSMenu new]; 100 [sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"]; 101 [sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"]; 102 [sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; 103 m = [NSMenu new]; 104 [m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""]; 105 [m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]]; 106 [NSApp setMainMenu:m]; 107 108 d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)]; 109 i = [[NSImage alloc] initWithData:d]; 110 [NSApp setApplicationIconImage:i]; 111 [[NSApp dockTile] display]; 112 113 gfx_started(); 114 } 115 116 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { 117 return client0 != nil; 118 } 119 @end 120 121 @interface DrawLayer : CAMetalLayer 122 @property (nonatomic, retain) id<MTLCommandQueue> cmd; 123 @property (nonatomic, retain) id<MTLTexture> texture; 124 @end 125 126 @implementation DrawLayer 127 - (void)display 128 { 129 LOG(@"display"); 130 LOG(@"display query drawable"); 131 132 @autoreleasepool{ 133 id<CAMetalDrawable> drawable = [self nextDrawable]; 134 if(!drawable){ 135 LOG(@"display couldn't get drawable"); 136 [self setNeedsDisplay]; 137 return; 138 } 139 140 LOG(@"display got drawable"); 141 142 id<MTLCommandBuffer> cbuf = [self.cmd commandBuffer]; 143 id<MTLBlitCommandEncoder> blit = [cbuf blitCommandEncoder]; 144 [blit copyFromTexture:self.texture 145 sourceSlice:0 146 sourceLevel:0 147 sourceOrigin:MTLOriginMake(0, 0, 0) 148 sourceSize:MTLSizeMake(self.texture.width, self.texture.height, self.texture.depth) 149 toTexture:drawable.texture 150 destinationSlice:0 151 destinationLevel:0 152 destinationOrigin:MTLOriginMake(0, 0, 0)]; 153 [blit endEncoding]; 154 155 [cbuf presentDrawable:drawable]; 156 drawable = nil; 157 [cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){ 158 if(cmdBuff.error){ 159 NSLog(@"command buffer finished with error: %@", 160 cmdBuff.error.localizedDescription); 161 }else 162 LOG(@"command buffer finishes present drawable"); 163 }]; 164 [cbuf commit]; 165 } 166 LOG(@"display commit"); 167 } 168 @end 169 170 @interface DrawView : NSView<NSTextInputClient,NSWindowDelegate> 171 @property (nonatomic, assign) Client *client; 172 @property (nonatomic, retain) DrawLayer *dlayer; 173 @property (nonatomic, retain) NSWindow *win; 174 @property (nonatomic, retain) NSCursor *currentCursor; 175 @property (nonatomic, assign) Memimage *img; 176 177 - (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label; 178 - (void)topwin; 179 - (void)setlabel:(char*)label; 180 - (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2; 181 - (void)setmouse:(Point)p; 182 - (void)clearInput; 183 - (void)getmouse:(NSEvent*)e; 184 - (void)sendmouse:(NSUInteger)b; 185 - (void)resetLastInputRect; 186 - (void)enlargeLastInputRect:(NSRect)r; 187 @end 188 189 @implementation DrawView 190 { 191 NSMutableString *_tmpText; 192 NSRange _markedRange; 193 NSRange _selectedRange; 194 NSRect _lastInputRect; // The view is flipped, this is not. 195 BOOL _tapping; 196 NSUInteger _tapFingers; 197 NSUInteger _tapTime; 198 } 199 200 - (id)init 201 { 202 LOG(@"View init"); 203 self = [super init]; 204 [self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect]; 205 _tmpText = [[NSMutableString alloc] initWithCapacity:2]; 206 _markedRange = NSMakeRange(NSNotFound, 0); 207 _selectedRange = NSMakeRange(0, 0); 208 return self; 209 } 210 211 - (CALayer*)makeBackingLayer { return [DrawLayer layer]; } 212 - (BOOL)wantsUpdateLayer { return YES; } 213 - (BOOL)isOpaque { return YES; } 214 - (BOOL)isFlipped { return YES; } 215 - (BOOL)acceptsFirstResponder { return YES; } 216 217 // rpc_attach allocates a new screen window with the given label and size 218 // and attaches it to client c (by setting c->view). 219 Memimage* 220 rpc_attach(Client *c, char *label, char *winsize) 221 { 222 LOG(@"attachscreen(%s, %s)", label, winsize); 223 224 c->impl = &macimpl; 225 dispatch_sync(dispatch_get_main_queue(), ^(void) { 226 @autoreleasepool { 227 DrawView *view = [[DrawView new] attach:c winsize:winsize label:label]; 228 [view initimg]; 229 } 230 }); 231 return ((__bridge DrawView*)c->view).img; 232 } 233 234 - (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label { 235 NSRect r, sr; 236 Rectangle wr; 237 int set; 238 char *s; 239 NSArray *allDevices; 240 241 NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled 242 | NSWindowStyleMaskClosable 243 | NSWindowStyleMaskMiniaturizable 244 | NSWindowStyleMaskResizable; 245 246 if(label == nil || *label == '\0') 247 Winstyle &= ~NSWindowStyleMaskTitled; 248 249 s = winsize; 250 sr = [[NSScreen mainScreen] frame]; 251 r = [[NSScreen mainScreen] visibleFrame]; 252 253 LOG(@"makewin(%s)", s); 254 if(s == nil || *s == '\0' || parsewinsize(s, &wr, &set) < 0) { 255 wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3); 256 set = 0; 257 } 258 259 r.origin.x = wr.min.x; 260 r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */ 261 r.size.width = fmin(Dx(wr), r.size.width); 262 r.size.height = fmin(Dy(wr), r.size.height); 263 r = [NSWindow contentRectForFrameRect:r styleMask:Winstyle]; 264 265 NSWindow *win = [[NSWindow alloc] 266 initWithContentRect:r 267 styleMask:Winstyle 268 backing:NSBackingStoreBuffered defer:NO]; 269 [win setTitle:@"devdraw"]; 270 271 if(!set) 272 [win center]; 273 [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; 274 [win setContentMinSize:NSMakeSize(64,64)]; 275 [win setOpaque:YES]; 276 [win setRestorable:NO]; 277 [win setAcceptsMouseMovedEvents:YES]; 278 279 client->view = CFBridgingRetain(self); 280 self.client = client; 281 self.win = win; 282 self.currentCursor = nil; 283 [win setContentView:self]; 284 [win setDelegate:self]; 285 [self setWantsLayer:YES]; 286 [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay]; 287 288 id<MTLDevice> device = nil; 289 allDevices = MTLCopyAllDevices(); 290 for(id mtlDevice in allDevices) { 291 if ([mtlDevice isLowPower] && ![mtlDevice isRemovable]) { 292 device = mtlDevice; 293 break; 294 } 295 } 296 if(!device) 297 device = MTLCreateSystemDefaultDevice(); 298 299 DrawLayer *layer = (DrawLayer*)[self layer]; 300 self.dlayer = layer; 301 layer.device = device; 302 layer.cmd = [device newCommandQueue]; 303 layer.pixelFormat = MTLPixelFormatBGRA8Unorm; 304 layer.framebufferOnly = YES; 305 layer.opaque = YES; 306 307 // We use a default transparent layer on top of the CAMetalLayer. 308 // This seems to make fullscreen applications behave. 309 // Specifically, without this code if you enter full screen with Cmd-F, 310 // the screen goes black until the first mouse click. 311 if(1) { 312 CALayer *stub = [CALayer layer]; 313 stub.frame = CGRectMake(0, 0, 1, 1); 314 [stub setNeedsDisplay]; 315 [layer addSublayer:stub]; 316 } 317 318 [NSEvent setMouseCoalescingEnabled:NO]; 319 320 [self topwin]; 321 [self setlabel:label]; 322 [self setcursor:nil cursor2:nil]; 323 324 return self; 325 } 326 327 // rpc_topwin moves the window to the top of the desktop. 328 // Called from an RPC thread with no client lock held. 329 static void 330 rpc_topwin(Client *c) 331 { 332 DrawView *view = (__bridge DrawView*)c->view; 333 dispatch_sync(dispatch_get_main_queue(), ^(void) { 334 [view topwin]; 335 }); 336 } 337 338 - (void)topwin { 339 [self.win makeKeyAndOrderFront:nil]; 340 [NSApp activateIgnoringOtherApps:YES]; 341 } 342 343 // rpc_setlabel updates the client window's label. 344 // If label == nil, the call is a no-op. 345 // Called from an RPC thread with no client lock held. 346 static void 347 rpc_setlabel(Client *client, char *label) 348 { 349 DrawView *view = (__bridge DrawView*)client->view; 350 dispatch_sync(dispatch_get_main_queue(), ^(void){ 351 [view setlabel:label]; 352 }); 353 } 354 355 - (void)setlabel:(char*)label { 356 LOG(@"setlabel(%s)", label); 357 if(label == nil) 358 return; 359 360 @autoreleasepool{ 361 NSString *s = [[NSString alloc] initWithUTF8String:label]; 362 [self.win setTitle:s]; 363 if(client0) 364 [[NSApp dockTile] setBadgeLabel:s]; 365 } 366 } 367 368 // rpc_setcursor updates the client window's cursor image. 369 // Either c and c2 are both non-nil, or they are both nil to use the default arrow. 370 // Called from an RPC thread with no client lock held. 371 static void 372 rpc_setcursor(Client *client, Cursor *c, Cursor2 *c2) 373 { 374 DrawView *view = (__bridge DrawView*)client->view; 375 dispatch_sync(dispatch_get_main_queue(), ^(void){ 376 [view setcursor:c cursor2:c2]; 377 }); 378 } 379 380 - (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2 { 381 if(!c) { 382 c = &bigarrow; 383 c2 = &bigarrow2; 384 } 385 386 NSBitmapImageRep *r, *r2; 387 NSImage *i; 388 NSPoint p; 389 uchar *plane[5], *plane2[5]; 390 uint b; 391 392 r = [[NSBitmapImageRep alloc] 393 initWithBitmapDataPlanes:nil 394 pixelsWide:16 395 pixelsHigh:16 396 bitsPerSample:1 397 samplesPerPixel:2 398 hasAlpha:YES 399 isPlanar:YES 400 colorSpaceName:NSDeviceWhiteColorSpace 401 bytesPerRow:2 402 bitsPerPixel:0]; 403 [r getBitmapDataPlanes:plane]; 404 for(b=0; b<nelem(c->set); b++){ 405 plane[0][b] = ~c->set[b] & c->clr[b]; 406 plane[1][b] = c->set[b] | c->clr[b]; 407 } 408 409 r2 = [[NSBitmapImageRep alloc] 410 initWithBitmapDataPlanes:nil 411 pixelsWide:32 412 pixelsHigh:32 413 bitsPerSample:1 414 samplesPerPixel:2 415 hasAlpha:YES 416 isPlanar:YES 417 colorSpaceName:NSDeviceWhiteColorSpace 418 bytesPerRow:4 419 bitsPerPixel:0]; 420 [r2 getBitmapDataPlanes:plane2]; 421 for(b=0; b<nelem(c2->set); b++){ 422 plane2[0][b] = ~c2->set[b] & c2->clr[b]; 423 plane2[1][b] = c2->set[b] | c2->clr[b]; 424 } 425 426 static BOOL debug = NO; 427 if(debug){ 428 NSData *data = [r representationUsingType: NSBitmapImageFileTypeBMP properties: @{}]; 429 [data writeToFile: @"/tmp/r.bmp" atomically: NO]; 430 data = [r2 representationUsingType: NSBitmapImageFileTypeBMP properties: @{}]; 431 [data writeToFile: @"/tmp/r2.bmp" atomically: NO]; 432 debug = NO; 433 } 434 435 i = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)]; 436 [i addRepresentation:r2]; 437 [i addRepresentation:r]; 438 439 p = NSMakePoint(-c->offset.x, -c->offset.y); 440 self.currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p]; 441 [self.win invalidateCursorRectsForView:self]; 442 } 443 444 - (void)initimg { 445 @autoreleasepool { 446 CGFloat scale; 447 NSSize size; 448 MTLTextureDescriptor *textureDesc; 449 450 size = [self convertSizeToBacking:[self bounds].size]; 451 self.client->mouserect = Rect(0, 0, size.width, size.height); 452 453 LOG(@"initimg %.0f %.0f", size.width, size.height); 454 455 self.img = allocmemimage(self.client->mouserect, XRGB32); 456 if(self.img == nil) 457 panic("allocmemimage: %r"); 458 if(self.img->data == nil) 459 panic("img->data == nil"); 460 461 textureDesc = [MTLTextureDescriptor 462 texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 463 width:size.width 464 height:size.height 465 mipmapped:NO]; 466 textureDesc.allowGPUOptimizedContents = YES; 467 textureDesc.usage = MTLTextureUsageShaderRead; 468 textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined; 469 self.dlayer.texture = [self.dlayer.device newTextureWithDescriptor:textureDesc]; 470 471 scale = [self.win backingScaleFactor]; 472 [self.dlayer setDrawableSize:size]; 473 [self.dlayer setContentsScale:scale]; 474 475 // NOTE: This is not really the display DPI. 476 // On retina, scale is 2; otherwise it is 1. 477 // This formula gives us 220 for retina, 110 otherwise. 478 // That's not quite right but it's close to correct. 479 // https://en.wikipedia.org/wiki/Retina_display#Models 480 self.client->displaydpi = scale * 110; 481 } 482 } 483 484 // rpc_flush flushes changes to view.img's rectangle r 485 // to the on-screen window, making them visible. 486 // Called from an RPC thread with no client lock held. 487 static void 488 rpc_flush(Client *client, Rectangle r) 489 { 490 DrawView *view = (__bridge DrawView*)client->view; 491 dispatch_async(dispatch_get_main_queue(), ^(void){ 492 [view flush:r]; 493 }); 494 } 495 496 - (void)flush:(Rectangle)r { 497 @autoreleasepool{ 498 if(!rectclip(&r, Rect(0, 0, self.dlayer.texture.width, self.dlayer.texture.height)) || !rectclip(&r, self.img->r)) 499 return; 500 501 // drawlk protects the pixel data in self.img. 502 // In addition to avoiding a technical data race, 503 // the lock avoids drawing partial updates, which makes 504 // animations like sweeping windows much less flickery. 505 qlock(&drawlk); 506 [self.dlayer.texture 507 replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r)) 508 mipmapLevel:0 509 withBytes:byteaddr(self.img, Pt(r.min.x, r.min.y)) 510 bytesPerRow:self.img->width*sizeof(u32int)]; 511 qunlock(&drawlk); 512 513 NSRect nr = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r)); 514 dispatch_time_t time; 515 516 LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", nr.origin.x, nr.origin.y, nr.size.width, nr.size.height); 517 nr = [self.win convertRectFromBacking:nr]; 518 LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", nr.origin.x, nr.origin.y, nr.size.width, nr.size.height); 519 [self.dlayer setNeedsDisplayInRect:nr]; 520 521 time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC); 522 dispatch_after(time, dispatch_get_main_queue(), ^(void){ 523 [self.dlayer setNeedsDisplayInRect:nr]; 524 }); 525 526 [self enlargeLastInputRect:nr]; 527 } 528 } 529 530 // rpc_resizeimg forces the client window to discard its current window and make a new one. 531 // It is called when the user types Cmd-R to toggle whether retina mode is forced. 532 // Called from an RPC thread with no client lock held. 533 static void 534 rpc_resizeimg(Client *c) 535 { 536 DrawView *view = (__bridge DrawView*)c->view; 537 dispatch_async(dispatch_get_main_queue(), ^(void){ 538 [view resizeimg]; 539 }); 540 } 541 542 - (void)resizeimg { 543 [self initimg]; 544 gfx_replacescreenimage(self.client, self.img); 545 } 546 547 - (void)windowDidResize:(NSNotification *)notification { 548 if(![self inLiveResize] && self.img) { 549 [self resizeimg]; 550 } 551 } 552 - (void)viewDidEndLiveResize 553 { 554 [super viewDidEndLiveResize]; 555 if(self.img) 556 [self resizeimg]; 557 } 558 559 - (void)viewDidChangeBackingProperties 560 { 561 [super viewDidChangeBackingProperties]; 562 if(self.img) 563 [self resizeimg]; 564 } 565 566 // rpc_resizewindow asks for the client window to be resized to size r. 567 // Called from an RPC thread with no client lock held. 568 static void 569 rpc_resizewindow(Client *c, Rectangle r) 570 { 571 DrawView *view = (__bridge DrawView*)c->view; 572 573 LOG(@"resizewindow %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r)); 574 dispatch_async(dispatch_get_main_queue(), ^(void){ 575 NSSize s; 576 577 s = [view convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))]; 578 [view.win setContentSize:s]; 579 }); 580 } 581 582 583 - (void)windowDidBecomeKey:(id)arg { 584 [self sendmouse:0]; 585 } 586 587 - (void)windowDidResignKey:(id)arg { 588 gfx_abortcompose(self.client); 589 } 590 591 - (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];} 592 - (void)mouseDown:(NSEvent*)e{ [self getmouse:e];} 593 - (void)mouseDragged:(NSEvent*)e{ [self getmouse:e];} 594 - (void)mouseUp:(NSEvent*)e{ [self getmouse:e];} 595 - (void)otherMouseDown:(NSEvent*)e{ [self getmouse:e];} 596 - (void)otherMouseDragged:(NSEvent*)e{ [self getmouse:e];} 597 - (void)otherMouseUp:(NSEvent*)e{ [self getmouse:e];} 598 - (void)rightMouseDown:(NSEvent*)e{ [self getmouse:e];} 599 - (void)rightMouseDragged:(NSEvent*)e{ [self getmouse:e];} 600 - (void)rightMouseUp:(NSEvent*)e{ [self getmouse:e];} 601 602 - (void)scrollWheel:(NSEvent*)e 603 { 604 CGFloat s; 605 606 s = [e scrollingDeltaY]; 607 if(s > 0.0f) 608 [self sendmouse:8]; 609 else if (s < 0.0f) 610 [self sendmouse:16]; 611 } 612 613 - (void)keyDown:(NSEvent*)e 614 { 615 LOG(@"keyDown to interpret"); 616 617 [self interpretKeyEvents:[NSArray arrayWithObject:e]]; 618 619 [self resetLastInputRect]; 620 } 621 622 - (void)flagsChanged:(NSEvent*)e 623 { 624 static NSEventModifierFlags omod; 625 NSEventModifierFlags m; 626 uint b; 627 628 LOG(@"flagsChanged"); 629 m = [e modifierFlags]; 630 631 b = [NSEvent pressedMouseButtons]; 632 b = (b&~6) | (b&4)>>1 | (b&2)<<1; 633 if(b){ 634 int x; 635 x = 0; 636 if(m & ~omod & NSEventModifierFlagControl) 637 x = 1; 638 if(m & ~omod & NSEventModifierFlagOption) 639 x = 2; 640 if(m & ~omod & NSEventModifierFlagCommand) 641 x = 4; 642 b |= x; 643 if(m & NSEventModifierFlagShift) 644 b <<= 5; 645 [self sendmouse:b]; 646 }else if(m & ~omod & NSEventModifierFlagOption) 647 gfx_keystroke(self.client, Kalt); 648 649 omod = m; 650 } 651 652 - (void)magnifyWithEvent:(NSEvent*)e 653 { 654 if(fabs([e magnification]) > 0.02) 655 [[self window] toggleFullScreen:nil]; 656 } 657 658 - (void)touchesBeganWithEvent:(NSEvent*)e 659 { 660 _tapping = YES; 661 _tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count; 662 _tapTime = msec(); 663 } 664 - (void)touchesMovedWithEvent:(NSEvent*)e 665 { 666 _tapping = NO; 667 } 668 - (void)touchesEndedWithEvent:(NSEvent*)e 669 { 670 if(_tapping 671 && [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0 672 && msec() - _tapTime < 250){ 673 switch(_tapFingers){ 674 case 3: 675 [self sendmouse:2]; 676 [self sendmouse:0]; 677 break; 678 case 4: 679 [self sendmouse:2]; 680 [self sendmouse:1]; 681 [self sendmouse:0]; 682 break; 683 } 684 _tapping = NO; 685 } 686 } 687 - (void)touchesCancelledWithEvent:(NSEvent*)e 688 { 689 _tapping = NO; 690 } 691 692 - (void)getmouse:(NSEvent *)e 693 { 694 NSUInteger b; 695 NSEventModifierFlags m; 696 697 b = [NSEvent pressedMouseButtons]; 698 b = b&~6 | (b&4)>>1 | (b&2)<<1; 699 b = mouseswap(b); 700 701 m = [e modifierFlags]; 702 if(b == 1){ 703 if(m & NSEventModifierFlagOption){ 704 gfx_abortcompose(self.client); 705 b = 2; 706 }else 707 if(m & NSEventModifierFlagCommand) 708 b = 4; 709 } 710 if(m & NSEventModifierFlagShift) 711 b <<= 5; 712 [self sendmouse:b]; 713 } 714 715 - (void)sendmouse:(NSUInteger)b 716 { 717 NSPoint p; 718 719 p = [self.window convertPointToBacking: 720 [self.window mouseLocationOutsideOfEventStream]]; 721 p.y = Dy(self.client->mouserect) - p.y; 722 // LOG(@"(%g, %g) <- sendmouse(%d)", p.x, p.y, (uint)b); 723 gfx_mousetrack(self.client, p.x, p.y, b, msec()); 724 if(b && _lastInputRect.size.width && _lastInputRect.size.height) 725 [self resetLastInputRect]; 726 } 727 728 // rpc_setmouse moves the mouse cursor. 729 // Called from an RPC thread with no client lock held. 730 static void 731 rpc_setmouse(Client *c, Point p) 732 { 733 DrawView *view = (__bridge DrawView*)c->view; 734 dispatch_async(dispatch_get_main_queue(), ^(void){ 735 [view setmouse:p]; 736 }); 737 } 738 739 - (void)setmouse:(Point)p { 740 @autoreleasepool{ 741 NSPoint q; 742 743 LOG(@"setmouse(%d,%d)", p.x, p.y); 744 q = [self.win convertPointFromBacking:NSMakePoint(p.x, p.y)]; 745 LOG(@"(%g, %g) <- fromBacking", q.x, q.y); 746 q = [self convertPoint:q toView:nil]; 747 LOG(@"(%g, %g) <- toWindow", q.x, q.y); 748 q = [self.win convertPointToScreen:q]; 749 LOG(@"(%g, %g) <- toScreen", q.x, q.y); 750 // Quartz has the origin of the "global display 751 // coordinate space" at the top left of the primary 752 // screen with y increasing downward, while Cocoa has 753 // the origin at the bottom left of the primary screen 754 // with y increasing upward. We flip the coordinate 755 // with a negative sign and shift upward by the height 756 // of the primary screen. 757 q.y = NSScreen.screens[0].frame.size.height - q.y; 758 LOG(@"(%g, %g) <- setmouse", q.x, q.y); 759 CGWarpMouseCursorPosition(NSPointToCGPoint(q)); 760 CGAssociateMouseAndMouseCursorPosition(true); 761 } 762 } 763 764 765 - (void)resetCursorRects { 766 [super resetCursorRects]; 767 [self addCursorRect:self.bounds cursor:self.currentCursor]; 768 } 769 770 // conforms to protocol NSTextInputClient 771 - (BOOL)hasMarkedText { return _markedRange.location != NSNotFound; } 772 - (NSRange)markedRange { return _markedRange; } 773 - (NSRange)selectedRange { return _selectedRange; } 774 775 - (void)setMarkedText:(id)string 776 selectedRange:(NSRange)sRange 777 replacementRange:(NSRange)rRange 778 { 779 NSString *str; 780 781 LOG(@"setMarkedText: %@ (%ld, %ld) (%ld, %ld)", string, 782 sRange.location, sRange.length, 783 rRange.location, rRange.length); 784 785 [self clearInput]; 786 787 if([string isKindOfClass:[NSAttributedString class]]) 788 str = [string string]; 789 else 790 str = string; 791 792 if(rRange.location == NSNotFound){ 793 if(_markedRange.location != NSNotFound){ 794 rRange = _markedRange; 795 }else{ 796 rRange = _selectedRange; 797 } 798 } 799 800 if(str.length == 0){ 801 [_tmpText deleteCharactersInRange:rRange]; 802 [self unmarkText]; 803 }else{ 804 _markedRange = NSMakeRange(rRange.location, str.length); 805 [_tmpText replaceCharactersInRange:rRange withString:str]; 806 } 807 _selectedRange.location = rRange.location + sRange.location; 808 _selectedRange.length = sRange.length; 809 810 if(_tmpText.length){ 811 uint i; 812 LOG(@"text length %ld", _tmpText.length); 813 for(i = 0; i <= _tmpText.length; ++i){ 814 if(i == _markedRange.location) 815 gfx_keystroke(self.client, '['); 816 if(_selectedRange.length){ 817 if(i == _selectedRange.location) 818 gfx_keystroke(self.client, '{'); 819 if(i == NSMaxRange(_selectedRange)) 820 gfx_keystroke(self.client, '}'); 821 } 822 if(i == NSMaxRange(_markedRange)) 823 gfx_keystroke(self.client, ']'); 824 if(i < _tmpText.length) 825 gfx_keystroke(self.client, [_tmpText characterAtIndex:i]); 826 } 827 int l; 828 l = 1 + _tmpText.length - NSMaxRange(_selectedRange) 829 + (_selectedRange.length > 0); 830 LOG(@"move left %d", l); 831 for(i = 0; i < l; ++i) 832 gfx_keystroke(self.client, Kleft); 833 } 834 835 LOG(@"text: \"%@\" (%ld,%ld) (%ld,%ld)", _tmpText, 836 _markedRange.location, _markedRange.length, 837 _selectedRange.location, _selectedRange.length); 838 } 839 840 - (void)unmarkText { 841 //NSUInteger i; 842 NSUInteger len; 843 844 LOG(@"unmarkText"); 845 len = [_tmpText length]; 846 //for(i = 0; i < len; ++i) 847 // gfx_keystroke(self.client, [_tmpText characterAtIndex:i]); 848 [_tmpText deleteCharactersInRange:NSMakeRange(0, len)]; 849 _markedRange = NSMakeRange(NSNotFound, 0); 850 _selectedRange = NSMakeRange(0, 0); 851 } 852 853 - (NSArray<NSAttributedStringKey>*)validAttributesForMarkedText { 854 LOG(@"validAttributesForMarkedText"); 855 return @[]; 856 } 857 858 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)r 859 actualRange:(NSRangePointer)actualRange 860 { 861 NSRange sr; 862 NSAttributedString *s; 863 864 LOG(@"attributedSubstringForProposedRange: (%ld, %ld) (%ld, %ld)", 865 r.location, r.length, actualRange->location, actualRange->length); 866 sr = NSMakeRange(0, [_tmpText length]); 867 sr = NSIntersectionRange(sr, r); 868 if(actualRange) 869 *actualRange = sr; 870 LOG(@"use range: %ld, %ld", sr.location, sr.length); 871 s = nil; 872 if(sr.length) 873 s = [[NSAttributedString alloc] 874 initWithString:[_tmpText substringWithRange:sr]]; 875 LOG(@" return %@", s); 876 return s; 877 } 878 879 - (void)insertText:(id)s replacementRange:(NSRange)r { 880 NSUInteger i; 881 NSUInteger len; 882 883 LOG(@"insertText: %@ replacementRange: %ld, %ld", s, r.location, r.length); 884 885 [self clearInput]; 886 887 len = [s length]; 888 for(i = 0; i < len; ++i) 889 gfx_keystroke(self.client, [s characterAtIndex:i]); 890 [_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)]; 891 _markedRange = NSMakeRange(NSNotFound, 0); 892 _selectedRange = NSMakeRange(0, 0); 893 } 894 895 - (NSUInteger)characterIndexForPoint:(NSPoint)point 896 { 897 LOG(@"characterIndexForPoint: %g, %g", point.x, point.y); 898 return 0; 899 } 900 901 - (NSRect)firstRectForCharacterRange:(NSRange)r actualRange:(NSRangePointer)actualRange { 902 LOG(@"firstRectForCharacterRange: (%ld, %ld) (%ld, %ld)", 903 r.location, r.length, actualRange->location, actualRange->length); 904 if(actualRange) 905 *actualRange = r; 906 return [[self window] convertRectToScreen:_lastInputRect]; 907 } 908 909 - (void)doCommandBySelector:(SEL)s { 910 NSEvent *e; 911 NSEventModifierFlags m; 912 uint c, k; 913 914 LOG(@"doCommandBySelector (%@)", NSStringFromSelector(s)); 915 916 e = [NSApp currentEvent]; 917 c = [[e characters] characterAtIndex:0]; 918 k = keycvt(c); 919 LOG(@"keyDown: character0: 0x%x -> 0x%x", c, k); 920 m = [e modifierFlags]; 921 922 if(m & NSEventModifierFlagCommand){ 923 if((m & NSEventModifierFlagShift) && 'a' <= k && k <= 'z') 924 k += 'A' - 'a'; 925 if(' '<=k && k<='~') 926 k += Kcmd; 927 } 928 if(k>0) 929 gfx_keystroke(self.client, k); 930 } 931 932 // Helper for managing input rect approximately 933 - (void)resetLastInputRect { 934 LOG(@"resetLastInputRect"); 935 _lastInputRect.origin.x = 0.0; 936 _lastInputRect.origin.y = 0.0; 937 _lastInputRect.size.width = 0.0; 938 _lastInputRect.size.height = 0.0; 939 } 940 941 - (void)enlargeLastInputRect:(NSRect)r { 942 r.origin.y = [self bounds].size.height - r.origin.y - r.size.height; 943 _lastInputRect = NSUnionRect(_lastInputRect, r); 944 LOG(@"update last input rect (%g, %g, %g, %g)", 945 _lastInputRect.origin.x, _lastInputRect.origin.y, 946 _lastInputRect.size.width, _lastInputRect.size.height); 947 } 948 949 - (void)clearInput { 950 if(_tmpText.length){ 951 uint i; 952 int l; 953 l = 1 + _tmpText.length - NSMaxRange(_selectedRange) 954 + (_selectedRange.length > 0); 955 LOG(@"move right %d", l); 956 for(i = 0; i < l; ++i) 957 gfx_keystroke(self.client, Kright); 958 l = _tmpText.length+2+2*(_selectedRange.length > 0); 959 LOG(@"backspace %d", l); 960 for(uint i = 0; i < l; ++i) 961 gfx_keystroke(self.client, Kbs); 962 } 963 } 964 965 - (NSApplicationPresentationOptions)window:(id)arg 966 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions { 967 // The default for full-screen is to auto-hide the dock and menu bar, 968 // but the menu bar in particular comes back when the cursor is just 969 // near the top of the screen, which makes acme's top tag line very difficult to use. 970 // Disable the menu bar entirely. 971 // In theory this code disables the dock entirely too, but if you drag the mouse 972 // down far enough off the bottom of the screen the dock still unhides. 973 // That's OK. 974 NSApplicationPresentationOptions o; 975 o = proposedOptions; 976 o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar); 977 o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar; 978 return o; 979 } 980 981 - (void)windowWillEnterFullScreen:(NSNotification*)notification { 982 // This is a heavier-weight way to make sure the menu bar and dock go away, 983 // but this affects all screens even though the app is running on full screen 984 // on only one screen, so it's not great. The behavior from the 985 // willUseFullScreenPresentationOptions seems to be enough for now. 986 /* 987 [[NSApplication sharedApplication] 988 setPresentationOptions:NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock]; 989 */ 990 } 991 992 - (void)windowDidExitFullScreen:(NSNotification*)notification { 993 /* 994 [[NSApplication sharedApplication] 995 setPresentationOptions:NSApplicationPresentationDefault]; 996 */ 997 } 998 @end 999 1000 static uint 1001 msec(void) 1002 { 1003 return nsec()/1000000; 1004 } 1005 1006 static uint 1007 keycvt(uint code) 1008 { 1009 switch(code){ 1010 case '\r': return '\n'; 1011 case 127: return '\b'; 1012 case NSUpArrowFunctionKey: return Kup; 1013 case NSDownArrowFunctionKey: return Kdown; 1014 case NSLeftArrowFunctionKey: return Kleft; 1015 case NSRightArrowFunctionKey: return Kright; 1016 case NSInsertFunctionKey: return Kins; 1017 case NSDeleteFunctionKey: return Kdel; 1018 case NSHomeFunctionKey: return Khome; 1019 case NSEndFunctionKey: return Kend; 1020 case NSPageUpFunctionKey: return Kpgup; 1021 case NSPageDownFunctionKey: return Kpgdown; 1022 case NSF1FunctionKey: return KF|1; 1023 case NSF2FunctionKey: return KF|2; 1024 case NSF3FunctionKey: return KF|3; 1025 case NSF4FunctionKey: return KF|4; 1026 case NSF5FunctionKey: return KF|5; 1027 case NSF6FunctionKey: return KF|6; 1028 case NSF7FunctionKey: return KF|7; 1029 case NSF8FunctionKey: return KF|8; 1030 case NSF9FunctionKey: return KF|9; 1031 case NSF10FunctionKey: return KF|10; 1032 case NSF11FunctionKey: return KF|11; 1033 case NSF12FunctionKey: return KF|12; 1034 case NSBeginFunctionKey: 1035 case NSPrintScreenFunctionKey: 1036 case NSScrollLockFunctionKey: 1037 case NSF13FunctionKey: 1038 case NSF14FunctionKey: 1039 case NSF15FunctionKey: 1040 case NSF16FunctionKey: 1041 case NSF17FunctionKey: 1042 case NSF18FunctionKey: 1043 case NSF19FunctionKey: 1044 case NSF20FunctionKey: 1045 case NSF21FunctionKey: 1046 case NSF22FunctionKey: 1047 case NSF23FunctionKey: 1048 case NSF24FunctionKey: 1049 case NSF25FunctionKey: 1050 case NSF26FunctionKey: 1051 case NSF27FunctionKey: 1052 case NSF28FunctionKey: 1053 case NSF29FunctionKey: 1054 case NSF30FunctionKey: 1055 case NSF31FunctionKey: 1056 case NSF32FunctionKey: 1057 case NSF33FunctionKey: 1058 case NSF34FunctionKey: 1059 case NSF35FunctionKey: 1060 case NSPauseFunctionKey: 1061 case NSSysReqFunctionKey: 1062 case NSBreakFunctionKey: 1063 case NSResetFunctionKey: 1064 case NSStopFunctionKey: 1065 case NSMenuFunctionKey: 1066 case NSUserFunctionKey: 1067 case NSSystemFunctionKey: 1068 case NSPrintFunctionKey: 1069 case NSClearLineFunctionKey: 1070 case NSClearDisplayFunctionKey: 1071 case NSInsertLineFunctionKey: 1072 case NSDeleteLineFunctionKey: 1073 case NSInsertCharFunctionKey: 1074 case NSDeleteCharFunctionKey: 1075 case NSPrevFunctionKey: 1076 case NSNextFunctionKey: 1077 case NSSelectFunctionKey: 1078 case NSExecuteFunctionKey: 1079 case NSUndoFunctionKey: 1080 case NSRedoFunctionKey: 1081 case NSFindFunctionKey: 1082 case NSHelpFunctionKey: 1083 case NSModeSwitchFunctionKey: return 0; 1084 default: return code; 1085 } 1086 } 1087 1088 // rpc_getsnarf reads the current pasteboard as a plain text string. 1089 // Called from an RPC thread with no client lock held. 1090 char* 1091 rpc_getsnarf(void) 1092 { 1093 char __block *ret; 1094 1095 ret = nil; 1096 dispatch_sync(dispatch_get_main_queue(), ^(void) { 1097 @autoreleasepool { 1098 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 1099 NSString *s = [pb stringForType:NSPasteboardTypeString]; 1100 if(s) 1101 ret = strdup((char*)[s UTF8String]); 1102 } 1103 }); 1104 return ret; 1105 } 1106 1107 // rpc_putsnarf writes the given text to the pasteboard. 1108 // Called from an RPC thread with no client lock held. 1109 void 1110 rpc_putsnarf(char *s) 1111 { 1112 if(s == nil || strlen(s) >= SnarfSize) 1113 return; 1114 1115 dispatch_sync(dispatch_get_main_queue(), ^(void) { 1116 @autoreleasepool{ 1117 NSArray *t = [NSArray arrayWithObject:NSPasteboardTypeString]; 1118 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 1119 NSString *str = [[NSString alloc] initWithUTF8String:s]; 1120 [pb declareTypes:t owner:nil]; 1121 [pb setString:str forType:NSPasteboardTypeString]; 1122 } 1123 }); 1124 } 1125 1126 // rpc_bouncemouse is for sending a mouse event 1127 // back to the X11 window manager rio(1). 1128 // Does not apply here. 1129 static void 1130 rpc_bouncemouse(Client *c, Mouse m) 1131 { 1132 } 1133 1134 // We don't use the graphics thread state during memimagedraw, 1135 // so rpc_gfxdrawlock and rpc_gfxdrawunlock are no-ops. 1136 void 1137 rpc_gfxdrawlock(void) 1138 { 1139 } 1140 1141 void 1142 rpc_gfxdrawunlock(void) 1143 { 1144 } 1145 1146 static void 1147 setprocname(const char *s) 1148 { 1149 CFStringRef process_name; 1150 1151 process_name = CFStringCreateWithBytes(nil, (uchar*)s, strlen(s), kCFStringEncodingUTF8, false); 1152 1153 // Adapted from Chrome's mac_util.mm. 1154 // http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm 1155 // 1156 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1157 // 1158 // Redistribution and use in source and binary forms, with or without 1159 // modification, are permitted provided that the following conditions are 1160 // met: 1161 // 1162 // * Redistributions of source code must retain the above copyright 1163 // notice, this list of conditions and the following disclaimer. 1164 // * Redistributions in binary form must reproduce the above 1165 // copyright notice, this list of conditions and the following disclaimer 1166 // in the documentation and/or other materials provided with the 1167 // distribution. 1168 // * Neither the name of Google Inc. nor the names of its 1169 // contributors may be used to endorse or promote products derived from 1170 // this software without specific prior written permission. 1171 // 1172 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1173 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1174 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1175 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1176 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 1177 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1178 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 1179 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 1180 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1181 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 1182 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1183 // Warning: here be dragons! This is SPI reverse-engineered from WebKit's 1184 // plugin host, and could break at any time (although realistically it's only 1185 // likely to break in a new major release). 1186 // When 10.7 is available, check that this still works, and update this 1187 // comment for 10.8. 1188 1189 // Private CFType used in these LaunchServices calls. 1190 typedef CFTypeRef PrivateLSASN; 1191 typedef PrivateLSASN (*LSGetCurrentApplicationASNType)(); 1192 typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN, 1193 CFStringRef, 1194 CFStringRef, 1195 CFDictionaryRef*); 1196 1197 static LSGetCurrentApplicationASNType ls_get_current_application_asn_func = 1198 NULL; 1199 static LSSetApplicationInformationItemType 1200 ls_set_application_information_item_func = NULL; 1201 static CFStringRef ls_display_name_key = NULL; 1202 1203 static bool did_symbol_lookup = false; 1204 if (!did_symbol_lookup) { 1205 did_symbol_lookup = true; 1206 CFBundleRef launch_services_bundle = 1207 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); 1208 if (!launch_services_bundle) { 1209 fprint(2, "Failed to look up LaunchServices bundle\n"); 1210 return; 1211 } 1212 1213 ls_get_current_application_asn_func = 1214 (LSGetCurrentApplicationASNType)( 1215 CFBundleGetFunctionPointerForName( 1216 launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN"))); 1217 if (!ls_get_current_application_asn_func) 1218 fprint(2, "Could not find _LSGetCurrentApplicationASN\n"); 1219 1220 ls_set_application_information_item_func = 1221 (LSSetApplicationInformationItemType)( 1222 CFBundleGetFunctionPointerForName( 1223 launch_services_bundle, 1224 CFSTR("_LSSetApplicationInformationItem"))); 1225 if (!ls_set_application_information_item_func) 1226 fprint(2, "Could not find _LSSetApplicationInformationItem\n"); 1227 1228 CFStringRef* key_pointer = (CFStringRef*)( 1229 CFBundleGetDataPointerForName(launch_services_bundle, 1230 CFSTR("_kLSDisplayNameKey"))); 1231 ls_display_name_key = key_pointer ? *key_pointer : NULL; 1232 if (!ls_display_name_key) 1233 fprint(2, "Could not find _kLSDisplayNameKey\n"); 1234 1235 // Internally, this call relies on the Mach ports that are started up by the 1236 // Carbon Process Manager. In debug builds this usually happens due to how 1237 // the logging layers are started up; but in release, it isn't started in as 1238 // much of a defined order. So if the symbols had to be loaded, go ahead 1239 // and force a call to make sure the manager has been initialized and hence 1240 // the ports are opened. 1241 ProcessSerialNumber psn; 1242 GetCurrentProcess(&psn); 1243 } 1244 if (!ls_get_current_application_asn_func || 1245 !ls_set_application_information_item_func || 1246 !ls_display_name_key) { 1247 return; 1248 } 1249 1250 PrivateLSASN asn = ls_get_current_application_asn_func(); 1251 // Constant used by WebKit; what exactly it means is unknown. 1252 const int magic_session_constant = -2; 1253 OSErr err = 1254 ls_set_application_information_item_func(magic_session_constant, asn, 1255 ls_display_name_key, 1256 process_name, 1257 NULL /* optional out param */); 1258 if(err != noErr) 1259 fprint(2, "Call to set process name failed\n"); 1260 }