diff options
Diffstat (limited to 'src/nsterm.m')
-rw-r--r-- | src/nsterm.m | 1687 |
1 files changed, 732 insertions, 955 deletions
diff --git a/src/nsterm.m b/src/nsterm.m index 1b2328628ee..ba5d81fb6cd 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -70,9 +70,6 @@ GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu) #ifdef NS_IMPL_COCOA #include "macfont.h" #include <Carbon/Carbon.h> -#endif - -#ifdef NS_DRAW_TO_BUFFER #include <IOSurface/IOSurface.h> #endif @@ -272,16 +269,11 @@ long context_menu_value = 0; /* display update */ static struct frame *ns_updating_frame; -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 -static NSView *focus_view = NULL; -#endif static int ns_window_num = 0; static BOOL gsaved = NO; -static BOOL ns_fake_keydown = NO; #ifdef NS_IMPL_COCOA static BOOL ns_menu_bar_is_hidden = NO; #endif -/* static int debug_lock = 0; */ /* event loop */ static BOOL send_appdefined = YES; @@ -499,118 +491,37 @@ append2 (Lisp_Object list, Lisp_Object item) const char * -ns_etc_directory (void) -/* If running as a self-contained app bundle, return as a string the - filename of the etc directory, if present; else nil. */ -{ - NSBundle *bundle = [NSBundle mainBundle]; - NSString *resourceDir = [bundle resourcePath]; - NSString *resourcePath; - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDir; - - resourcePath = [resourceDir stringByAppendingPathComponent: @"etc"]; - if ([fileManager fileExistsAtPath: resourcePath isDirectory: &isDir]) - { - if (isDir) return [resourcePath UTF8String]; - } - return NULL; -} +ns_relocate (const char *epath) +/* If we're running in a self-contained app bundle some hard-coded + paths are relative to the root of the bundle, so work out the full + path. - -const char * -ns_exec_path (void) -/* If running as a self-contained app bundle, return as a path string - the filenames of the libexec and bin directories, ie libexec:bin. - Otherwise, return nil. - Normally, Emacs does not add its own bin/ directory to the PATH. - However, a self-contained NS build has a different layout, with - bin/ and libexec/ subdirectories in the directory that contains - Emacs.app itself. - We put libexec first, because init_callproc_1 uses the first - element to initialize exec-directory. An alternative would be - for init_callproc to check for invocation-directory/libexec. -*/ + FIXME: I think this should be able to handle cases where multiple + directories are separated by colons. */ { +#ifdef NS_SELF_CONTAINED NSBundle *bundle = [NSBundle mainBundle]; - NSString *resourceDir = [bundle resourcePath]; - NSString *binDir = [bundle bundlePath]; - NSString *resourcePath, *resourcePaths; - NSRange range; - NSString *pathSeparator = [NSString stringWithFormat: @"%c", SEPCHAR]; + NSString *root = [bundle bundlePath]; + NSString *original = [NSString stringWithUTF8String:epath]; + NSString *fixedPath = [NSString pathWithComponents: + [NSArray arrayWithObjects: + root, original, nil]]; NSFileManager *fileManager = [NSFileManager defaultManager]; - NSArray *paths; - NSEnumerator *pathEnum; - BOOL isDir; - - range = [resourceDir rangeOfString: @"Contents"]; - if (range.location != NSNotFound) - { - binDir = [binDir stringByAppendingPathComponent: @"Contents"]; -#ifdef NS_IMPL_COCOA - binDir = [binDir stringByAppendingPathComponent: @"MacOS"]; -#endif - } - - paths = [binDir stringsByAppendingPaths: - [NSArray arrayWithObjects: @"libexec", @"bin", nil]]; - pathEnum = [paths objectEnumerator]; - resourcePaths = @""; - while ((resourcePath = [pathEnum nextObject])) - { - if ([fileManager fileExistsAtPath: resourcePath isDirectory: &isDir]) - if (isDir) - { - if ([resourcePaths length] > 0) - resourcePaths - = [resourcePaths stringByAppendingString: pathSeparator]; - resourcePaths - = [resourcePaths stringByAppendingString: resourcePath]; - } - } - if ([resourcePaths length] > 0) return [resourcePaths UTF8String]; - - return NULL; -} + if (![original isAbsolutePath] + && [fileManager fileExistsAtPath:fixedPath isDirectory:NULL]) + return [fixedPath UTF8String]; + /* If we reach here either the path is absolute and therefore we + don't need to complete it, or we're unable to relocate the + file/directory. If it's the latter it may be because the user is + trying to use a bundled app as though it's a Unix style install + and we have no way to guess what was intended, so return the + original string unaltered. */ -const char * -ns_load_path (void) -/* If running as a self-contained app bundle, return as a path string - the filenames of the site-lisp and lisp directories. - Ie, site-lisp:lisp. Otherwise, return nil. */ -{ - NSBundle *bundle = [NSBundle mainBundle]; - NSString *resourceDir = [bundle resourcePath]; - NSString *resourcePath, *resourcePaths; - NSString *pathSeparator = [NSString stringWithFormat: @"%c", SEPCHAR]; - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDir; - NSArray *paths = [resourceDir stringsByAppendingPaths: - [NSArray arrayWithObjects: - @"site-lisp", @"lisp", nil]]; - NSEnumerator *pathEnum = [paths objectEnumerator]; - resourcePaths = @""; - - /* Hack to skip site-lisp. */ - if (no_site_lisp) resourcePath = [pathEnum nextObject]; - - while ((resourcePath = [pathEnum nextObject])) - { - if ([fileManager fileExistsAtPath: resourcePath isDirectory: &isDir]) - if (isDir) - { - if ([resourcePaths length] > 0) - resourcePaths - = [resourcePaths stringByAppendingString: pathSeparator]; - resourcePaths - = [resourcePaths stringByAppendingString: resourcePath]; - } - } - if ([resourcePaths length] > 0) return [resourcePaths UTF8String]; +#endif - return NULL; + return epath; } @@ -857,6 +768,17 @@ ns_row_rect (struct window *w, struct glyph_row *row, } +double +ns_frame_scale_factor (struct frame *f) +{ +#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED > 1060 + return [[FRAME_NS_VIEW (f) window] backingScaleFactor]; +#else + return [[FRAME_NS_VIEW (f) window] userSpaceScaleFactor]; +#endif +} + + /* ========================================================================== Focus (clipping) and screen update @@ -1104,33 +1026,14 @@ ns_update_begin (struct frame *f) { // Fix reappearing tool bar in fullscreen for Mac OS X 10.7 BOOL tbar_visible = FRAME_EXTERNAL_TOOL_BAR (f) ? YES : NO; - NSToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar]; + NSToolbar *toolbar = [[FRAME_NS_VIEW (f) window] toolbar]; if (! tbar_visible != ! [toolbar isVisible]) [toolbar setVisible: tbar_visible]; } #endif ns_updating_frame = f; -#ifdef NS_DRAW_TO_BUFFER -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if ([FRAME_NS_VIEW (f) wantsUpdateLayer]) - { -#endif - [view focusOnDrawingBuffer]; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } - else - { -#endif -#endif /* NS_DRAW_TO_BUFFER */ - -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - [view lockFocus]; -#endif -#if defined (NS_DRAW_TO_BUFFER) && MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } -#endif - + [view lockFocus]; } @@ -1141,39 +1044,21 @@ ns_update_end (struct frame *f) external (RIF) call; for whole frame, called after gui_update_window_end -------------------------------------------------------------------------- */ { -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 EmacsView *view = FRAME_NS_VIEW (f); -#endif NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_end"); /* if (f == MOUSE_HL_INFO (f)->mouse_face_mouse_frame) */ MOUSE_HL_INFO (f)->mouse_face_defer = 0; -#ifdef NS_DRAW_TO_BUFFER -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if ([FRAME_NS_VIEW (f) wantsUpdateLayer]) - { -#endif - [FRAME_NS_VIEW (f) unfocusDrawingBuffer]; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } - else - { -#endif -#endif /* NS_DRAW_TO_BUFFER */ - -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - block_input (); - - [view unlockFocus]; - [[view window] flushWindow]; + block_input (); - unblock_input (); -#endif -#if defined (NS_DRAW_TO_BUFFER) && MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } + [view unlockFocus]; +#if defined (NS_IMPL_GNUSTEP) + [[view window] flushWindow]; #endif + + unblock_input (); ns_updating_frame = NULL; } @@ -1188,8 +1073,6 @@ ns_focus (struct frame *f, NSRect *r, int n) the entire window. -------------------------------------------------------------------------- */ { - EmacsView *view = FRAME_NS_VIEW (f); - NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_focus"); if (r != NULL) { @@ -1198,39 +1081,10 @@ ns_focus (struct frame *f, NSRect *r, int n) if (f != ns_updating_frame) { -#ifdef NS_DRAW_TO_BUFFER -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if ([FRAME_NS_VIEW (f) wantsUpdateLayer]) - { -#endif - [view focusOnDrawingBuffer]; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } - else - { -#endif -#endif /* NS_DRAW_TO_BUFFER */ - -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if (view != focus_view) - { - if (focus_view != NULL) - { - [focus_view unlockFocus]; - [[focus_view window] flushWindow]; - } - - if (view) - [view lockFocus]; - focus_view = view; - } -#endif -#if defined (NS_DRAW_TO_BUFFER) && MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } -#endif + EmacsView *view = FRAME_NS_VIEW (f); + [view lockFocus]; } - /* clipping */ if (r) { @@ -1258,35 +1112,14 @@ ns_unfocus (struct frame *f) gsaved = NO; } -#ifdef NS_DRAW_TO_BUFFER - #if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if ([FRAME_NS_VIEW (f) wantsUpdateLayer]) - { -#endif - if (! ns_updating_frame) - [FRAME_NS_VIEW (f) unfocusDrawingBuffer]; - [FRAME_NS_VIEW (f) setNeedsDisplay:YES]; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - } - else + if (f != ns_updating_frame) { + EmacsView *view = FRAME_NS_VIEW (f); + [view unlockFocus]; +#if defined (NS_IMPL_GNUSTEP) + [[view window] flushWindow]; #endif -#endif /* NS_DRAW_TO_BUFFER */ - -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if (f != ns_updating_frame) - { - if (focus_view != NULL) - { - [focus_view unlockFocus]; - [[focus_view window] flushWindow]; - focus_view = NULL; - } - } -#endif -#if defined (NS_DRAW_TO_BUFFER) && MAC_OS_X_VERSION_MIN_REQUIRED < 101400 } -#endif } @@ -1453,7 +1286,7 @@ ns_ring_bell (struct frame *f) } } -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 +#if !defined (NS_IMPL_COCOA) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 static void hide_bell (void) /* -------------------------------------------------------------------------- @@ -1620,7 +1453,7 @@ ns_make_frame_visible (struct frame *f) if (!FRAME_VISIBLE_P (f)) { EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f); - NSWindow *window = [view window]; + EmacsWindow *window = (EmacsWindow *)[view window]; SET_FRAME_VISIBLE (f, 1); ns_raise_frame (f, ! FRAME_NO_FOCUS_ON_MAP (f)); @@ -1629,8 +1462,6 @@ ns_make_frame_visible (struct frame *f) fullscreen also. So skip handleFS as this will print an error. */ if ([view fsIsNative] && [view isFullscreen]) { - // maybe it is not necessary to wait - [view waitFullScreenTransition]; return; } @@ -1645,11 +1476,8 @@ ns_make_frame_visible (struct frame *f) relationship, so reinstate it. */ if ([window parentWindow] == nil && FRAME_PARENT_FRAME (f) != NULL) { - NSWindow *parent = [FRAME_NS_VIEW (FRAME_PARENT_FRAME (f)) window]; - block_input (); - [parent addChildWindow: window - ordered: NSWindowAbove]; + [window setParentChildRelationships]; unblock_input (); /* If the parent frame moved while the child frame was @@ -1806,52 +1634,35 @@ ns_set_offset (struct frame *f, int xoff, int yoff, int change_grav) block_input (); - if (FRAME_PARENT_FRAME (f)) - { - /* Convert the parent frame's view rectangle into screen - coords. */ - EmacsView *parentView = FRAME_NS_VIEW (FRAME_PARENT_FRAME (f)); - NSRect parentRect = [parentView convertRect:[parentView frame] - toView:nil]; - parentRect = [[parentView window] convertRectToScreen:parentRect]; + /* If there is no parent frame then just convert to screen + coordinates, UNLESS we have negative values, in which case I + think it's best to position from the bottom and right of the + current screen rather than the main screen or whole display. */ - if (f->size_hint_flags & XNegative) - topLeft.x = NSMaxX (parentRect) - NSWidth (windowFrame) + xoff; - else - topLeft.x = NSMinX (parentRect) + xoff; + NSRect parentRect = ns_parent_window_rect (f); - if (f->size_hint_flags & YNegative) - topLeft.y = NSMinY (parentRect) + NSHeight (windowFrame) - yoff; - else - topLeft.y = NSMaxY (parentRect) - yoff; - } + if (f->size_hint_flags & XNegative) + topLeft.x = NSMaxX (parentRect) - NSWidth (windowFrame) + xoff; + else if (FRAME_PARENT_FRAME (f)) + topLeft.x = NSMinX (parentRect) + xoff; else - { - /* If there is no parent frame then just convert to screen - coordinates, UNLESS we have negative values, in which case I - think it's best to position from the bottom and right of the - current screen rather than the main screen or whole - display. */ - NSRect screenFrame = [[[view window] screen] frame]; - - if (f->size_hint_flags & XNegative) - topLeft.x = NSMaxX (screenFrame) - NSWidth (windowFrame) + xoff; - else - topLeft.x = xoff; + topLeft.x = xoff; - if (f->size_hint_flags & YNegative) - topLeft.y = NSMinY (screenFrame) + NSHeight (windowFrame) - yoff; - else - topLeft.y = NSMaxY ([[[NSScreen screens] objectAtIndex:0] frame]) - yoff; + if (f->size_hint_flags & YNegative) + topLeft.y = NSMinY (parentRect) + NSHeight (windowFrame) - yoff; + else if (FRAME_PARENT_FRAME (f)) + topLeft.y = NSMaxY (parentRect) - yoff; + else + topLeft.y = NSMaxY ([[[NSScreen screens] objectAtIndex:0] frame]) - yoff; #ifdef NS_IMPL_GNUSTEP - /* Don't overlap the menu. + /* Don't overlap the menu. - FIXME: Surely there's a better way than just hardcoding 100 - in here? */ - topLeft.x = 100; + FIXME: Surely there's a better way than just hardcoding 100 in + here? */ + if (topLeft.x < 100) + topLeft.x = 100; #endif - } NSTRACE_POINT ("setFrameTopLeftPoint", topLeft); [[view window] setFrameTopLeftPoint:topLeft]; @@ -1865,78 +1676,47 @@ static void ns_set_window_size (struct frame *f, bool change_gravity, int width, - int height, - bool pixelwise) + int height) /* -------------------------------------------------------------------------- - Adjust window pixel size based on given character grid size + Adjust window pixel size based on native sizes WIDTH and HEIGHT. Impl is a bit more complex than other terms, need to do some internal clipping. -------------------------------------------------------------------------- */ { EmacsView *view = FRAME_NS_VIEW (f); NSWindow *window = [view window]; - NSRect wr = [window frame]; - int pixelwidth, pixelheight; - int orig_height = wr.size.height; + NSRect frameRect; NSTRACE ("ns_set_window_size"); if (view == nil) return; - NSTRACE_RECT ("current", wr); - NSTRACE_MSG ("Width:%d Height:%d Pixelwise:%d", width, height, pixelwise); + NSTRACE_RECT ("current", [window frame]); + NSTRACE_MSG ("Width:%d Height:%d", width, height); NSTRACE_MSG ("Font %d x %d", FRAME_COLUMN_WIDTH (f), FRAME_LINE_HEIGHT (f)); block_input (); - if (pixelwise) - { - pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width); - pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height); - } - else - { - pixelwidth = FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, width); - pixelheight = FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, height); - } - - wr.size.width = pixelwidth + f->border_width; - wr.size.height = pixelheight; - if (! [view isFullscreen]) - wr.size.height += FRAME_NS_TITLEBAR_HEIGHT (f) - + FRAME_TOOLBAR_HEIGHT (f); - - /* Do not try to constrain to this screen. We may have multiple - screens, and want Emacs to span those. Constraining to screen - prevents that, and that is not nice to the user. */ - if (f->output_data.ns->zooming) - f->output_data.ns->zooming = 0; - else - wr.origin.y += orig_height - wr.size.height; - - frame_size_history_add - (f, Qx_set_window_size_1, width, height, - list5 (Fcons (make_fixnum (pixelwidth), make_fixnum (pixelheight)), - Fcons (make_fixnum (wr.size.width), make_fixnum (wr.size.height)), - make_fixnum (f->border_width), - make_fixnum (FRAME_NS_TITLEBAR_HEIGHT (f)), - make_fixnum (FRAME_TOOLBAR_HEIGHT (f)))); - - /* Usually it seems safe to delay changing the frame size, but when a - series of actions are taken with no redisplay between them then we - can end up using old values so don't delay here. */ - change_frame_size (f, - FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth), - FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight), - 0, NO, 0, 1); - - [window setFrame:wr display:NO]; + frameRect = [window frameRectForContentRect:NSMakeRect (0, 0, width, height)]; + + /* Set the origin so the top left of the frame doesn't move. */ + frameRect.origin = [window frame].origin; + frameRect.origin.y += NSHeight ([view frame]) - height; + + if (f->output_data.ns->zooming) + f->output_data.ns->zooming = 0; + + /* Usually it seems safe to delay changing the frame size, but when a + series of actions are taken with no redisplay between them then we + can end up using old values so don't delay here. */ + change_frame_size (f, width, height, false, NO, false); + + [window setFrame:frameRect display:NO]; unblock_input (); } -#ifdef NS_IMPL_COCOA void ns_set_undecorated (struct frame *f, Lisp_Object new_value, Lisp_Object old_value) /* -------------------------------------------------------------------------- @@ -1946,45 +1726,34 @@ ns_set_undecorated (struct frame *f, Lisp_Object new_value, Lisp_Object old_valu dragged, resized, iconified, maximized or deleted with the mouse. If nil, draw the frame with all the elements listed above unless these have been suspended via window manager settings. - - GNUStep cannot change an existing window's style. -------------------------------------------------------------------------- */ { - EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f); - NSWindow *window = [view window]; - NSTRACE ("ns_set_undecorated"); if (!EQ (new_value, old_value)) { + EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f); + NSWindow *oldWindow = [view window]; + NSWindow *newWindow; + block_input (); - if (NILP (new_value)) - { - FRAME_UNDECORATED (f) = false; - [window setStyleMask: ((window.styleMask | FRAME_DECORATED_FLAGS) - ^ FRAME_UNDECORATED_FLAGS)]; + FRAME_UNDECORATED (f) = !NILP (new_value); - [view createToolbar: f]; - } - else - { - [window setToolbar: nil]; - /* Do I need to release the toolbar here? */ + newWindow = [[EmacsWindow alloc] initWithEmacsFrame:f]; - FRAME_UNDECORATED (f) = true; - [window setStyleMask: ((window.styleMask | FRAME_UNDECORATED_FLAGS) - ^ FRAME_DECORATED_FLAGS)]; - } + if ([oldWindow isKeyWindow]) + [newWindow makeKeyAndOrderFront:NSApp]; - /* At this point it seems we don't have an active NSResponder, - so some key presses (TAB) are swallowed by the system. */ - [window makeFirstResponder: view]; + [newWindow setIsVisible:[oldWindow isVisible]]; + if ([oldWindow isMiniaturized]) + [newWindow miniaturize:NSApp]; + + [oldWindow close]; unblock_input (); } } -#endif /* NS_IMPL_COCOA */ void ns_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_value) @@ -2011,7 +1780,6 @@ ns_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_val -------------------------------------------------------------------------- */ { struct frame *p = NULL; - NSWindow *parent, *child; NSTRACE ("ns_set_parent_frame"); @@ -2024,76 +1792,11 @@ ns_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_val error ("Invalid specification of `parent-frame'"); } - if (p != FRAME_PARENT_FRAME (f)) - { - block_input (); - child = [FRAME_NS_VIEW (f) window]; + fset_parent_frame (f, new_value); -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f); -#endif - - if ([child parentWindow] != nil) - { -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - parent = [child parentWindow]; -#endif - - [[child parentWindow] removeChildWindow:child]; -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 - if ([child respondsToSelector:@selector(setAccessibilitySubrole:)]) -#endif - [child setAccessibilitySubrole:NSAccessibilityStandardWindowSubrole]; -#endif -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - if (NILP (new_value)) - { - NSTRACE ("child setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary"); - [child setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - // if current parent in fullscreen and no new parent make child fullscreen - while (parent) { - if (([parent styleMask] & NSWindowStyleMaskFullScreen) != 0) - { - [view toggleFullScreen:child]; - break; - } - // check all parents - parent = [parent parentWindow]; - } - } -#endif - } - - if (!NILP (new_value)) - { -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - // child frame must not be in fullscreen - if ([view fsIsNative] && [view isFullscreen]) - { - // in case child is going fullscreen - [view waitFullScreenTransition]; - [view toggleFullScreen:child]; - } - NSTRACE ("child setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary"); - [child setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; -#endif - parent = [FRAME_NS_VIEW (p) window]; - - [parent addChildWindow: child - ordered: NSWindowAbove]; -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 - if ([child respondsToSelector:@selector(setAccessibilitySubrole:)]) -#endif - [child setAccessibilitySubrole:NSAccessibilityFloatingWindowSubrole]; -#endif - } - - unblock_input (); - - fset_parent_frame (f, new_value); - } + block_input (); + [(EmacsWindow *)[FRAME_NS_VIEW (f) window] setParentChildRelationships]; + unblock_input (); } void @@ -2535,12 +2238,10 @@ ns_set_frame_alpha (struct frame *f) else if (0.0 <= alpha && alpha < alpha_min && alpha_min <= 1.0) alpha = alpha_min; -#ifdef NS_IMPL_COCOA { EmacsView *view = FRAME_NS_VIEW (f); - [[view window] setAlphaValue: alpha]; + [[view window] setAlphaValue: alpha]; } -#endif } @@ -3194,8 +2895,40 @@ ns_compute_glyph_string_overhangs (struct glyph_string *s) ========================================================================== */ +static NSMutableDictionary *fringe_bmp; + +static void +ns_define_fringe_bitmap (int which, unsigned short *bits, int h, int w) +{ + NSBezierPath *p = [NSBezierPath bezierPath]; + + if (!fringe_bmp) + fringe_bmp = [[NSMutableDictionary alloc] initWithCapacity:25]; + + [p moveToPoint:NSMakePoint (0, 0)]; + + for (int y = 0 ; y < h ; y++) + for (int x = 0 ; x < w ; x++) + { + /* XBM rows are always round numbers of bytes, with any unused + bits ignored. */ + int byte = y * (w/8 + (w%8 ? 1 : 0)) + x/8; + bool bit = bits[byte] & (0x80 >> x%8); + if (bit) + [p appendBezierPathWithRect:NSMakeRect (x, y, 1, 1)]; + } + + [fringe_bmp setObject:p forKey:[NSNumber numberWithInt:which]]; +} + + +static void +ns_destroy_fringe_bitmap (int which) +{ + [fringe_bmp removeObjectForKey:[NSNumber numberWithInt:which]]; +} + -extern int max_used_fringe_bitmap; static void ns_draw_fringe_bitmap (struct window *w, struct glyph_row *row, struct draw_fringe_bitmap_params *p) @@ -3221,41 +2954,18 @@ ns_draw_fringe_bitmap (struct window *w, struct glyph_row *row, struct frame *f = XFRAME (WINDOW_FRAME (w)); struct face *face = p->face; - static EmacsImage **bimgs = NULL; - static int nBimgs = 0; NSRect clearRect = NSZeroRect; - NSRect imageRect = NSZeroRect; NSRect rowRect = ns_row_rect (w, row, ANY_AREA); NSTRACE_WHEN (NSTRACE_GROUP_FRINGE, "ns_draw_fringe_bitmap"); NSTRACE_MSG ("which:%d cursor:%d overlay:%d width:%d height:%d period:%d", p->which, p->cursor_p, p->overlay_p, p->wd, p->h, p->dh); - /* grow bimgs if needed */ - if (nBimgs < max_used_fringe_bitmap) - { - bimgs = xrealloc (bimgs, max_used_fringe_bitmap * sizeof *bimgs); - memset (bimgs + nBimgs, 0, - (max_used_fringe_bitmap - nBimgs) * sizeof *bimgs); - nBimgs = max_used_fringe_bitmap; - } - - /* Work out the rectangle we will composite into. */ - if (p->which) - imageRect = NSMakeRect (p->x, p->y, p->wd, p->h); + /* Work out the rectangle we will need to clear. */ + clearRect = NSMakeRect (p->x, p->y, p->wd, p->h); - /* Work out the rectangle we will need to clear. Because we're - compositing rather than blitting, we need to clear the area under - the image regardless of anything else. */ if (p->bx >= 0 && !p->overlay_p) - { - clearRect = NSMakeRect (p->bx, p->by, p->nx, p->ny); - clearRect = NSUnionRect (clearRect, imageRect); - } - else - { - clearRect = imageRect; - } + clearRect = NSUnionRect (clearRect, NSMakeRect (p->bx, p->by, p->nx, p->ny)); /* Handle partially visible rows. */ clearRect = NSIntersectionRect (clearRect, rowRect); @@ -3271,53 +2981,29 @@ ns_draw_fringe_bitmap (struct window *w, struct glyph_row *row, NSRectFill (clearRect); } - if (p->which) + NSBezierPath *bmp = [fringe_bmp objectForKey:[NSNumber numberWithInt:p->which]]; + if (bmp) { - EmacsImage *img = bimgs[p->which - 1]; + NSAffineTransform *transform = [NSAffineTransform transform]; + NSColor *bm_color; - if (!img) - { - // Note: For "periodic" images, allocate one EmacsImage for - // the base image, and use it for all dh:s. - unsigned short *bits = p->bits; - int full_height = p->h + p->dh; - int i; - unsigned char *cbits = xmalloc (full_height); - - for (i = 0; i < full_height; i++) - cbits[i] = bits[i]; - img = [[EmacsImage alloc] initFromXBM: cbits width: 8 - height: full_height - fg: 0 bg: 0 - reverseBytes: NO]; - bimgs[p->which - 1] = img; - xfree (cbits); - } + /* Because the image is defined at (0, 0) we need to take a copy + and then transform that copy to the new origin. */ + bmp = [bmp copy]; + [transform translateXBy:p->x yBy:p->y - p->dh]; + [bmp transformUsingAffineTransform:transform]; + if (!p->cursor_p) + bm_color = ns_lookup_indexed_color(face->foreground, f); + else if (p->overlay_p) + bm_color = ns_lookup_indexed_color(face->background, f); + else + bm_color = f->output_data.ns->cursor_color; - { - NSColor *bm_color; - if (!p->cursor_p) - bm_color = ns_lookup_indexed_color(face->foreground, f); - else if (p->overlay_p) - bm_color = ns_lookup_indexed_color(face->background, f); - else - bm_color = f->output_data.ns->cursor_color; - [img setXBMColor: bm_color]; - } - - // Note: For periodic images, the full image height is "h + hd". - // By using the height h, a suitable part of the image is used. - NSRect fromRect = NSMakeRect(0, 0, p->wd, p->h); - - NSTRACE_RECT ("fromRect", fromRect); + [bm_color set]; + [bmp fill]; - [img drawInRect: imageRect - fromRect: fromRect - operation: NSCompositingOperationSourceOver - fraction: 1.0 - respectFlipped: YES - hints: nil]; + [bmp release]; } ns_unfocus (f); } @@ -3792,7 +3478,7 @@ ns_draw_box (NSRect r, CGFloat hthickness, CGFloat vthickness, static void -ns_draw_relief (NSRect r, int hthickness, int vthickness, char raised_p, +ns_draw_relief (NSRect outer, int hthickness, int vthickness, char raised_p, char top_p, char bottom_p, char left_p, char right_p, struct glyph_string *s) /* -------------------------------------------------------------------------- @@ -3803,7 +3489,7 @@ ns_draw_relief (NSRect r, int hthickness, int vthickness, char raised_p, { static NSColor *baseCol = nil, *lightCol = nil, *darkCol = nil; NSColor *newBaseCol = nil; - NSRect sr = r; + NSRect inner; NSTRACE ("ns_draw_relief"); @@ -3837,33 +3523,50 @@ ns_draw_relief (NSRect r, int hthickness, int vthickness, char raised_p, darkCol = [[baseCol shadowWithLevel: 0.3] retain]; } - [(raised_p ? lightCol : darkCol) set]; - - /* TODO: mitering. Using NSBezierPath doesn't work because of color switch. */ + /* Calculate the inner rectangle. */ + inner = NSInsetRect (outer, hthickness, vthickness); - /* top */ - sr.size.height = hthickness; - if (top_p) NSRectFill (sr); + [(raised_p ? lightCol : darkCol) set]; - /* left */ - sr.size.height = r.size.height; - sr.size.width = vthickness; - if (left_p) NSRectFill (sr); + if (top_p || left_p) + { + NSBezierPath *p = [NSBezierPath bezierPath]; + [p moveToPoint:NSMakePoint (NSMinX (outer), NSMinY (outer))]; + if (top_p) + { + [p lineToPoint:NSMakePoint (NSMaxX (outer), NSMinY (outer))]; + [p lineToPoint:NSMakePoint (NSMaxX (inner), NSMinY (inner))]; + } + [p lineToPoint:NSMakePoint (NSMinX (inner), NSMinY (inner))]; + if (left_p) + { + [p lineToPoint:NSMakePoint (NSMinX (inner), NSMaxY (inner))]; + [p lineToPoint:NSMakePoint (NSMinX (outer), NSMaxY (outer))]; + } + [p closePath]; + [p fill]; + } [(raised_p ? darkCol : lightCol) set]; - /* bottom */ - sr.size.width = r.size.width; - sr.size.height = hthickness; - sr.origin.y += r.size.height - hthickness; - if (bottom_p) NSRectFill (sr); - - /* right */ - sr.size.height = r.size.height; - sr.origin.y = r.origin.y; - sr.size.width = vthickness; - sr.origin.x += r.size.width - vthickness; - if (right_p) NSRectFill (sr); + if (bottom_p || right_p) + { + NSBezierPath *p = [NSBezierPath bezierPath]; + [p moveToPoint:NSMakePoint (NSMaxX (outer), NSMaxY (outer))]; + if (right_p) + { + [p lineToPoint:NSMakePoint (NSMaxX (outer), NSMinY (outer))]; + [p lineToPoint:NSMakePoint (NSMaxX (inner), NSMinY (inner))]; + } + [p lineToPoint:NSMakePoint (NSMaxX (inner), NSMaxY (inner))]; + if (bottom_p) + { + [p lineToPoint:NSMakePoint (NSMinX (inner), NSMaxY (inner))]; + [p lineToPoint:NSMakePoint (NSMinX (outer), NSMaxY (outer))]; + } + [p closePath]; + [p fill]; + } } @@ -5272,8 +4975,8 @@ static struct redisplay_interface ns_redisplay_interface = gui_get_glyph_overhangs, gui_fix_overlapping_area, ns_draw_fringe_bitmap, - 0, /* define_fringe_bitmap */ /* FIXME: simplify ns_draw_fringe_bitmap */ - 0, /* destroy_fringe_bitmap */ + ns_define_fringe_bitmap, + ns_destroy_fringe_bitmap, ns_compute_glyph_string_overhangs, ns_draw_glyph_string, ns_define_frame_cursor, @@ -5459,6 +5162,8 @@ ns_term_init (Lisp_Object display_name) terminal->name = xlispstrdup (display_name); + gui_init_fringe (terminal->rif); + unblock_input (); if (!inhibit_x_resources) @@ -6300,11 +6005,6 @@ not_in_argv (NSString *arg) name:NSViewFrameDidChangeNotification object:nil]; -#ifdef NS_DRAW_TO_BUFFER - [surface release]; -#endif - - [toolbar release]; if (fs_state == FULLSCREEN_BOTH) [nonfs_window release]; [super dealloc]; @@ -6393,9 +6093,7 @@ not_in_argv (NSString *arg) NSTRACE ("[EmacsView keyDown:]"); /* Rhapsody and macOS give up and down events for the arrow keys. */ - if (ns_fake_keydown == YES) - ns_fake_keydown = NO; - else if ([theEvent type] != NSEventTypeKeyDown) + if ([theEvent type] != NSEventTypeKeyDown) return; if (!emacs_event) @@ -7252,43 +6950,6 @@ not_in_argv (NSString *arg) } -- (void)windowDidResize: (NSNotification *)notification -{ - NSTRACE ("[EmacsView windowDidResize:]"); - if (!FRAME_LIVE_P (emacsframe)) - { - NSTRACE_MSG ("Ignored (frame dead)"); - return; - } - if (emacsframe->output_data.ns->in_animation) - { - NSTRACE_MSG ("Ignored (in animation)"); - return; - } - - if (! [self fsIsNative]) - { - NSWindow *theWindow = [notification object]; - /* We can get notification on the non-FS window when in - fullscreen mode. */ - if ([self window] != theWindow) return; - } - - NSTRACE_RECT ("frame", [[notification object] frame]); - -#ifdef NS_IMPL_GNUSTEP - NSWindow *theWindow = [notification object]; - - /* In GNUstep, at least currently, it's possible to get a didResize - without getting a willResize, therefore we need to act as if we got - the willResize now. */ - NSSize sz = [theWindow frame].size; - sz = [self windowWillResize: theWindow toSize: sz]; -#endif /* NS_IMPL_GNUSTEP */ - - ns_send_appdefined (-1); -} - #ifdef NS_IMPL_COCOA - (void)viewDidEndLiveResize { @@ -7306,53 +6967,30 @@ not_in_argv (NSString *arg) #endif /* NS_IMPL_COCOA */ -- (void)viewDidResize:(NSNotification *)notification +- (void)resizeWithOldSuperviewSize: (NSSize)oldSize { - NSRect frame = [self frame]; - int neww, newh; - - if (! FRAME_LIVE_P (emacsframe)) - return; + NSRect frame; + int width, height; - NSTRACE ("[EmacsView viewDidResize]"); + NSTRACE ("[EmacsView resizeWithOldSuperviewSize:]"); - neww = (int)NSWidth (frame); - newh = (int)NSHeight (frame); - NSTRACE_SIZE ("New size", NSMakeSize (neww, newh)); + [super resizeWithOldSuperviewSize:oldSize]; -#ifdef NS_DRAW_TO_BUFFER - if ([self wantsUpdateLayer]) - { - CGFloat scale = [[self window] backingScaleFactor]; - NSSize size = [surface getSize]; - int oldw = size.width / scale; - int oldh = size.height / scale; - - NSTRACE_SIZE ("Original size", NSMakeSize (oldw, oldh)); + if (! FRAME_LIVE_P (emacsframe)) + return; - /* Don't want to do anything when the view size hasn't changed. */ - if ((oldh == newh && oldw == neww)) - { - NSTRACE_MSG ("No change"); - return; - } + frame = [self frame]; + width = (int)NSWidth (frame); + height = (int)NSHeight (frame); - [surface release]; - surface = nil; - } -#endif + NSTRACE_SIZE ("New size", NSMakeSize (width, height)); + NSTRACE_SIZE ("Original size", size); - /* I'm not sure if it's safe to call this every time the view - changes size, as Emacs may already know about the change. - Unfortunately there doesn't seem to be a bullet-proof method of - determining whether we need to call it or not. */ - change_frame_size (emacsframe, - FRAME_PIXEL_TO_TEXT_WIDTH (emacsframe, neww), - FRAME_PIXEL_TO_TEXT_HEIGHT (emacsframe, newh), - 0, YES, 0, 1); + change_frame_size (emacsframe, width, height, false, YES, false); SET_FRAME_GARBAGED (emacsframe); cancel_mouse_face (emacsframe); + ns_send_appdefined (-1); } @@ -7447,42 +7085,8 @@ not_in_argv (NSString *arg) } -- (void)createToolbar: (struct frame *)f -{ - EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f); - NSWindow *window = [view window]; - - toolbar = [[EmacsToolbar alloc] initForView: self withIdentifier: - [NSString stringWithFormat: @"Emacs Frame %d", - ns_window_num]]; - [toolbar setVisible: NO]; - [window setToolbar: toolbar]; - - /* Don't set frame garbaged until tool bar is up to date? - This avoids an extra clear and redraw (flicker) at frame creation. */ - if (FRAME_EXTERNAL_TOOL_BAR (f)) wait_for_tool_bar = YES; - else wait_for_tool_bar = NO; - - -#ifdef NS_IMPL_COCOA - { - NSButton *toggleButton; - toggleButton = [window standardWindowButton: NSWindowToolbarButton]; - [toggleButton setTarget: self]; - [toggleButton setAction: @selector (toggleToolbar: )]; - } -#endif -} - - - (instancetype) initFrameFromEmacs: (struct frame *)f { - NSRect r, wr; - Lisp_Object tem; - EmacsWindow *win; - NSColor *col; - NSString *name; - NSTRACE ("[EmacsView initFrameFromEmacs:]"); NSTRACE_MSG ("cols:%d lines:%d", f->text_cols, f->text_lines); @@ -7499,15 +7103,14 @@ not_in_argv (NSString *arg) #endif fs_is_native = ns_use_native_fullscreen; #endif - in_fullscreen_transition = NO; maximized_width = maximized_height = -1; nonfs_window = nil; ns_userRect = NSMakeRect (0, 0, 0, 0); - r = NSMakeRect (0, 0, FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, f->text_cols), - FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, f->text_lines)); - [self initWithFrame: r]; + [self initWithFrame: + NSMakeRect (0, 0, FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, f->text_cols), + FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, f->text_lines))]; [self setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; FRAME_NS_VIEW (f) = self; @@ -7517,100 +7120,22 @@ not_in_argv (NSString *arg) maximizing_resize = NO; #endif - win = [[EmacsWindow alloc] - initWithContentRect: r - styleMask: (FRAME_UNDECORATED (f) - ? FRAME_UNDECORATED_FLAGS - : FRAME_DECORATED_FLAGS) - backing: NSBackingStoreBuffered - defer: YES]; + [[EmacsWindow alloc] initWithEmacsFrame:f]; -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) -#endif - if (FRAME_PARENT_FRAME (f)) - [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; - else - [win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; -#endif - - wr = [win frame]; - bwidth = f->border_width = wr.size.width - r.size.width; - - [win setAcceptsMouseMovedEvents: YES]; - [win setDelegate: self]; -#if !defined (NS_IMPL_COCOA) || MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 -#if MAC_OS_X_VERSION_MAX_ALLOWED > 1090 - if ([win respondsToSelector: @selector(useOptimizedDrawing:)]) -#endif - [win useOptimizedDrawing: YES]; +#ifdef NS_IMPL_COCOA + /* These settings mean AppKit will retain the contents of the frame + on resize. Unfortunately it also means the frame will not be + automatically marked for display, but we can do that ourselves in + resizeWithOldSuperviewSize. */ + [self setWantsLayer:YES]; + [self setLayerContentsRedrawPolicy: + NSViewLayerContentsRedrawOnSetNeedsDisplay]; + [self setLayerContentsPlacement:NSViewLayerContentsPlacementTopLeft]; #endif - [[win contentView] addSubview: self]; - if (ns_drag_types) [self registerForDraggedTypes: ns_drag_types]; - tem = f->name; - name = NILP (tem) ? @"Emacs" : [NSString stringWithLispString:tem]; - [win setTitle: name]; - - /* toolbar support */ - if (! FRAME_UNDECORATED (f)) - [self createToolbar: f]; - - - [win setAppearance]; - -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 - if ([win respondsToSelector: @selector(titlebarAppearsTransparent)]) - win.titlebarAppearsTransparent = FRAME_NS_TRANSPARENT_TITLEBAR (f); -#endif - - tem = f->icon_name; - if (!NILP (tem)) - [win setMiniwindowTitle: - [NSString stringWithLispString:tem]]; - - if (FRAME_PARENT_FRAME (f) != NULL) - { - NSWindow *parent = [FRAME_NS_VIEW (FRAME_PARENT_FRAME (f)) window]; - [parent addChildWindow: win - ordered: NSWindowAbove]; - } - - if (FRAME_Z_GROUP (f) != z_group_none) - win.level = NSNormalWindowLevel - + (FRAME_Z_GROUP_BELOW (f) ? -1 : 1); - - { - NSScreen *screen = [win screen]; - - if (screen != 0) - { - NSPoint pt = NSMakePoint - (IN_BOUND (-SCREENMAX, f->left_pos - + NS_PARENT_WINDOW_LEFT_POS (f), SCREENMAX), - IN_BOUND (-SCREENMAX, - NS_PARENT_WINDOW_TOP_POS (f) - f->top_pos, - SCREENMAX)); - - [win setFrameTopLeftPoint: pt]; - - NSTRACE_RECT ("new frame", [win frame]); - } - } - - [win makeFirstResponder: self]; - - col = ns_lookup_indexed_color (NS_FACE_BACKGROUND - (FACE_FROM_ID (emacsframe, DEFAULT_FACE_ID)), - emacsframe); - [win setBackgroundColor: col]; - if ([col alphaComponent] != (EmacsCGFloat) 1.0) - [win setOpaque: NO]; - #if !defined (NS_IMPL_COCOA) \ || MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 #if MAC_OS_X_VERSION_MAX_ALLOWED > 1090 @@ -7621,21 +7146,6 @@ not_in_argv (NSString *arg) [NSApp registerServicesMenuSendTypes: ns_send_types returnTypes: [NSArray array]]; - /* Set up view resize notifications. */ - [self setPostsFrameChangedNotifications:YES]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector (viewDidResize:) - name:NSViewFrameDidChangeNotification object:nil]; - - /* macOS Sierra automatically enables tabbed windows. We can't - allow this to be enabled until it's available on a Free system. - Currently it only happens by accident and is buggy anyway. */ -#ifdef NS_IMPL_COCOA - if ([win respondsToSelector: @selector(setTabbingMode:)]) - [win setTabbingMode: NSWindowTabbingModeDisallowed]; -#endif - ns_window_num++; return self; } @@ -7862,7 +7372,6 @@ not_in_argv (NSString *arg) - (void)windowWillEnterFullScreen:(NSNotification *)notification { NSTRACE ("[EmacsView windowWillEnterFullScreen:]"); - in_fullscreen_transition = YES; [self windowWillEnterFullScreen]; } - (void)windowWillEnterFullScreen /* provided for direct calls */ @@ -7875,7 +7384,6 @@ not_in_argv (NSString *arg) { NSTRACE ("[EmacsView windowDidEnterFullScreen:]"); [self windowDidEnterFullScreen]; - in_fullscreen_transition = NO; } - (void)windowDidEnterFullScreen /* provided for direct calls */ @@ -7907,14 +7415,13 @@ not_in_argv (NSString *arg) [NSApp setPresentationOptions: options]; } #endif - [toolbar setVisible:tbar_visible]; + [[[self window]toolbar] setVisible:tbar_visible]; } } - (void)windowWillExitFullScreen:(NSNotification *)notification { NSTRACE ("[EmacsView windowWillExitFullScreen:]"); - in_fullscreen_transition = YES; [self windowWillExitFullScreen]; } @@ -7934,7 +7441,6 @@ not_in_argv (NSString *arg) { NSTRACE ("[EmacsView windowDidExitFullScreen:]"); [self windowDidExitFullScreen]; - in_fullscreen_transition = NO; } - (void)windowDidExitFullScreen /* provided for direct calls */ @@ -7952,33 +7458,17 @@ not_in_argv (NSString *arg) #endif if (FRAME_EXTERNAL_TOOL_BAR (emacsframe)) { - [toolbar setVisible:YES]; + [[[self window] toolbar] setVisible:YES]; update_frame_tool_bar (emacsframe); [[self window] display]; } else - [toolbar setVisible:NO]; + [[[self window] toolbar] setVisible:NO]; if (next_maximized != -1) [[self window] performZoom:self]; } -- (BOOL)inFullScreenTransition -{ - return in_fullscreen_transition; -} - -- (void)waitFullScreenTransition -{ -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - while ([self inFullScreenTransition]) - { - NSTRACE ("wait for fullscreen"); - wait_reading_process_output (0, 300000000, 0, 1, Qnil, NULL, 0); - } -#endif -} - - (BOOL)fsIsNative { return fs_is_native; @@ -8018,7 +7508,7 @@ not_in_argv (NSString *arg) NSWindowCollectionBehavior b = [win collectionBehavior]; if (ns_use_native_fullscreen) { - if ([win parentWindow]) + if (FRAME_PARENT_FRAME (emacsframe)) { b &= ~NSWindowCollectionBehaviorFullScreenPrimary; b |= NSWindowCollectionBehaviorFullScreenAuxiliary; @@ -8045,7 +7535,7 @@ not_in_argv (NSString *arg) - (void)toggleFullScreen: (id)sender { - NSWindow *w, *fw; + EmacsWindow *w, *fw; BOOL onFirstScreen; struct frame *f; NSRect r, wr; @@ -8058,19 +7548,13 @@ not_in_argv (NSString *arg) #if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 if ([[self window] respondsToSelector: @selector(toggleFullScreen:)]) - { -#endif - [[self window] toggleFullScreen:sender]; - // wait for fullscreen animation complete (bug#28496) - [self waitFullScreenTransition]; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 - } #endif + [[self window] toggleFullScreen:sender]; #endif return; } - w = [self window]; + w = (EmacsWindow *)[self window]; onFirstScreen = [[w screen] isEqual:[[NSScreen screens] objectAtIndex:0]]; f = emacsframe; wr = [w frame]; @@ -8105,27 +7589,9 @@ not_in_argv (NSString *arg) #endif } - fw = [[EmacsFSWindow alloc] - initWithContentRect:[w contentRectForFrameRect:wr] - styleMask:NSWindowStyleMaskBorderless - backing:NSBackingStoreBuffered - defer:YES - screen:screen]; - - [fw setContentView:[w contentView]]; - [fw setTitle:[w title]]; - [fw setDelegate:self]; - [fw setAcceptsMouseMovedEvents: YES]; -#if !defined (NS_IMPL_COCOA) \ - || MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 -#if MAC_OS_X_VERSION_MAX_ALLOWED > 1090 - if ([fw respondsToSelector: @selector(useOptimizedDrawing:)]) -#endif - [fw useOptimizedDrawing: YES]; -#endif - [fw setBackgroundColor: col]; - if ([col alphaComponent] != (EmacsCGFloat) 1.0) - [fw setOpaque: NO]; + fw = [[EmacsWindow alloc] initWithEmacsFrame:emacsframe + fullscreen:YES + screen:screen]; f->border_width = 0; @@ -8133,7 +7599,6 @@ not_in_argv (NSString *arg) [self windowWillEnterFullScreen]; [fw makeKeyAndOrderFront:NSApp]; - [fw makeFirstResponder:self]; [w orderOut:self]; r = [fw frameRectForContentRect:[screen frame]]; [fw setFrame: r display:YES animate:ns_use_fullscreen_animation]; @@ -8160,7 +7625,7 @@ not_in_argv (NSString *arg) if ([col alphaComponent] != (EmacsCGFloat) 1.0) [w setOpaque: NO]; - f->border_width = bwidth; + f->border_width = [w borderWidth]; // To do: consider using [NSNotificationCenter postNotificationName:] to // send notifications. @@ -8297,12 +7762,6 @@ not_in_argv (NSString *arg) } -- (EmacsToolbar *)toolbar -{ - return toolbar; -} - - /* This gets called on toolbar button click. */ - (instancetype)toolbarClicked: (id)item { @@ -8339,42 +7798,54 @@ not_in_argv (NSString *arg) } -#ifdef NS_DRAW_TO_BUFFER -- (void)focusOnDrawingBuffer +#ifdef NS_IMPL_COCOA +- (CALayer *)makeBackingLayer; { - CGFloat scale = [[self window] backingScaleFactor]; + EmacsLayer *l = [[EmacsLayer alloc] + initWithColorSpace:[[[self window] colorSpace] CGColorSpace]]; + [l setDelegate:(id)self]; + [l setContentsScale:[[self window] backingScaleFactor]]; - NSTRACE ("[EmacsView focusOnDrawingBuffer]"); + return l; +} - if (! surface) - { - NSRect frame = [self frame]; - NSSize s = NSMakeSize (NSWidth (frame) * scale, NSHeight (frame) * scale); - surface = [[EmacsSurface alloc] initWithSize:s - ColorSpace:[[[self window] colorSpace] - CGColorSpace]]; - } - - CGContextRef context = [surface getContext]; +- (void)lockFocus +{ + NSTRACE ("[EmacsView lockFocus]"); - CGContextTranslateCTM(context, 0, [surface getSize].height); - CGContextScaleCTM(context, scale, -scale); + if ([self wantsLayer]) + { + CGContextRef context = [(EmacsLayer*)[self layer] getContext]; - [NSGraphicsContext - setCurrentContext:[NSGraphicsContext - graphicsContextWithCGContext:context - flipped:YES]]; + [NSGraphicsContext + setCurrentContext:[NSGraphicsContext + graphicsContextWithCGContext:context + flipped:YES]]; + } +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 + else + [super lockFocus]; +#endif } -- (void)unfocusDrawingBuffer +- (void)unlockFocus { - NSTRACE ("[EmacsView unfocusDrawingBuffer]"); + NSTRACE ("[EmacsView unlockFocus]"); - [NSGraphicsContext setCurrentContext:nil]; - [surface releaseContext]; - [self setNeedsDisplay:YES]; + if ([self wantsLayer]) + { + [NSGraphicsContext setCurrentContext:nil]; + [self setNeedsDisplay:YES]; + } +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 + else + { + [super unlockFocus]; + [super flushWindow]; + } +#endif } @@ -8383,15 +7854,19 @@ not_in_argv (NSString *arg) { NSTRACE ("EmacsView windowDidChangeBackingProperties:]"); - NSRect frame = [self frame]; + if ([self wantsLayer]) + { + NSRect frame = [self frame]; + EmacsLayer *layer = (EmacsLayer *)[self layer]; - [surface release]; - surface = nil; + [layer setContentsScale:[[notification object] backingScaleFactor]]; + [layer setColorSpace:[[[notification object] colorSpace] CGColorSpace]]; - ns_clear_frame (emacsframe); - expose_frame (emacsframe, 0, 0, NSWidth (frame), NSHeight (frame)); + ns_clear_frame (emacsframe); + expose_frame (emacsframe, 0, 0, NSWidth (frame), NSHeight (frame)); + } } -#endif /* NS_DRAW_TO_BUFFER */ +#endif /* NS_IMPL_COCOA */ - (void)copyRect:(NSRect)srcRect to:(NSRect)dstRect @@ -8400,11 +7875,9 @@ not_in_argv (NSString *arg) NSTRACE_RECT ("Source", srcRect); NSTRACE_RECT ("Destination", dstRect); -#ifdef NS_DRAW_TO_BUFFER -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if ([self wantsUpdateLayer]) +#ifdef NS_IMPL_COCOA + if ([self wantsLayer]) { -#endif double scale = [[self window] backingScaleFactor]; CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; int bpp = CGBitmapContextGetBitsPerPixel (context) / 8; @@ -8430,14 +7903,14 @@ not_in_argv (NSString *arg) (char *) srcPixels + y * rowSize, srcRowSize); -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 } +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 else { #endif -#endif /* NS_DRAW_TO_BUFFER */ +#endif /* NS_IMPL_COCOA */ -#if !defined (NS_DRAW_TO_BUFFER) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 +#if !defined (NS_IMPL_COCOA) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400 hide_bell(); // Ensure the bell image isn't scrolled. ns_focus (emacsframe, &dstRect, 1); @@ -8446,39 +7919,44 @@ not_in_argv (NSString *arg) dstRect.origin.y - srcRect.origin.y)]; ns_unfocus (emacsframe); #endif -#if defined (NS_DRAW_TO_BUFFER) && MAC_OS_X_VERSION_MIN_REQUIRED < 101400 +#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED < 101400 } #endif } -#ifdef NS_DRAW_TO_BUFFER -- (BOOL)wantsUpdateLayer -{ -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if (NSAppKitVersionNumber < 1671) - return NO; -#endif - - /* Running on macOS 10.14 or above. */ - return YES; -} - +#ifdef NS_IMPL_COCOA +/* If the frame has been garbaged but the toolkit wants to draw, for + example when resizing the frame, we end up with a blank screen. + Sometimes this results in an unpleasant flicker, so try to + redisplay before drawing. + + This used to be done in viewWillDraw, but with the custom layer + that method is not called. We cannot call redisplay directly from + [NSView layout], because it may trigger another round of layout by + changing the frame size and recursive layout calls are banned. It + appears to be safe to call redisplay here. */ +- (void)layoutSublayersOfLayer:(CALayer *)layer +{ + if (!redisplaying_p && FRAME_GARBAGED_P (emacsframe)) + { + /* If there is IO going on when redisplay is run here Emacs + crashes. I think it's because this code will always be run + within the run loop and for whatever reason processing input + is dangerous. This technique was stolen wholesale from + nsmenu.m and seems to work. */ + bool owfi = waiting_for_input; + waiting_for_input = 0; + block_input (); -- (void)updateLayer -{ - NSTRACE ("[EmacsView updateLayer]"); + redisplay (); - /* This can fail to update the screen if the same surface is - provided twice in a row, even if its contents have changed. - There's a private method, -[CALayer setContentsChanged], that we - could use to force it, but we shouldn't often get the same - surface twice in a row. */ - [[self layer] setContents:(id)[surface getSurface]]; + unblock_input (); + waiting_for_input = owfi; + } } #endif - - (void)drawRect: (NSRect)rect { NSTRACE ("[EmacsView drawRect:" NSTRACE_FMT_RECT "]", @@ -8722,6 +8200,242 @@ not_in_argv (NSString *arg) @implementation EmacsWindow + +- (instancetype) initWithEmacsFrame:(struct frame *)f +{ + return [self initWithEmacsFrame:f fullscreen:NO screen:nil]; +} + + +- (instancetype) initWithEmacsFrame:(struct frame *)f + fullscreen:(BOOL)fullscreen + screen:(NSScreen *)screen +{ + NSWindowStyleMask styleMask; + + NSTRACE ("[EmacsWindow initWithEmacsFrame:fullscreen:screen:]"); + + if (fullscreen) + styleMask = NSWindowStyleMaskBorderless; + else if (FRAME_UNDECORATED (f)) + styleMask = FRAME_UNDECORATED_FLAGS; + else + styleMask = FRAME_DECORATED_FLAGS; + + + self = [super initWithContentRect: + NSMakeRect (0, 0, + FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, f->text_cols), + FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, f->text_lines)) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:YES + screen:screen]; + if (self) + { + NSString *name; + NSColor *col; + NSScreen *screen = [self screen]; + EmacsView *view = FRAME_NS_VIEW (f); + + [self setDelegate:view]; + [[self contentView] addSubview:view]; + [self makeFirstResponder:view]; + +#if !defined (NS_IMPL_COCOA) || MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 +#if MAC_OS_X_VERSION_MAX_ALLOWED > 1090 + if ([self respondsToSelector: @selector(useOptimizedDrawing:)]) +#endif + [self useOptimizedDrawing:YES]; +#endif + + [self setAcceptsMouseMovedEvents:YES]; + + name = NILP (f->name) ? @"Emacs" : [NSString stringWithLispString:f->name]; + [self setTitle:name]; + + if (!NILP (f->icon_name)) + [self setMiniwindowTitle: + [NSString stringWithLispString:f->icon_name]]; + + [self setAppearance]; + +#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + if ([self respondsToSelector:@selector(titlebarAppearsTransparent)]) + [self setTitlebarAppearsTransparent:FRAME_NS_TRANSPARENT_TITLEBAR (f)]; +#endif + + [self setParentChildRelationships]; + + if (FRAME_Z_GROUP (f) != z_group_none) + [self setLevel:NSNormalWindowLevel + (FRAME_Z_GROUP_BELOW (f) ? -1 : 1)]; + + if (screen != 0) + { + NSPoint pt = NSMakePoint + (IN_BOUND (-SCREENMAX, f->left_pos + + NS_PARENT_WINDOW_LEFT_POS (f), SCREENMAX), + IN_BOUND (-SCREENMAX, + NS_PARENT_WINDOW_TOP_POS (f) - f->top_pos, + SCREENMAX)); + + [self setFrameTopLeftPoint:pt]; + + NSTRACE_RECT ("new frame", [self frame]); + } + + f->border_width = [self borderWidth]; + + col = ns_lookup_indexed_color (NS_FACE_BACKGROUND + (FACE_FROM_ID (f, DEFAULT_FACE_ID)), + f); + [self setBackgroundColor:col]; + if ([col alphaComponent] != (EmacsCGFloat) 1.0) + [self setOpaque:NO]; + + /* toolbar support */ + if (! FRAME_UNDECORATED (f)) + [self createToolbar:f]; + + /* macOS Sierra automatically enables tabbed windows. We can't + allow this to be enabled until it's available on a Free system. + Currently it only happens by accident and is buggy anyway. */ +#ifdef NS_IMPL_COCOA + if ([self respondsToSelector:@selector(setTabbingMode:)]) + [self setTabbingMode:NSWindowTabbingModeDisallowed]; +#endif + } + + return self; +} + + +- (void)createToolbar: (struct frame *)f +{ + EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f); + + EmacsToolbar *toolbar = [[EmacsToolbar alloc] + initForView:view + withIdentifier:[NSString stringWithLispString:f->name]]; + [self setToolbar:toolbar]; + + update_frame_tool_bar (f); + +#ifdef NS_IMPL_COCOA + { + NSButton *toggleButton; + toggleButton = [self standardWindowButton:NSWindowToolbarButton]; + [toggleButton setTarget:view]; + [toggleButton setAction:@selector (toggleToolbar:)]; + } +#endif +} + +- (void)dealloc +{ + NSTRACE ("[EmacsWindow dealloc]"); + + /* We need to release the toolbar ourselves. */ + [[self toolbar] release]; + [super dealloc]; +} + +- (NSInteger) borderWidth +{ + return NSWidth ([self frame]) - NSWidth ([[self contentView] frame]); +} + + +- (void)setParentChildRelationships + /* After certain operations, for example making a frame visible or + resetting the NSWindow through modifying the undecorated status, + the parent/child relationship may be broken. We can also use + this method to set them, as long as the frame struct already has + the correct relationship set. */ +{ + NSTRACE ("[EmacsWindow setParentChildRelationships]"); + + Lisp_Object frame, tail; + EmacsView *ourView = (EmacsView *)[self delegate]; + struct frame *ourFrame = ourView->emacsframe; + struct frame *parentFrame = FRAME_PARENT_FRAME (ourFrame); + EmacsWindow *oldParentWindow = (EmacsWindow *)[self parentWindow]; + + +#ifdef NS_IMPL_COCOA + /* We have to set the accesibility subroles and/or the collection + behaviors early otherwise child windows may not go fullscreen as + expected later. */ + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 + if ([child respondsToSelector:@selector(setAccessibilitySubrole:)]) +#endif + /* Set the accessibilty subroles. */ + if (parentFrame) + [self setAccessibilitySubrole:NSAccessibilityFloatingWindowSubrole]; + else + [self setAccessibilitySubrole:NSAccessibilityStandardWindowSubrole]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + [ourView updateCollectionBehavior]; +#endif +#endif + + + /* Check if we have an incorrectly set parent. */ + if ((! parentFrame && oldParentWindow) + || (parentFrame && oldParentWindow + && ((EmacsView *)[oldParentWindow delegate])->emacsframe != parentFrame)) + { + [[self parentWindow] removeChildWindow:self]; + +#ifdef NS_IMPL_COCOA +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 + if ([ourView respondsToSelector:@selector (toggleFullScreen)] +#endif + /* If we are the descendent of a fullscreen window and we + have no new parent, go fullscreen. */ + { + NSWindow *parent = (NSWindow *)oldParentWindow; + while (parent) + { + if (([parent styleMask] & NSWindowStyleMaskFullScreen) != 0) + { + [ourView toggleFullScreen:self]; + break; + } + parent = [parent parentWindow]; + } + } +#endif + } + + if (parentFrame) + { + NSWindow *parentWindow = [FRAME_NS_VIEW (parentFrame) window]; + +#ifdef NS_IMPL_COCOA +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 + if ([ourView respondsToSelector:@selector (toggleFullScreen)] +#endif + /* Child frames must not be fullscreen. */ + if ([ourView fsIsNative] && [ourView isFullscreen]) + [ourView toggleFullScreen:self]; +#endif + + [parentWindow addChildWindow:self + ordered:NSWindowAbove]; + } + + /* Check our child windows are configured correctly. */ + FOR_EACH_FRAME (tail, frame) + { + if (FRAME_PARENT_FRAME (XFRAME (frame)) == ourFrame) + [(EmacsWindow *)[FRAME_NS_VIEW (XFRAME (frame)) window] setParentChildRelationships]; + } +} + + /* It seems the only way to reorder child frames is by removing them from the parent and then reattaching them in the correct order. */ @@ -8753,6 +8467,16 @@ not_in_argv (NSString *arg) } +#ifdef NS_IMPL_GNUSTEP +/* orderedIndex isn't yet available in GNUstep, but it seems pretty + easy to implement. */ +- (NSInteger) orderedIndex +{ + return [[NSApp orderedWindows] indexOfObjectIdenticalTo:self]; +} +#endif + + /* The array returned by [NSWindow parentWindow] may already be sorted, but the documentation doesn't tell us whether or not it is, so to be safe we'll sort it. */ @@ -9121,22 +8845,15 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) { return !FRAME_NO_ACCEPT_FOCUS (((EmacsView *)[self delegate])->emacsframe); } -@end /* EmacsWindow */ - - -@implementation EmacsFSWindow - -- (BOOL)canBecomeKeyWindow -{ - return YES; -} - (BOOL)canBecomeMainWindow + /* Required for fullscreen and undecorated windows. */ { return YES; } -@end +@end /* EmacsWindow */ + /* ========================================================================== @@ -9635,7 +9352,7 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) @end /* EmacsScroller */ -#ifdef NS_DRAW_TO_BUFFER +#ifdef NS_IMPL_COCOA /* ========================================================================== @@ -9643,7 +9360,7 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) ========================================================================== */ -@implementation EmacsSurface +@implementation EmacsLayer /* An IOSurface is a pixel buffer that is efficiently copied to VRAM @@ -9656,116 +9373,159 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) ability to draw to the screen at any time, we need to keep a cache of multiple surfaces that we can use at will. - The EmacsSurface class maintains this cache of surfaces, and + The EmacsLayer class maintains this cache of surfaces, and handles the conversion to a CGGraphicsContext that AppKit can use to draw on. The cache is simple: if a free surface is found it is removed from - the cache and set as the "current" surface. Once Emacs is done - with drawing to the current surface, the previous surface that was - drawn to is added to the cache for reuse, and the current one is - set as the last surface. If no free surfaces are found in the - cache then a new one is created. + the cache and set as the "current" surface. Emacs draws to the + surface and when the layer wants to update the screen we set it's + contents to the surface and then add it back on to the end of the + cache. If no free surfaces are found in the cache then a new one + is created. */ - When AppKit wants to update the screen, we provide it with the last - surface, as that has the most recent data. +#define CACHE_MAX_SIZE 2 - FIXME: It is possible for the cache to grow if Emacs draws faster - than the surfaces can be drawn to the screen, so there should - probably be some sort of pruning job that removes excess - surfaces. */ +- (id) initWithColorSpace: (CGColorSpaceRef)cs +{ + NSTRACE ("[EmacsLayer initWithColorSpace:]"); + self = [super init]; + if (self) + { + cache = [[NSMutableArray arrayWithCapacity:CACHE_MAX_SIZE] retain]; + colorSpace = cs; + } + else + { + return nil; + } -- (id) initWithSize: (NSSize)s - ColorSpace: (CGColorSpaceRef)cs -{ - NSTRACE ("[EmacsSurface initWithSize:ColorSpace:]"); + return self; +} - [super init]; - cache = [[NSMutableArray arrayWithCapacity:3] retain]; - size = s; +- (void) setColorSpace: (CGColorSpaceRef)cs +{ + /* We don't need to clear the cache because the new colorspace will + be used next time we create a new context. */ colorSpace = cs; - - return self; } - (void) dealloc { - if (context) - CGContextRelease (context); + [self releaseSurfaces]; + [cache release]; - if (currentSurface) - CFRelease (currentSurface); - if (lastSurface) - CFRelease (lastSurface); + [super dealloc]; +} - for (id object in cache) - CFRelease ((IOSurfaceRef)object); - [cache removeAllObjects]; +- (void) releaseSurfaces +{ + [self setContents:nil]; + [self releaseContext]; - [super dealloc]; + if (currentSurface) + { + CFRelease (currentSurface); + currentSurface = nil; + } + + if (cache) + { + for (id object in cache) + CFRelease ((IOSurfaceRef)object); + + [cache removeAllObjects]; + } } -/* Return the size values our cached data is using. */ -- (NSSize) getSize +/* Check whether the current bounds match the IOSurfaces we are using. + If they do return YES, otherwise NO. */ +- (BOOL) checkDimensions { - return size; + int width = NSWidth ([self bounds]) * [self contentsScale]; + int height = NSHeight ([self bounds]) * [self contentsScale]; + IOSurfaceRef s = currentSurface ? currentSurface + : (IOSurfaceRef)[cache firstObject]; + + return !s || (IOSurfaceGetWidth (s) == width + && IOSurfaceGetHeight (s) == height); } -/* Return a CGContextRef that can be used for drawing to the screen. - This must ALWAYS be paired with a call to releaseContext, and the - calls cannot be nested. */ +/* Return a CGContextRef that can be used for drawing to the screen. */ - (CGContextRef) getContext { - IOSurfaceRef surface = NULL; + CGFloat scale = [self contentsScale]; - NSTRACE ("[EmacsSurface getContextWithSize:]"); - NSTRACE_MSG (@"IOSurface count: %lu", [cache count] + (lastSurface ? 1 : 0)); + NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer getContext]"); + NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (currentSurface ? 1 : 0)); - for (id object in cache) - { - if (!IOSurfaceIsInUse ((IOSurfaceRef)object)) - { - surface = (IOSurfaceRef)object; - [cache removeObject:object]; - break; - } - } + if (![self checkDimensions]) + [self releaseSurfaces]; - if (!surface) + if (!context) { - int bytesPerRow = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow, - size.width * 4); + IOSurfaceRef surface = NULL; + int width = NSWidth ([self bounds]) * scale; + int height = NSHeight ([self bounds]) * scale; - surface = IOSurfaceCreate - ((CFDictionaryRef)@{(id)kIOSurfaceWidth:[NSNumber numberWithInt:size.width], - (id)kIOSurfaceHeight:[NSNumber numberWithInt:size.height], - (id)kIOSurfaceBytesPerRow:[NSNumber numberWithInt:bytesPerRow], - (id)kIOSurfaceBytesPerElement:[NSNumber numberWithInt:4], - (id)kIOSurfacePixelFormat:[NSNumber numberWithUnsignedInt:'BGRA']}); - } + for (id object in cache) + { + if (!IOSurfaceIsInUse ((IOSurfaceRef)object)) + { + surface = (IOSurfaceRef)object; + [cache removeObject:object]; + break; + } + } - IOReturn lockStatus = IOSurfaceLock (surface, 0, nil); - if (lockStatus != kIOReturnSuccess) - NSLog (@"Failed to lock surface: %x", lockStatus); + if (!surface && [cache count] >= CACHE_MAX_SIZE) + { + /* Just grab the first one off the cache. This may result + in tearing effects. The alternative is to wait for one + of the surfaces to become free. */ + surface = (IOSurfaceRef)[cache firstObject]; + [cache removeObject:(id)surface]; + } + else if (!surface) + { + int bytesPerRow = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow, + width * 4); + + surface = IOSurfaceCreate + ((CFDictionaryRef)@{(id)kIOSurfaceWidth:[NSNumber numberWithInt:width], + (id)kIOSurfaceHeight:[NSNumber numberWithInt:height], + (id)kIOSurfaceBytesPerRow:[NSNumber numberWithInt:bytesPerRow], + (id)kIOSurfaceBytesPerElement:[NSNumber numberWithInt:4], + (id)kIOSurfacePixelFormat:[NSNumber numberWithUnsignedInt:'BGRA']}); + } - [self copyContentsTo:surface]; + IOReturn lockStatus = IOSurfaceLock (surface, 0, nil); + if (lockStatus != kIOReturnSuccess) + NSLog (@"Failed to lock surface: %x", lockStatus); - currentSurface = surface; + [self copyContentsTo:surface]; + + currentSurface = surface; + + context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface), + IOSurfaceGetWidth (currentSurface), + IOSurfaceGetHeight (currentSurface), + 8, + IOSurfaceGetBytesPerRow (currentSurface), + colorSpace, + (kCGImageAlphaPremultipliedFirst + | kCGBitmapByteOrder32Host)); + + CGContextTranslateCTM(context, 0, IOSurfaceGetHeight (currentSurface)); + CGContextScaleCTM(context, scale, -scale); + } - context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface), - IOSurfaceGetWidth (currentSurface), - IOSurfaceGetHeight (currentSurface), - 8, - IOSurfaceGetBytesPerRow (currentSurface), - colorSpace, - (kCGImageAlphaPremultipliedFirst - | kCGBitmapByteOrder32Host)); return context; } @@ -9774,7 +9534,10 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) IOSurface, so it will be sent to VRAM. */ - (void) releaseContext { - NSTRACE ("[EmacsSurface releaseContextAndGetSurface]"); + NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer releaseContext]"); + + if (!context) + return; CGContextRelease (context); context = NULL; @@ -9782,22 +9545,34 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) IOReturn lockStatus = IOSurfaceUnlock (currentSurface, 0, nil); if (lockStatus != kIOReturnSuccess) NSLog (@"Failed to unlock surface: %x", lockStatus); - - /* Put lastSurface back on the end of the cache. It may not have - been displayed on the screen yet, but we probably want the new - data and not some stale data anyway. */ - if (lastSurface) - [cache addObject:(id)lastSurface]; - lastSurface = currentSurface; - currentSurface = NULL; } -/* Get the IOSurface that we want to draw to the screen. */ -- (IOSurfaceRef) getSurface +- (void) display { - /* lastSurface always contains the most up-to-date and complete data. */ - return lastSurface; + NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer display]"); + + if (context) + { + [self releaseContext]; + +#if CACHE_MAX_SIZE == 1 + /* This forces the layer to see the surface as updated. */ + [self setContents:nil]; +#endif + + [self setContents:(id)currentSurface]; + + /* Put currentSurface back on the end of the cache. */ + [cache addObject:(id)currentSurface]; + currentSurface = NULL; + + /* Schedule a run of getContext so that if Emacs is idle it will + perform the buffer copy, etc. */ + [self performSelectorOnMainThread:@selector (getContext) + withObject:nil + waitUntilDone:NO]; + } } @@ -9807,19 +9582,20 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) - (void) copyContentsTo: (IOSurfaceRef) destination { IOReturn lockStatus; + IOSurfaceRef source = (IOSurfaceRef)[self contents]; void *sourceData, *destinationData; int numBytes = IOSurfaceGetAllocSize (destination); - NSTRACE ("[EmacsSurface copyContentsTo:]"); + NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer copyContentsTo:]"); - if (! lastSurface) + if (!source || source == destination) return; - lockStatus = IOSurfaceLock (lastSurface, kIOSurfaceLockReadOnly, nil); + lockStatus = IOSurfaceLock (source, kIOSurfaceLockReadOnly, nil); if (lockStatus != kIOReturnSuccess) NSLog (@"Failed to lock source surface: %x", lockStatus); - sourceData = IOSurfaceGetBaseAddress (lastSurface); + sourceData = IOSurfaceGetBaseAddress (source); destinationData = IOSurfaceGetBaseAddress (destination); /* Since every IOSurface should have the exact same settings, a @@ -9827,16 +9603,17 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) the other. */ memcpy (destinationData, sourceData, numBytes); - lockStatus = IOSurfaceUnlock (lastSurface, kIOSurfaceLockReadOnly, nil); + lockStatus = IOSurfaceUnlock (source, kIOSurfaceLockReadOnly, nil); if (lockStatus != kIOReturnSuccess) NSLog (@"Failed to unlock source surface: %x", lockStatus); } +#undef CACHE_MAX_SIZE -@end /* EmacsSurface */ +@end /* EmacsLayer */ -#endif +#endif /* NS_IMPL_COCOA */ #ifdef NS_IMPL_GNUSTEP |