plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

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 }