In MacOS 10.8, the NSBitmapImageRep class appears to cache the specified image data at the time of construction. As a result updates to the backing memimage object do not get pushed to the screen in flushimg. This patch creates the NSBitmapImageRep object over again for each flushimg which would appear to fix the problem. R=rsc CC=plan9port.codebot http://codereview.appspot.com/6443063
1330 lines
25 KiB
Objective-C
1330 lines
25 KiB
Objective-C
/*
|
|
* Cocoa's event loop must be in main thread.
|
|
*
|
|
* Unless otherwise stated, all coordinate systems
|
|
* are bottom-left-based.
|
|
*/
|
|
|
|
#define Cursor OSXCursor
|
|
#define Point OSXPoint
|
|
#define Rect OSXRect
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#undef Cursor
|
|
#undef Point
|
|
#undef Rect
|
|
|
|
#include <u.h>
|
|
#include <libc.h>
|
|
#include "cocoa-thread.h"
|
|
#include <draw.h>
|
|
#include <memdraw.h>
|
|
#include <keyboard.h>
|
|
#include <cursor.h>
|
|
#include "cocoa-screen.h"
|
|
#include "osx-keycodes.h"
|
|
#include "devdraw.h"
|
|
#include "bigarrow.h"
|
|
#include "glendapng.h"
|
|
|
|
AUTOFRAMEWORK(Cocoa)
|
|
|
|
#define LOG if(0)NSLog
|
|
#define panic sysfatal
|
|
|
|
int usegestures = 0;
|
|
int useliveresizing = 0;
|
|
int useoldfullscreen = 0;
|
|
int usebigarrow = 0;
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: devdraw (don't run directly)\n");
|
|
threadexitsall("usage");
|
|
}
|
|
|
|
@interface appdelegate : NSObject @end
|
|
|
|
void
|
|
threadmain(int argc, char **argv)
|
|
{
|
|
/*
|
|
* Move the protocol off stdin/stdout so that
|
|
* any inadvertent prints don't screw things up.
|
|
*/
|
|
dup(0,3);
|
|
dup(1,4);
|
|
close(0);
|
|
close(1);
|
|
open("/dev/null", OREAD);
|
|
open("/dev/null", OWRITE);
|
|
|
|
ARGBEGIN{
|
|
case 'D': /* for good ps -a listings */
|
|
break;
|
|
case 'f':
|
|
useoldfullscreen = 1;
|
|
break;
|
|
case 'g':
|
|
usegestures = 1;
|
|
break;
|
|
case 'b':
|
|
usebigarrow = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
|
|
if(OSX_VERSION < 100700)
|
|
[NSAutoreleasePool new];
|
|
|
|
[NSApplication sharedApplication];
|
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
[NSApp setDelegate:[appdelegate new]];
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
[NSApp run];
|
|
}
|
|
|
|
#define WIN win.ofs[win.isofs]
|
|
|
|
struct
|
|
{
|
|
NSWindow *ofs[2]; /* ofs[1] for old fullscreen; ofs[0] else */
|
|
int isofs;
|
|
int isnfs;
|
|
NSView *content;
|
|
int needimg;
|
|
int deferflush;
|
|
NSCursor *cursor;
|
|
Memimage *memimage;
|
|
} win;
|
|
|
|
struct
|
|
{
|
|
NSCursor *bigarrow;
|
|
int kbuttons;
|
|
int mbuttons;
|
|
NSPoint mpos;
|
|
int mscroll;
|
|
int willactivate;
|
|
} in;
|
|
|
|
static void hidebars(int);
|
|
static void flushimg(NSRect);
|
|
static void autoflushwin(int);
|
|
static void flushwin(void);
|
|
static void followzoombutton(NSRect);
|
|
static void getmousepos(void);
|
|
static void makeicon(void);
|
|
static void makemenu(void);
|
|
static void makewin(char*);
|
|
static void sendmouse(void);
|
|
static void setcursor0(Cursor*);
|
|
static void togglefs(void);
|
|
static void acceptresizing(int);
|
|
|
|
static NSCursor* makecursor(Cursor*);
|
|
|
|
@implementation appdelegate
|
|
- (void)applicationDidFinishLaunching:(id)arg
|
|
{
|
|
in.bigarrow = makecursor(&bigarrow);
|
|
makeicon();
|
|
makemenu();
|
|
[NSApplication
|
|
detachDrawingThread:@selector(callservep9p:)
|
|
toTarget:[self class] withObject:nil];
|
|
}
|
|
- (void)windowDidBecomeKey:(id)arg
|
|
{
|
|
getmousepos();
|
|
sendmouse();
|
|
}
|
|
- (void)windowDidResize:(id)arg
|
|
{
|
|
getmousepos();
|
|
sendmouse();
|
|
}
|
|
- (void)windowWillStartLiveResize:(id)arg
|
|
{
|
|
if(useliveresizing == 0)
|
|
[win.content setHidden:YES];
|
|
}
|
|
- (void)windowDidEndLiveResize:(id)arg
|
|
{
|
|
if(useliveresizing == 0)
|
|
[win.content setHidden:NO];
|
|
}
|
|
- (void)windowDidChangeScreen:(id)arg
|
|
{
|
|
if(win.isnfs || win.isofs)
|
|
hidebars(1);
|
|
[win.ofs[1] setFrame:[[WIN screen] frame] display:YES];
|
|
}
|
|
- (BOOL)windowShouldZoom:(id)arg toFrame:(NSRect)r
|
|
{
|
|
followzoombutton(r);
|
|
return YES;
|
|
}
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg
|
|
{
|
|
return YES;
|
|
}
|
|
- (void)applicationDidBecomeActive:(id)arg{ in.willactivate = 0;}
|
|
- (void)windowWillEnterFullScreen:(id)arg{ acceptresizing(1);}
|
|
- (void)windowDidEnterFullScreen:(id)arg{ win.isnfs = 1; hidebars(1);}
|
|
- (void)windowWillExitFullScreen:(id)arg{ win.isnfs = 0; hidebars(0);}
|
|
- (void)windowDidExitFullScreen:(id)arg
|
|
{
|
|
NSButton *b;
|
|
|
|
b = [WIN standardWindowButton:NSWindowMiniaturizeButton];
|
|
|
|
if([b isEnabled] == 0){
|
|
[b setEnabled:YES];
|
|
hidebars(0);
|
|
}
|
|
}
|
|
- (void)windowWillClose:(id)arg
|
|
{
|
|
autoflushwin(0); /* can crash otherwise */
|
|
}
|
|
|
|
+ (void)callservep9p:(id)arg
|
|
{
|
|
servep9p();
|
|
[NSApp terminate:self];
|
|
}
|
|
- (void)plumbmanual:(id)arg
|
|
{
|
|
if(fork() != 0)
|
|
return;
|
|
execl("plumb", "plumb", "devdraw(1)", nil);
|
|
}
|
|
+ (void)callflushwin:(id)arg{ flushwin();}
|
|
- (void)calltogglefs:(id)arg{ togglefs();}
|
|
|
|
+ (void)callflushimg:(NSValue*)v{ flushimg([v rectValue]);}
|
|
+ (void)callmakewin:(NSValue*)v{ makewin([v pointerValue]);}
|
|
+ (void)callsetcursor0:(NSValue*)v{ setcursor0([v pointerValue]);}
|
|
@end
|
|
|
|
static Memimage* initimg(void);
|
|
|
|
Memimage*
|
|
attachscreen(char *label, char *winsize)
|
|
{
|
|
static int first = 1;
|
|
|
|
if(first)
|
|
first = 0;
|
|
else
|
|
panic("attachscreen called twice");
|
|
|
|
if(label == nil)
|
|
label = "gnot a label";
|
|
if(strcmp(label, "page") == 0)
|
|
useliveresizing = 1;
|
|
|
|
/*
|
|
* Create window in main thread, else no cursor
|
|
* change while resizing.
|
|
*/
|
|
[appdelegate
|
|
performSelectorOnMainThread:@selector(callmakewin:)
|
|
withObject:[NSValue valueWithPointer:winsize]
|
|
waitUntilDone:YES];
|
|
// makewin(winsize);
|
|
|
|
kicklabel(label);
|
|
return initimg();
|
|
}
|
|
|
|
@interface appwin : NSWindow @end
|
|
@interface contentview : NSView @end
|
|
|
|
@implementation appwin
|
|
- (NSTimeInterval)animationResizeTime:(NSRect)r
|
|
{
|
|
return 0;
|
|
}
|
|
- (BOOL)canBecomeKeyWindow
|
|
{
|
|
return YES; /* else no keyboard for old fullscreen */
|
|
}
|
|
- (void)makeKeyAndOrderFront:(id)arg
|
|
{
|
|
LOG(@"makeKeyAndOrderFront");
|
|
|
|
autoflushwin(1);
|
|
[win.content setHidden:NO];
|
|
[super makeKeyAndOrderFront:arg];
|
|
}
|
|
- (void)miniaturize:(id)arg
|
|
{
|
|
[super miniaturize:arg];
|
|
[NSApp hide:nil];
|
|
|
|
[win.content setHidden:YES];
|
|
autoflushwin(0);
|
|
}
|
|
- (void)deminiaturize:(id)arg
|
|
{
|
|
autoflushwin(1);
|
|
[win.content setHidden:NO];
|
|
[super deminiaturize:arg];
|
|
}
|
|
@end
|
|
|
|
double
|
|
min(double a, double b)
|
|
{
|
|
return a<b? a : b;
|
|
}
|
|
|
|
enum
|
|
{
|
|
Winstyle = NSTitledWindowMask
|
|
| NSClosableWindowMask
|
|
| NSMiniaturizableWindowMask
|
|
| NSResizableWindowMask
|
|
};
|
|
|
|
static void
|
|
makewin(char *s)
|
|
{
|
|
NSRect r, sr;
|
|
NSWindow *w;
|
|
Rectangle wr;
|
|
int i, set;
|
|
|
|
sr = [[NSScreen mainScreen] frame];
|
|
r = [[NSScreen mainScreen] visibleFrame];
|
|
|
|
if(s && *s){
|
|
if(parsewinsize(s, &wr, &set) < 0)
|
|
sysfatal("%r");
|
|
}else{
|
|
wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
|
|
set = 0;
|
|
}
|
|
|
|
r.origin.x = wr.min.x;
|
|
r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */
|
|
r.size.width = min(Dx(wr), r.size.width);
|
|
r.size.height = min(Dy(wr), r.size.height);
|
|
r = [NSWindow contentRectForFrameRect:r
|
|
styleMask:Winstyle];
|
|
|
|
w = [[appwin alloc]
|
|
initWithContentRect:r
|
|
styleMask:Winstyle
|
|
backing:NSBackingStoreBuffered defer:NO];
|
|
if(!set)
|
|
[w center];
|
|
#if OSX_VERSION >= 100700
|
|
[w setCollectionBehavior:
|
|
NSWindowCollectionBehaviorFullScreenPrimary];
|
|
#endif
|
|
[w setContentMinSize:NSMakeSize(128,128)];
|
|
|
|
win.ofs[0] = w;
|
|
win.ofs[1] = [[appwin alloc]
|
|
initWithContentRect:sr
|
|
styleMask:NSBorderlessWindowMask
|
|
backing:NSBackingStoreBuffered defer:YES];
|
|
for(i=0; i<2; i++){
|
|
[win.ofs[i] setAcceptsMouseMovedEvents:YES];
|
|
[win.ofs[i] setDelegate:[NSApp delegate]];
|
|
[win.ofs[i] setDisplaysWhenScreenProfileChanges:NO];
|
|
}
|
|
win.isofs = 0;
|
|
win.content = [contentview new];
|
|
[WIN setContentView:win.content];
|
|
}
|
|
|
|
static Memimage*
|
|
initimg(void)
|
|
{
|
|
NSSize size;
|
|
Rectangle r;
|
|
|
|
size = [win.content bounds].size;
|
|
LOG(@"initimg %.0f %.0f", size.width, size.height);
|
|
|
|
r = Rect(0, 0, size.width, size.height);
|
|
win.memimage = allocmemimage(r, XBGR32);
|
|
if(win.memimage == nil)
|
|
panic("allocmemimage: %r");
|
|
if(win.memimage->data == nil)
|
|
panic("i->data == nil");
|
|
|
|
return win.memimage;
|
|
}
|
|
|
|
static void
|
|
resizeimg()
|
|
{
|
|
_drawreplacescreenimage(initimg());
|
|
mouseresized = 1;
|
|
sendmouse();
|
|
}
|
|
|
|
static void
|
|
waitimg(int msec)
|
|
{
|
|
NSDate *limit;
|
|
int n;
|
|
|
|
win.needimg = 1;
|
|
win.deferflush = 0;
|
|
|
|
n = 0;
|
|
limit = [NSDate dateWithTimeIntervalSinceNow:msec/1000.0];
|
|
do{
|
|
[[NSRunLoop currentRunLoop]
|
|
runMode:@"waiting image"
|
|
beforeDate:limit];
|
|
n++;
|
|
}while(win.needimg && [(NSDate*)[NSDate date] compare:limit]<0);
|
|
|
|
win.deferflush = win.needimg;
|
|
|
|
LOG(@"waitimg %s (%d loop)", win.needimg?"defer":"ok", n);
|
|
}
|
|
|
|
void
|
|
_flushmemscreen(Rectangle r)
|
|
{
|
|
static int n;
|
|
NSRect rect;
|
|
|
|
LOG(@"_flushmemscreen");
|
|
|
|
if(n==0){
|
|
n++;
|
|
return; /* to skip useless white init rect */
|
|
}else
|
|
if(n==1){
|
|
[WIN performSelectorOnMainThread:
|
|
@selector(makeKeyAndOrderFront:)
|
|
withObject:nil
|
|
waitUntilDone:NO];
|
|
n++;
|
|
}else
|
|
if([win.content canDraw] == 0)
|
|
return;
|
|
|
|
rect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
|
|
[appdelegate
|
|
performSelectorOnMainThread:@selector(callflushimg:)
|
|
withObject:[NSValue valueWithRect:rect]
|
|
waitUntilDone:YES
|
|
modes:[NSArray arrayWithObjects:
|
|
NSRunLoopCommonModes,
|
|
@"waiting image", nil]];
|
|
}
|
|
|
|
static void drawimg(NSBitmapImageRep*, NSRect, uint);
|
|
static void drawresizehandle(void);
|
|
|
|
enum
|
|
{
|
|
Pixel = 1,
|
|
Barsize = 4*Pixel,
|
|
Cornersize = 3*Pixel,
|
|
Handlesize = 3*Barsize + 1*Pixel,
|
|
};
|
|
|
|
static void
|
|
flushimg(NSRect rect)
|
|
{
|
|
NSRect dr, r;
|
|
NSBitmapImageRep *image;
|
|
Rectangle rrect;
|
|
NSSize size;
|
|
|
|
if([win.content lockFocusIfCanDraw] == 0)
|
|
return;
|
|
|
|
if(win.needimg){
|
|
size.width = win.memimage->r.max.x - win.memimage->r.min.x;
|
|
size.height = win.memimage->r.max.y - win.memimage->r.min.y;
|
|
if(!NSEqualSizes(rect.size, size)){
|
|
LOG(@"flushimg reject %.0f %.0f",
|
|
rect.size.width, rect.size.height);
|
|
[win.content unlockFocus];
|
|
return;
|
|
}
|
|
win.needimg = 0;
|
|
}else
|
|
win.deferflush = 1;
|
|
|
|
LOG(@"flushimg ok %.0f %.0f", rect.size.width, rect.size.height);
|
|
|
|
|
|
size = [win.content bounds].size;
|
|
LOG(@"initimg %.0f %.0f", size.width, size.height);
|
|
rrect = Rect(0, 0, size.width, size.height);
|
|
|
|
// FIXME: It is possible that we could do a smaller pixel copy here.
|
|
image = [[NSBitmapImageRep alloc]
|
|
initWithBitmapDataPlanes:&win.memimage->data->bdata
|
|
pixelsWide:Dx(rrect)
|
|
pixelsHigh:Dy(rrect)
|
|
bitsPerSample:8
|
|
samplesPerPixel:3
|
|
hasAlpha:NO
|
|
isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bytesPerRow:bytesperline(rrect, 32)
|
|
bitsPerPixel:32];
|
|
|
|
/*
|
|
* Unless we are inside "drawRect", we have to round
|
|
* the corners ourselves, if this is the custom.
|
|
* "NSCompositeSourceIn" can do that, but we don't
|
|
* apply it to the whole rectangle, because this
|
|
* slows down trackpad scrolling considerably in
|
|
* Acme.
|
|
*/
|
|
r = [win.content bounds];
|
|
r.size.height -= Cornersize;
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(image, dr, NSCompositeCopy);
|
|
|
|
r.origin.y = r.size.height;
|
|
r.size = NSMakeSize(Cornersize, Cornersize);
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(image, dr, NSCompositeSourceIn);
|
|
|
|
r.origin.x = size.width - Cornersize;
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(image, dr, NSCompositeSourceIn);
|
|
|
|
r.size.width = r.origin.x - Cornersize;
|
|
r.origin.x -= r.size.width;
|
|
dr = NSIntersectionRect(r, rect);
|
|
drawimg(image, dr, NSCompositeCopy);
|
|
|
|
if(OSX_VERSION<100700 && win.isofs==0){
|
|
r.origin.x = size.width - Handlesize;
|
|
r.origin.y = size.height - Handlesize;
|
|
r.size = NSMakeSize(Handlesize, Handlesize);
|
|
if(NSIntersectsRect(r, rect))
|
|
drawresizehandle();
|
|
}
|
|
[win.content unlockFocus];
|
|
}
|
|
|
|
static void
|
|
autoflushwin(int set)
|
|
{
|
|
static NSTimer *t;
|
|
|
|
if(set){
|
|
if(t)
|
|
return;
|
|
/*
|
|
* We need "NSRunLoopCommonModes", otherwise the
|
|
* timer will not fire during live resizing.
|
|
*/
|
|
t = [NSTimer
|
|
timerWithTimeInterval:0.033
|
|
target:[appdelegate class]
|
|
selector:@selector(callflushwin:) userInfo:nil
|
|
repeats:YES];
|
|
[[NSRunLoop currentRunLoop] addTimer:t
|
|
forMode:NSRunLoopCommonModes];
|
|
}else{
|
|
[t invalidate];
|
|
t = nil;
|
|
win.deferflush = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
flushwin(void)
|
|
{
|
|
if(win.deferflush && win.needimg==0){
|
|
[WIN flushWindow];
|
|
win.deferflush = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
drawimg(NSBitmapImageRep* image, NSRect dr, uint op)
|
|
{
|
|
NSRect sr;
|
|
|
|
if(NSIsEmptyRect(dr))
|
|
return;
|
|
|
|
sr = [win.content convertRect:dr fromView:nil];
|
|
|
|
[image drawInRect:dr fromRect:sr
|
|
operation:op fraction:1
|
|
respectFlipped:YES hints:nil];
|
|
|
|
// NSFrameRect(dr);
|
|
}
|
|
|
|
static void
|
|
drawresizehandle(void)
|
|
{
|
|
NSColor *color[Barsize];
|
|
NSPoint a,b;
|
|
Point c;
|
|
int i,j;
|
|
|
|
c = Pt(win.memimage->r.max.x - win.memimage->r.min.x, win.memimage->r.max.y - win.memimage->r.min.y);
|
|
|
|
[[WIN graphicsContext] setShouldAntialias:NO];
|
|
|
|
color[0] = [NSColor clearColor];
|
|
color[1] = [NSColor darkGrayColor];
|
|
color[2] = [NSColor lightGrayColor];
|
|
color[3] = [NSColor whiteColor];
|
|
|
|
for(i=1; i+Barsize <= Handlesize; )
|
|
for(j=0; j<Barsize; j++){
|
|
[color[j] setStroke];
|
|
i++;
|
|
a = NSMakePoint(c.x-i, c.y-1);
|
|
b = NSMakePoint(c.x-2, c.y+1-i);
|
|
[NSBezierPath strokeLineFromPoint:a toPoint:b];
|
|
}
|
|
}
|
|
|
|
static void getgesture(NSEvent*);
|
|
static void getkeyboard(NSEvent*);
|
|
static void getmouse(NSEvent*);
|
|
static void gettouch(NSEvent*, int);
|
|
static void updatecursor(void);
|
|
|
|
@implementation contentview
|
|
/*
|
|
* "drawRect" is called each time Cocoa needs an
|
|
* image, and each time we call "display". It is
|
|
* preceded by background painting, and followed by
|
|
* "flushWindow".
|
|
*/
|
|
- (void)drawRect:(NSRect)r
|
|
{
|
|
static int first = 1;
|
|
|
|
LOG(@"drawrect %.0f %.0f %.0f %.0f",
|
|
r.origin.x, r.origin.y, r.size.width, r.size.height);
|
|
|
|
if(first)
|
|
first = 0;
|
|
else
|
|
resizeimg();
|
|
|
|
if([WIN inLiveResize])
|
|
waitimg(100);
|
|
else
|
|
waitimg(500);
|
|
}
|
|
- (BOOL)isFlipped
|
|
{
|
|
return YES; /* to make the content's origin top left */
|
|
}
|
|
- (BOOL)acceptsFirstResponder
|
|
{
|
|
return YES; /* else no keyboard */
|
|
}
|
|
- (id)initWithFrame:(NSRect)r
|
|
{
|
|
[super initWithFrame:r];
|
|
[self setAcceptsTouchEvents:YES];
|
|
[self setHidden:YES]; /* to avoid early "drawRect" call */
|
|
return self;
|
|
}
|
|
- (void)setHidden:(BOOL)set
|
|
{
|
|
if(!set)
|
|
[WIN makeFirstResponder:self]; /* for keyboard focus */
|
|
[super setHidden:set];
|
|
}
|
|
- (void)cursorUpdate:(NSEvent*)e{ updatecursor();}
|
|
|
|
- (void)mouseMoved:(NSEvent*)e{ getmouse(e);}
|
|
- (void)mouseDown:(NSEvent*)e{ getmouse(e);}
|
|
- (void)mouseDragged:(NSEvent*)e{ getmouse(e);}
|
|
- (void)mouseUp:(NSEvent*)e{ getmouse(e);}
|
|
- (void)otherMouseDown:(NSEvent*)e{ getmouse(e);}
|
|
- (void)otherMouseDragged:(NSEvent*)e{ getmouse(e);}
|
|
- (void)otherMouseUp:(NSEvent*)e{ getmouse(e);}
|
|
- (void)rightMouseDown:(NSEvent*)e{ getmouse(e);}
|
|
- (void)rightMouseDragged:(NSEvent*)e{ getmouse(e);}
|
|
- (void)rightMouseUp:(NSEvent*)e{ getmouse(e);}
|
|
- (void)scrollWheel:(NSEvent*)e{ getmouse(e);}
|
|
|
|
- (void)keyDown:(NSEvent*)e{ getkeyboard(e);}
|
|
- (void)flagsChanged:(NSEvent*)e{ getkeyboard(e);}
|
|
|
|
- (void)magnifyWithEvent:(NSEvent*)e{ getgesture(e);}
|
|
|
|
- (void)touchesBeganWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseBegan);
|
|
}
|
|
- (void)touchesMovedWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseMoved);
|
|
}
|
|
- (void)touchesEndedWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseEnded);
|
|
}
|
|
- (void)touchesCancelledWithEvent:(NSEvent*)e
|
|
{
|
|
gettouch(e, NSTouchPhaseCancelled);
|
|
}
|
|
@end
|
|
|
|
static int keycvt[] =
|
|
{
|
|
[QZ_IBOOK_ENTER] '\n',
|
|
[QZ_RETURN] '\n',
|
|
[QZ_ESCAPE] 27,
|
|
[QZ_BACKSPACE] '\b',
|
|
[QZ_LALT] Kalt,
|
|
[QZ_LCTRL] Kctl,
|
|
[QZ_LSHIFT] Kshift,
|
|
[QZ_F1] KF+1,
|
|
[QZ_F2] KF+2,
|
|
[QZ_F3] KF+3,
|
|
[QZ_F4] KF+4,
|
|
[QZ_F5] KF+5,
|
|
[QZ_F6] KF+6,
|
|
[QZ_F7] KF+7,
|
|
[QZ_F8] KF+8,
|
|
[QZ_F9] KF+9,
|
|
[QZ_F10] KF+10,
|
|
[QZ_F11] KF+11,
|
|
[QZ_F12] KF+12,
|
|
[QZ_INSERT] Kins,
|
|
[QZ_DELETE] 0x7F,
|
|
[QZ_HOME] Khome,
|
|
[QZ_END] Kend,
|
|
[QZ_KP_PLUS] '+',
|
|
[QZ_KP_MINUS] '-',
|
|
[QZ_TAB] '\t',
|
|
[QZ_PAGEUP] Kpgup,
|
|
[QZ_PAGEDOWN] Kpgdown,
|
|
[QZ_UP] Kup,
|
|
[QZ_DOWN] Kdown,
|
|
[QZ_LEFT] Kleft,
|
|
[QZ_RIGHT] Kright,
|
|
[QZ_KP_MULTIPLY] '*',
|
|
[QZ_KP_DIVIDE] '/',
|
|
[QZ_KP_ENTER] '\n',
|
|
[QZ_KP_PERIOD] '.',
|
|
[QZ_KP0] '0',
|
|
[QZ_KP1] '1',
|
|
[QZ_KP2] '2',
|
|
[QZ_KP3] '3',
|
|
[QZ_KP4] '4',
|
|
[QZ_KP5] '5',
|
|
[QZ_KP6] '6',
|
|
[QZ_KP7] '7',
|
|
[QZ_KP8] '8',
|
|
[QZ_KP9] '9',
|
|
};
|
|
|
|
@interface apptext : NSTextView @end
|
|
|
|
@implementation apptext
|
|
- (void)doCommandBySelector:(SEL)s{} /* Esc key beeps otherwise */
|
|
- (void)insertText:(id)arg{} /* to avoid a latency after some time */
|
|
@end
|
|
|
|
static void
|
|
interpretdeadkey(NSEvent *e)
|
|
{
|
|
static apptext *t;
|
|
|
|
if(t == nil)
|
|
t = [apptext new];
|
|
[t interpretKeyEvents:[NSArray arrayWithObject:e]];
|
|
}
|
|
|
|
static void
|
|
getkeyboard(NSEvent *e)
|
|
{
|
|
static int omod;
|
|
NSString *s;
|
|
char c;
|
|
int k, m;
|
|
uint code;
|
|
|
|
m = [e modifierFlags];
|
|
|
|
switch([e type]){
|
|
case NSKeyDown:
|
|
s = [e characters];
|
|
c = [s UTF8String][0];
|
|
|
|
interpretdeadkey(e);
|
|
|
|
if(m & NSCommandKeyMask){
|
|
if(' '<=c && c<='~')
|
|
keystroke(Kcmd+c);
|
|
break;
|
|
}
|
|
k = c;
|
|
code = [e keyCode];
|
|
if(code<nelem(keycvt) && keycvt[code])
|
|
k = keycvt[code];
|
|
if(k==0)
|
|
break;
|
|
if(k>0)
|
|
keystroke(k);
|
|
else
|
|
keystroke([s characterAtIndex:0]);
|
|
break;
|
|
|
|
case NSFlagsChanged:
|
|
if(in.mbuttons || in.kbuttons){
|
|
in.kbuttons = 0;
|
|
if(m & NSAlternateKeyMask)
|
|
in.kbuttons |= 2;
|
|
if(m & NSCommandKeyMask)
|
|
in.kbuttons |= 4;
|
|
sendmouse();
|
|
}else
|
|
if(m&NSAlternateKeyMask && (omod&NSAlternateKeyMask)==0)
|
|
keystroke(Kalt);
|
|
break;
|
|
|
|
default:
|
|
panic("getkey: unexpected event type");
|
|
}
|
|
omod = m;
|
|
}
|
|
|
|
/*
|
|
* Devdraw does not use NSTrackingArea, that often
|
|
* forgets to update the cursor on entering and on
|
|
* leaving the area, and that sometimes stops sending
|
|
* us MouseMove events, at least on OS X Lion.
|
|
*/
|
|
static void
|
|
updatecursor(void)
|
|
{
|
|
NSCursor *c;
|
|
int isdown, isinside;
|
|
|
|
isinside = NSPointInRect(in.mpos, [win.content bounds]);
|
|
isdown = (in.mbuttons || in.kbuttons);
|
|
|
|
if(win.cursor && (isinside || isdown))
|
|
c = win.cursor;
|
|
else if(isinside && usebigarrow)
|
|
c = in.bigarrow;
|
|
else
|
|
c = [NSCursor arrowCursor];
|
|
[c set];
|
|
|
|
/*
|
|
* Without this trick, we can come back from the dock
|
|
* with a resize cursor.
|
|
*/
|
|
if(OSX_VERSION >= 100700)
|
|
[NSCursor unhide];
|
|
}
|
|
|
|
static void
|
|
acceptresizing(int set)
|
|
{
|
|
uint old, style;
|
|
|
|
old = [WIN styleMask];
|
|
|
|
if((old | NSResizableWindowMask) != Winstyle)
|
|
return; /* when entering new fullscreen */
|
|
|
|
if(set)
|
|
style = Winstyle;
|
|
else
|
|
style = Winstyle & ~NSResizableWindowMask;
|
|
|
|
if(style != old)
|
|
[WIN setStyleMask:style];
|
|
}
|
|
|
|
static void
|
|
getmousepos(void)
|
|
{
|
|
NSPoint p, q;
|
|
|
|
p = [WIN mouseLocationOutsideOfEventStream];
|
|
q = [win.content convertPoint:p fromView:nil];
|
|
in.mpos.x = round(q.x);
|
|
in.mpos.y = round(q.y);
|
|
|
|
updatecursor();
|
|
|
|
if(win.isnfs || win.isofs)
|
|
hidebars(1);
|
|
else if(OSX_VERSION>=100700 && [WIN inLiveResize]==0){
|
|
if(p.x<12 && p.y<12 && p.x>2 && p.y>2)
|
|
acceptresizing(0);
|
|
else
|
|
acceptresizing(1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
getmouse(NSEvent *e)
|
|
{
|
|
float d;
|
|
int b, m;
|
|
|
|
if([WIN isKeyWindow] == 0)
|
|
return;
|
|
|
|
getmousepos();
|
|
|
|
switch([e type]){
|
|
case NSLeftMouseDown:
|
|
case NSLeftMouseUp:
|
|
case NSOtherMouseDown:
|
|
case NSOtherMouseUp:
|
|
case NSRightMouseDown:
|
|
case NSRightMouseUp:
|
|
b = [NSEvent pressedMouseButtons];
|
|
b = b&~6 | (b&4)>>1 | (b&2)<<1;
|
|
b = mouseswap(b);
|
|
|
|
if(b == 1){
|
|
m = [e modifierFlags];
|
|
if(m & NSAlternateKeyMask){
|
|
abortcompose();
|
|
b = 2;
|
|
}else
|
|
if(m & NSCommandKeyMask)
|
|
b = 4;
|
|
}
|
|
in.mbuttons = b;
|
|
break;
|
|
|
|
case NSScrollWheel:
|
|
#if OSX_VERSION >= 100700
|
|
d = [e scrollingDeltaY];
|
|
#else
|
|
d = [e deltaY];
|
|
#endif
|
|
if(d>0)
|
|
in.mscroll = 8;
|
|
else
|
|
if(d<0)
|
|
in.mscroll = 16;
|
|
break;
|
|
|
|
case NSMouseMoved:
|
|
case NSLeftMouseDragged:
|
|
case NSRightMouseDragged:
|
|
case NSOtherMouseDragged:
|
|
break;
|
|
|
|
default:
|
|
panic("getmouse: unexpected event type");
|
|
}
|
|
sendmouse();
|
|
}
|
|
|
|
#define Minpinch 0.02
|
|
|
|
static void
|
|
getgesture(NSEvent *e)
|
|
{
|
|
switch([e type]){
|
|
case NSEventTypeMagnify:
|
|
if(fabs([e magnification]) > Minpinch)
|
|
togglefs();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void sendclick(int);
|
|
|
|
static uint
|
|
msec(void)
|
|
{
|
|
return nsec()/1000000;
|
|
}
|
|
|
|
static void
|
|
gettouch(NSEvent *e, int type)
|
|
{
|
|
static int tapping;
|
|
static uint taptime;
|
|
NSSet *set;
|
|
int p;
|
|
|
|
switch(type){
|
|
case NSTouchPhaseBegan:
|
|
p = NSTouchPhaseTouching;
|
|
set = [e touchesMatchingPhase:p inView:nil];
|
|
if(set.count == 3){
|
|
tapping = 1;
|
|
taptime = msec();
|
|
}else
|
|
if(set.count > 3)
|
|
tapping = 0;
|
|
break;
|
|
|
|
case NSTouchPhaseMoved:
|
|
tapping = 0;
|
|
break;
|
|
|
|
case NSTouchPhaseEnded:
|
|
p = NSTouchPhaseTouching;
|
|
set = [e touchesMatchingPhase:p inView:nil];
|
|
if(set.count == 0){
|
|
if(tapping && msec()-taptime<400)
|
|
sendclick(2);
|
|
tapping = 0;
|
|
}
|
|
break;
|
|
|
|
case NSTouchPhaseCancelled:
|
|
break;
|
|
|
|
default:
|
|
panic("gettouch: unexpected event type");
|
|
}
|
|
}
|
|
|
|
static void
|
|
sendclick(int b)
|
|
{
|
|
in.mbuttons = b;
|
|
sendmouse();
|
|
in.mbuttons = 0;
|
|
sendmouse();
|
|
}
|
|
|
|
static void
|
|
sendmouse(void)
|
|
{
|
|
NSSize size;
|
|
int b;
|
|
|
|
size = [win.content bounds].size;
|
|
mouserect = Rect(0, 0, size.width, size.height);
|
|
|
|
b = in.kbuttons | in.mbuttons | in.mscroll;
|
|
mousetrack(in.mpos.x, in.mpos.y, b, msec());
|
|
in.mscroll = 0;
|
|
}
|
|
|
|
void
|
|
setmouse(Point p)
|
|
{
|
|
static int first = 1;
|
|
NSPoint q;
|
|
NSRect r;
|
|
|
|
if([NSApp isActive]==0 && in.willactivate==0)
|
|
return;
|
|
|
|
if(first){
|
|
/* Try to move Acme's scrollbars without that! */
|
|
CGSetLocalEventsSuppressionInterval(0);
|
|
first = 0;
|
|
}
|
|
if([WIN inLiveResize])
|
|
return;
|
|
|
|
in.mpos = NSMakePoint(p.x, p.y); // race condition
|
|
|
|
q = [win.content convertPoint:in.mpos toView:nil];
|
|
q = [WIN convertBaseToScreen:q];
|
|
|
|
r = [[[NSScreen screens] objectAtIndex:0] frame];
|
|
q.y = r.size.height - q.y; /* Quartz is top-left-based here */
|
|
|
|
CGWarpMouseCursorPosition(NSPointToCGPoint(q));
|
|
}
|
|
|
|
static void
|
|
followzoombutton(NSRect r)
|
|
{
|
|
NSRect wr;
|
|
Point p;
|
|
|
|
wr = [WIN frame];
|
|
wr.origin.y += wr.size.height;
|
|
r.origin.y += r.size.height;
|
|
|
|
getmousepos();
|
|
p.x = (r.origin.x - wr.origin.x) + in.mpos.x;
|
|
p.y = -(r.origin.y - wr.origin.y) + in.mpos.y;
|
|
setmouse(p);
|
|
}
|
|
|
|
static void
|
|
togglefs(void)
|
|
{
|
|
uint opt, tmp;
|
|
|
|
#if OSX_VERSION >= 100700
|
|
NSScreen *s, *s0;
|
|
|
|
s = [WIN screen];
|
|
s0 = [[NSScreen screens] objectAtIndex:0];
|
|
|
|
if((s==s0 && useoldfullscreen==0) || win.isnfs) {
|
|
[WIN toggleFullScreen:nil];
|
|
return;
|
|
}
|
|
#endif
|
|
[win.content retain];
|
|
[WIN orderOut:nil];
|
|
[WIN setContentView:nil];
|
|
|
|
win.isofs = ! win.isofs;
|
|
hidebars(win.isofs);
|
|
|
|
/*
|
|
* If we move the window from one space to another,
|
|
* ofs[0] and ofs[1] can be on different spaces.
|
|
* This "setCollectionBehavior" trick moves the
|
|
* window to the active space.
|
|
*/
|
|
opt = [WIN collectionBehavior];
|
|
tmp = opt | NSWindowCollectionBehaviorCanJoinAllSpaces;
|
|
[WIN setContentView:win.content];
|
|
[WIN setCollectionBehavior:tmp];
|
|
[WIN makeKeyAndOrderFront:nil];
|
|
[WIN setCollectionBehavior:opt];
|
|
[win.content release];
|
|
}
|
|
|
|
enum
|
|
{
|
|
Autohiddenbars = NSApplicationPresentationAutoHideDock
|
|
| NSApplicationPresentationAutoHideMenuBar,
|
|
|
|
Hiddenbars = NSApplicationPresentationHideDock
|
|
| NSApplicationPresentationHideMenuBar,
|
|
};
|
|
|
|
static void
|
|
hidebars(int set)
|
|
{
|
|
NSScreen *s,*s0;
|
|
uint old, opt;
|
|
|
|
s = [WIN screen];
|
|
s0 = [[NSScreen screens] objectAtIndex:0];
|
|
old = [NSApp presentationOptions];
|
|
|
|
#if OSX_VERSION >= 100700
|
|
/* This bit can get lost, resulting in dreadful bugs. */
|
|
if(win.isnfs)
|
|
old |= NSApplicationPresentationFullScreen;
|
|
#endif
|
|
|
|
if(set && s==s0)
|
|
opt = (old & ~Autohiddenbars) | Hiddenbars;
|
|
else
|
|
opt = old & ~(Autohiddenbars | Hiddenbars);
|
|
|
|
if(opt != old)
|
|
[NSApp setPresentationOptions:opt];
|
|
}
|
|
|
|
static void
|
|
makemenu(void)
|
|
{
|
|
NSMenu *m;
|
|
NSMenuItem *i0,*i1;
|
|
|
|
m = [NSMenu new];
|
|
i0 = [m addItemWithTitle:@"app" action:NULL keyEquivalent:@""];
|
|
i1 = [m addItemWithTitle:@"help" action:NULL keyEquivalent:@""];
|
|
[NSApp setMainMenu:m];
|
|
[m release];
|
|
|
|
m = [[NSMenu alloc] initWithTitle:@"app"];
|
|
[m addItemWithTitle:@"Full Screen"
|
|
action:@selector(calltogglefs:)
|
|
keyEquivalent:@"f"];
|
|
[m addItemWithTitle:@"Hide"
|
|
action:@selector(hide:)
|
|
keyEquivalent:@"h"];
|
|
[m addItemWithTitle:@"Quit"
|
|
action:@selector(terminate:)
|
|
keyEquivalent:@"q"];
|
|
[i0 setSubmenu:m];
|
|
[m release];
|
|
|
|
m = [[NSMenu alloc] initWithTitle:@"help"];
|
|
[m addItemWithTitle:@"Plumb devdraw(1)"
|
|
action:@selector(plumbmanual:)
|
|
keyEquivalent:@""];
|
|
[i1 setSubmenu:m];
|
|
[m release];
|
|
}
|
|
|
|
static void
|
|
makeicon(void)
|
|
{
|
|
NSData *d;
|
|
NSImage *i;
|
|
|
|
d = [[NSData alloc]
|
|
initWithBytes:glenda_png
|
|
length:(sizeof glenda_png)];
|
|
|
|
i = [[NSImage alloc] initWithData:d];
|
|
[NSApp setApplicationIconImage:i];
|
|
[[NSApp dockTile] display];
|
|
[i release];
|
|
[d release];
|
|
}
|
|
|
|
QLock snarfl;
|
|
|
|
char*
|
|
getsnarf(void)
|
|
{
|
|
NSPasteboard *pb;
|
|
NSString *s;
|
|
|
|
pb = [NSPasteboard generalPasteboard];
|
|
|
|
qlock(&snarfl);
|
|
s = [pb stringForType:NSPasteboardTypeString];
|
|
qunlock(&snarfl);
|
|
|
|
if(s)
|
|
return strdup((char*)[s UTF8String]);
|
|
else
|
|
return nil;
|
|
}
|
|
|
|
void
|
|
putsnarf(char *s)
|
|
{
|
|
NSArray *t;
|
|
NSPasteboard *pb;
|
|
NSString *str;
|
|
|
|
if(strlen(s) >= SnarfSize)
|
|
return;
|
|
|
|
t = [NSArray arrayWithObject:NSPasteboardTypeString];
|
|
pb = [NSPasteboard generalPasteboard];
|
|
str = [[NSString alloc] initWithUTF8String:s];
|
|
|
|
qlock(&snarfl);
|
|
[pb declareTypes:t owner:nil];
|
|
[pb setString:str forType:NSPasteboardTypeString];
|
|
qunlock(&snarfl);
|
|
|
|
[str release];
|
|
}
|
|
|
|
void
|
|
kicklabel(char *label)
|
|
{
|
|
NSString *s;
|
|
|
|
if(label == nil)
|
|
return;
|
|
|
|
s = [[NSString alloc] initWithUTF8String:label];
|
|
[win.ofs[0] setTitle:s];
|
|
[win.ofs[1] setTitle:s];
|
|
[[NSApp dockTile] setBadgeLabel:s];
|
|
[s release];
|
|
}
|
|
|
|
void
|
|
setcursor(Cursor *c)
|
|
{
|
|
/*
|
|
* No cursor change unless in main thread.
|
|
*/
|
|
[appdelegate
|
|
performSelectorOnMainThread:@selector(callsetcursor0:)
|
|
withObject:[NSValue valueWithPointer:c]
|
|
waitUntilDone:YES];
|
|
}
|
|
|
|
static void
|
|
setcursor0(Cursor *c)
|
|
{
|
|
NSCursor *d;
|
|
|
|
d = win.cursor;
|
|
|
|
if(c)
|
|
win.cursor = makecursor(c);
|
|
else
|
|
win.cursor = nil;
|
|
|
|
updatecursor();
|
|
|
|
if(d)
|
|
[d release];
|
|
}
|
|
|
|
static NSCursor*
|
|
makecursor(Cursor *c)
|
|
{
|
|
NSBitmapImageRep *r;
|
|
NSCursor *d;
|
|
NSImage *i;
|
|
NSPoint p;
|
|
int b;
|
|
uchar *plane[5];
|
|
|
|
r = [[NSBitmapImageRep alloc]
|
|
initWithBitmapDataPlanes:nil
|
|
pixelsWide:16
|
|
pixelsHigh:16
|
|
bitsPerSample:1
|
|
samplesPerPixel:2
|
|
hasAlpha:YES
|
|
isPlanar:YES
|
|
colorSpaceName:NSDeviceBlackColorSpace
|
|
bytesPerRow:2
|
|
bitsPerPixel:1];
|
|
|
|
[r getBitmapDataPlanes:plane];
|
|
|
|
for(b=0; b<2*16; b++){
|
|
plane[0][b] = c->set[b];
|
|
plane[1][b] = c->clr[b];
|
|
}
|
|
p = NSMakePoint(-c->offset.x, -c->offset.y);
|
|
i = [NSImage new];
|
|
[i addRepresentation:r];
|
|
[r release];
|
|
|
|
d = [[NSCursor alloc] initWithImage:i hotSpot:p];
|
|
[i release];
|
|
return d;
|
|
}
|
|
|
|
void
|
|
topwin(void)
|
|
{
|
|
[WIN performSelectorOnMainThread:
|
|
@selector(makeKeyAndOrderFront:)
|
|
withObject:nil
|
|
waitUntilDone:NO];
|
|
|
|
in.willactivate = 1;
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
}
|