/* NeXT/Open/GNUstep / macOS communication module.      -*- coding: utf-8 -*-

Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2024 Free Software
Foundation, Inc.

This file is part of GNU Emacs.

GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */

/*
Originally by Carl Edman
Updated by Christian Limpach (chris@nice.ch)
OpenStep/Rhapsody port by Scott Bender (sbender@harmony-ds.com)
macOS/Aqua port by Christophe de Dinechin (descubes@earthlink.net)
GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
*/

/* This should be the first include, as it may set up #defines affecting
   interpretation of even the system includes.  */
#include <config.h>

#include <fcntl.h>
#include <math.h>
#include <pthread.h>
#include <sys/types.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>

#include <c-ctype.h>
#include <c-strcase.h>
#include <ftoastr.h>

#include "lisp.h"
#include "blockinput.h"
#include "sysselect.h"
#include "nsterm.h"
#include "systime.h"
#include "character.h"
#include "xwidget.h"
#include "fontset.h"
#include "composite.h"
#include "ccl.h"

#include "termhooks.h"
#include "termchar.h"
#include "menu.h"
#include "window.h"
#include "keyboard.h"
#include "buffer.h"
#include "font.h"
#include "pdumper.h"

#ifdef NS_IMPL_GNUSTEP
#include "process.h"
#import <GNUstepGUI/GSDisplayServer.h>
#endif

#ifdef NS_IMPL_COCOA
#include "macfont.h"
#include <Carbon/Carbon.h>
#include <IOSurface/IOSurface.h>
#endif

static EmacsMenu *dockMenu;
#ifdef NS_IMPL_COCOA
static EmacsMenu *mainMenu;
#endif

/* The last known monitor attributes list.  */
static Lisp_Object last_known_monitors;

/* ==========================================================================

   NSTRACE, Trace support.

   ========================================================================== */

#if NSTRACE_ENABLED

/* The following use "volatile" since they can be accessed from
   parallel threads.  */
volatile int nstrace_num;
volatile int nstrace_depth;

/* When 0, no trace is emitted.  This is used by NSTRACE_WHEN and
   NSTRACE_UNLESS to silence functions called.

   TODO: This should really be a thread-local variable, to avoid that
   a function with disabled trace thread silence trace output in
   another.  However, in practice this seldom is a problem.  */
volatile int nstrace_enabled_global = 1;

/* Called when nstrace_enabled goes out of scope.  */
void
nstrace_leave (int *pointer_to_nstrace_enabled)
{
  if (*pointer_to_nstrace_enabled)
    --nstrace_depth;
}


/* Called when nstrace_saved_enabled_global goes out of scope.  */
void
nstrace_restore_global_trace_state (int *pointer_to_saved_enabled_global)
{
  nstrace_enabled_global = *pointer_to_saved_enabled_global;
}


const char *
nstrace_fullscreen_type_name (int fs_type)
{
  switch (fs_type)
    {
    case -1:
      return "-1";
    case FULLSCREEN_NONE:
      return "FULLSCREEN_NONE";
    case FULLSCREEN_WIDTH:
      return "FULLSCREEN_WIDTH";
    case FULLSCREEN_HEIGHT:
      return "FULLSCREEN_HEIGHT";
    case FULLSCREEN_BOTH:
      return "FULLSCREEN_BOTH";
    case FULLSCREEN_MAXIMIZED:
      return "FULLSCREEN_MAXIMIZED";
    default:
      return "FULLSCREEN_?????";
    }
}
#endif


/* ==========================================================================

   NSColor, EmacsColor category.

   ========================================================================== */
@implementation NSColor (EmacsColor)
+ (NSColor *)colorForEmacsRed:(CGFloat)red green:(CGFloat)green
                         blue:(CGFloat)blue alpha:(CGFloat)alpha
{
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  if (ns_use_srgb_colorspace
      && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7)
    return [NSColor colorWithSRGBRed: red
                               green: green
                                blue: blue
                               alpha: alpha];
#endif
  return [NSColor colorWithCalibratedRed: red
                                   green: green
                                    blue: blue
                                   alpha: alpha];
}

- (NSColor *)colorUsingDefaultColorSpace
{
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  if (ns_use_srgb_colorspace
      && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7)
    return [self colorUsingColorSpace: [NSColorSpace sRGBColorSpace]];
#endif
  return [self colorUsingColorSpace: [NSColorSpace genericRGBColorSpace]];
}

+ (NSColor *)colorWithUnsignedLong:(unsigned long)c
{
  EmacsCGFloat a = (double)((c >> 24) & 0xff) / 255.0;
  EmacsCGFloat r = (double)((c >> 16) & 0xff) / 255.0;
  EmacsCGFloat g = (double)((c >> 8) & 0xff) / 255.0;
  EmacsCGFloat b = (double)(c & 0xff) / 255.0;

  return [NSColor colorForEmacsRed:r green:g blue:b alpha:a];
}

- (unsigned long)unsignedLong
{
  EmacsCGFloat r, g, b, a;
  [self getRed:&r green:&g blue:&b alpha:&a];

  return (((unsigned long) (a * 255)) << 24)
    | (((unsigned long) (r * 255)) << 16)
    | (((unsigned long) (g * 255)) << 8)
    | ((unsigned long) (b * 255));
}

@end

/* ==========================================================================

    Local declarations

   ========================================================================== */

/* Convert a symbol indexed with an NSxxx value to a value as defined
   in keyboard.c (lispy_function_key). I hope this is a correct way
   of doing things...  */
static unsigned convert_ns_to_X_keysym[] =
{
  NSHomeFunctionKey,            0x50,
  NSLeftArrowFunctionKey,       0x51,
  NSUpArrowFunctionKey,         0x52,
  NSRightArrowFunctionKey,      0x53,
  NSDownArrowFunctionKey,       0x54,
  NSPageUpFunctionKey,          0x55,
  NSPageDownFunctionKey,        0x56,
  NSEndFunctionKey,             0x57,
  NSBeginFunctionKey,           0x58,
  NSSelectFunctionKey,          0x60,
  NSPrintFunctionKey,           0x61,
  NSClearLineFunctionKey,       0x0B,
  NSExecuteFunctionKey,         0x62,
  NSInsertFunctionKey,          0x63,
  NSUndoFunctionKey,            0x65,
  NSRedoFunctionKey,            0x66,
  NSMenuFunctionKey,            0x67,
  NSFindFunctionKey,            0x68,
  NSHelpFunctionKey,            0x6A,
  NSBreakFunctionKey,           0x6B,

  NSF1FunctionKey,              0xBE,
  NSF2FunctionKey,              0xBF,
  NSF3FunctionKey,              0xC0,
  NSF4FunctionKey,              0xC1,
  NSF5FunctionKey,              0xC2,
  NSF6FunctionKey,              0xC3,
  NSF7FunctionKey,              0xC4,
  NSF8FunctionKey,              0xC5,
  NSF9FunctionKey,              0xC6,
  NSF10FunctionKey,             0xC7,
  NSF11FunctionKey,             0xC8,
  NSF12FunctionKey,             0xC9,
  NSF13FunctionKey,             0xCA,
  NSF14FunctionKey,             0xCB,
  NSF15FunctionKey,             0xCC,
  NSF16FunctionKey,             0xCD,
  NSF17FunctionKey,             0xCE,
  NSF18FunctionKey,             0xCF,
  NSF19FunctionKey,             0xD0,
  NSF20FunctionKey,             0xD1,
  NSF21FunctionKey,             0xD2,
  NSF22FunctionKey,             0xD3,
  NSF23FunctionKey,             0xD4,
  NSF24FunctionKey,             0xD5,

  NSBackspaceCharacter,         0x08,  /* 8: Not on some KBs.  */
  NSDeleteCharacter,            0xFF,  /* 127: Big 'delete' key upper right.  */
  NSDeleteFunctionKey,          0x9F,  /* 63272: Del forw key off main array.  */

  NSTabCharacter,		0x09,
  0x19,				0x09,  /* left tab->regular since pass shift */
  NSCarriageReturnCharacter,	0x0D,
  NSNewlineCharacter,		0x0D,
  NSEnterCharacter,		0x8D,

  0x41|NSEventModifierFlagNumericPad,	0xAE,  /* KP_Decimal */
  0x43|NSEventModifierFlagNumericPad,	0xAA,  /* KP_Multiply */
  0x45|NSEventModifierFlagNumericPad,	0xAB,  /* KP_Add */
  0x4B|NSEventModifierFlagNumericPad,	0xAF,  /* KP_Divide */
  0x4E|NSEventModifierFlagNumericPad,	0xAD,  /* KP_Subtract */
  0x51|NSEventModifierFlagNumericPad,	0xBD,  /* KP_Equal */
  0x52|NSEventModifierFlagNumericPad,	0xB0,  /* KP_0 */
  0x53|NSEventModifierFlagNumericPad,	0xB1,  /* KP_1 */
  0x54|NSEventModifierFlagNumericPad,	0xB2,  /* KP_2 */
  0x55|NSEventModifierFlagNumericPad,	0xB3,  /* KP_3 */
  0x56|NSEventModifierFlagNumericPad,	0xB4,  /* KP_4 */
  0x57|NSEventModifierFlagNumericPad,	0xB5,  /* KP_5 */
  0x58|NSEventModifierFlagNumericPad,	0xB6,  /* KP_6 */
  0x59|NSEventModifierFlagNumericPad,	0xB7,  /* KP_7 */
  0x5B|NSEventModifierFlagNumericPad,	0xB8,  /* KP_8 */
  0x5C|NSEventModifierFlagNumericPad,	0xB9,  /* KP_9 */

  0x1B,				0x1B   /* escape */
};

/* On macOS picks up the default NSGlobalDomain AppleAntiAliasingThreshold,
   the maximum font size to NOT antialias.  On GNUstep there is currently
   no way to control this behavior.  */
float ns_antialias_threshold;

NSArray *ns_send_types = 0, *ns_return_types = 0;
static NSArray *ns_drag_types = 0;
NSString *ns_app_name = @"Emacs";  /* default changed later */

/* Display variables */
struct ns_display_info *x_display_list; /* Chain of existing displays */
long context_menu_value = 0;

/* display update */
static struct frame *ns_updating_frame;
static int ns_window_num = 0;
static BOOL gsaved = NO;
#ifdef NS_IMPL_COCOA
static BOOL ns_menu_bar_is_hidden = NO;
#endif

/* event loop */
static BOOL send_appdefined = YES;
#define NO_APPDEFINED_DATA (-8)
static int last_appdefined_event_data = NO_APPDEFINED_DATA;
static NSTimer *timed_entry = 0;
static NSTimer *scroll_repeat_entry = nil;
static fd_set select_readfds, select_writefds;
enum { SELECT_HAVE_READ = 1, SELECT_HAVE_WRITE = 2, SELECT_HAVE_TMO = 4 };
static int select_nfds = 0, select_valid = 0;
static struct timespec select_timeout = { 0, 0 };
static int selfds[2] = { -1, -1 };
static pthread_mutex_t select_mutex;
static NSAutoreleasePool *outerpool;
static struct input_event *emacs_event = NULL;
static struct input_event *q_event_ptr = NULL;
static int n_emacs_events_pending = 0;
static NSMutableArray *ns_pending_files, *ns_pending_service_names,
  *ns_pending_service_args;
static BOOL ns_do_open_file = NO;
static BOOL ns_last_use_native_fullscreen;

/* Non-zero means that a HELP_EVENT has been generated since Emacs
   start.  */

static BOOL any_help_event_p = NO;

static struct {
  struct input_event *q;
  int nr, cap;
} hold_event_q = {
  NULL, 0, 0
};

/* Convert modifiers in a NeXTstep event to emacs style modifiers.  */
#define NS_FUNCTION_KEY_MASK 0x800000
#define NSLeftControlKeyMask    (0x000001 | NSEventModifierFlagControl)
#define NSRightControlKeyMask   (0x002000 | NSEventModifierFlagControl)
#define NSLeftCommandKeyMask    (0x000008 | NSEventModifierFlagCommand)
#define NSRightCommandKeyMask   (0x000010 | NSEventModifierFlagCommand)
#define NSLeftAlternateKeyMask  (0x000020 | NSEventModifierFlagOption)
#define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption)

/* MODIFIER if a symbol; otherwise its property KIND, if a symbol.  */
static Lisp_Object
mod_of_kind (Lisp_Object modifier, Lisp_Object kind)
{
  if (SYMBOLP (modifier))
    return modifier;
  else
    {
      Lisp_Object val = plist_get (modifier, kind);
      return SYMBOLP (val) ? val : Qnil;
    }
}

static unsigned int
ev_modifiers_helper (unsigned int flags, unsigned int left_mask,
                     unsigned int right_mask, unsigned int either_mask,
                     Lisp_Object left_modifier, Lisp_Object right_modifier)
{
  unsigned int modifiers = 0;

  if (flags & either_mask)
    {
      BOOL left_key = (flags & left_mask) == left_mask;
      BOOL right_key = (flags & right_mask) == right_mask
        && ! EQ (right_modifier, Qleft);

      if (right_key)
        modifiers |= parse_solitary_modifier (right_modifier);

      /* GNUstep (and possibly macOS in certain circumstances) doesn't
         differentiate between the left and right keys, so if we can't
         identify which key it is, we use the left key setting.  */
      if (left_key || ! right_key)
        modifiers |= parse_solitary_modifier (left_modifier);
    }

  return modifiers;
}

#define EV_MODIFIERS2(flags, kind)                                      \
  (((flags & NSEventModifierFlagHelp) ?                                 \
    hyper_modifier : 0)                                                 \
   | ((flags & NSEventModifierFlagShift) ?                              \
      shift_modifier : 0)                                               \
   | ((flags & NS_FUNCTION_KEY_MASK)                                    \
      ? parse_solitary_modifier (mod_of_kind (ns_function_modifier,     \
                                              kind))                    \
      : 0)                                                              \
   | ev_modifiers_helper (flags, NSLeftControlKeyMask,                  \
                          NSRightControlKeyMask,                        \
                          NSEventModifierFlagControl,                   \
                          mod_of_kind (ns_control_modifier, kind),      \
                          mod_of_kind (ns_right_control_modifier,       \
                                       kind))                           \
   | ev_modifiers_helper (flags, NSLeftCommandKeyMask,                  \
                          NSRightCommandKeyMask,                        \
                          NSEventModifierFlagCommand,                   \
                          mod_of_kind (ns_command_modifier, kind),      \
                          mod_of_kind (ns_right_command_modifier,       \
                                       kind))                           \
   | ev_modifiers_helper (flags, NSLeftAlternateKeyMask,                \
                          NSRightAlternateKeyMask,                      \
                          NSEventModifierFlagOption,                    \
                          mod_of_kind (ns_alternate_modifier, kind),    \
                          mod_of_kind (ns_right_alternate_modifier,     \
                                       kind)))

#define EV_MODIFIERS(e) EV_MODIFIERS2 ([e modifierFlags], QCmouse)

#define EV_UDMODIFIERS(e)                                      \
    ((([e type] == NSEventTypeLeftMouseDown) ? down_modifier : 0)       \
     | (([e type] == NSEventTypeRightMouseDown) ? down_modifier : 0)    \
     | (([e type] == NSEventTypeOtherMouseDown) ? down_modifier : 0)    \
     | (([e type] == NSEventTypeLeftMouseDragged) ? down_modifier : 0)  \
     | (([e type] == NSEventTypeRightMouseDragged) ? down_modifier : 0) \
     | (([e type] == NSEventTypeOtherMouseDragged) ? down_modifier : 0) \
     | (([e type] == NSEventTypeLeftMouseUp)   ? up_modifier   : 0)     \
     | (([e type] == NSEventTypeRightMouseUp)   ? up_modifier   : 0)    \
     | (([e type] == NSEventTypeOtherMouseUp)   ? up_modifier   : 0))

#define EV_BUTTON(e)                                                         \
    ((([e type] == NSEventTypeLeftMouseDown) || ([e type] == NSEventTypeLeftMouseUp)) ? 0 :    \
      (([e type] == NSEventTypeRightMouseDown) || ([e type] == NSEventTypeRightMouseUp)) ? 2 : \
     [e buttonNumber] - 1)

/* Convert the time field to a timestamp in milliseconds.  */
#define EV_TIMESTAMP(e) ([e timestamp] * 1000)

/* This is a piece of code which is common to all the event handling
   methods.  Maybe it should even be a function.  */
#define EV_TRAILER(e)						\
  {								\
    XSETFRAME (emacs_event->frame_or_window, emacsframe);	\
    EV_TRAILER2 (e);						\
  }

#define EV_TRAILER2(e)                                                  \
  {                                                                     \
    if (e) emacs_event->timestamp = EV_TIMESTAMP (e);			\
    if (q_event_ptr)							\
      {									\
	Lisp_Object tem = Vinhibit_quit;				\
	Vinhibit_quit = Qt;						\
	n_emacs_events_pending++;					\
	kbd_buffer_store_event_hold (emacs_event, q_event_ptr);		\
	Vinhibit_quit = tem;						\
      }									\
    else								\
      hold_event (emacs_event);						\
    EVENT_INIT (*emacs_event);						\
    ns_send_appdefined (-1);						\
  }


/* TODO: Get rid of need for these forward declarations.  */
static void ns_condemn_scroll_bars (struct frame *f);
static void ns_judge_scroll_bars (struct frame *f);


/* ==========================================================================

    Utilities

   ========================================================================== */

void
ns_init_events (struct input_event *ev)
{
  EVENT_INIT (*ev);
  emacs_event = ev;
}

void
ns_finish_events (void)
{
  emacs_event = NULL;
}

static void
hold_event (struct input_event *event)
{
  if (hold_event_q.nr == hold_event_q.cap)
    {
      if (hold_event_q.cap == 0) hold_event_q.cap = 10;
      else hold_event_q.cap *= 2;
      hold_event_q.q =
        xrealloc (hold_event_q.q, hold_event_q.cap * sizeof *hold_event_q.q);
    }

  hold_event_q.q[hold_event_q.nr++] = *event;
  /* Make sure ns_read_socket is called, i.e. we have input.  */
  raise (SIGIO);
  send_appdefined = YES;
}

static Lisp_Object
append2 (Lisp_Object list, Lisp_Object item)
/* --------------------------------------------------------------------------
   Utility to append to a list
   -------------------------------------------------------------------------- */
{
  return nconc2 (list, list (item));
}


const char *
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.

   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 *root = [bundle bundlePath];
  NSString *original = [NSString stringWithUTF8String:epath];
  NSString *fixedPath = [NSString pathWithComponents:
                                    [NSArray arrayWithObjects:
                                               root, original, nil]];
  NSFileManager *fileManager = [NSFileManager defaultManager];

  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.  */

#endif

  return epath;
}


void
ns_init_locale (void)
/* macOS doesn't set any environment variables for the locale when run
   from the GUI. Get the locale from the OS and set LANG.  */
{
  NSTRACE ("ns_init_locale");

  /* Either use LANG, if set, or try to construct LANG from
     NSLocale.  */
  const char *lang = getenv ("LANG");
  if (lang == NULL || *lang == 0)
    {
      const NSLocale *locale = [NSLocale currentLocale];
      const NSString *localeID = [NSString stringWithFormat:@"%@.UTF-8",
					   [locale localeIdentifier]];
      lang = [localeID UTF8String];
    }

  /* Check if LANG can be used for initializing the locale.  If not,
     use a default setting.  Note that Emacs' main will undo the
     setlocale below, initializing the locale from the
     environment.  */
  if (setlocale (LC_ALL, lang) == NULL)
    {
      const char *const default_lang = "en_US.UTF-8";
      fprintf (stderr, "LANG=%s cannot be used, using %s instead.\n",
	       lang, default_lang);
      lang = default_lang;
    }

  setenv ("LANG", lang, 1);
}


void
ns_release_object (void *obj)
/* --------------------------------------------------------------------------
    Release an object (callable from C)
   -------------------------------------------------------------------------- */
{
    [(id)obj release];
}


void
ns_retain_object (void *obj)
/* --------------------------------------------------------------------------
     Retain an object (callable from C)
   -------------------------------------------------------------------------- */
{
    [(id)obj retain];
}


void *
ns_alloc_autorelease_pool (void)
/* --------------------------------------------------------------------------
     Allocate a pool for temporary objects (callable from C)
   -------------------------------------------------------------------------- */
{
  return [[NSAutoreleasePool alloc] init];
}


void
ns_release_autorelease_pool (void *pool)
/* --------------------------------------------------------------------------
     Free a pool and temporary objects it refers to (callable from C)
   -------------------------------------------------------------------------- */
{
  ns_release_object (pool);
}


static BOOL
ns_menu_bar_should_be_hidden (void)
/* True, if the menu bar should be hidden.  */
{
  return !NILP (ns_auto_hide_menu_bar)
    && [NSApp respondsToSelector:@selector(setPresentationOptions:)];
}


struct EmacsMargins
{
  CGFloat top;
  CGFloat bottom;
  CGFloat left;
  CGFloat right;
};


static struct EmacsMargins
ns_screen_margins (NSScreen *screen)
/* The parts of SCREEN used by the operating system.  */
{
  NSTRACE ("ns_screen_margins");

  struct EmacsMargins margins;

  NSRect screenFrame = [screen frame];
  NSRect screenVisibleFrame = [screen visibleFrame];

  /* Sometimes, visibleFrame isn't up-to-date with respect to a hidden
     menu bar, check this explicitly.  */
  if (ns_menu_bar_should_be_hidden())
    {
      margins.top = 0;
    }
  else
    {
      CGFloat frameTop = screenFrame.origin.y + screenFrame.size.height;
      CGFloat visibleFrameTop = (screenVisibleFrame.origin.y
                                 + screenVisibleFrame.size.height);

      margins.top = frameTop - visibleFrameTop;
    }

  {
    CGFloat frameRight = screenFrame.origin.x + screenFrame.size.width;
    CGFloat visibleFrameRight = (screenVisibleFrame.origin.x
                                 + screenVisibleFrame.size.width);
    margins.right = frameRight - visibleFrameRight;
  }

  margins.bottom = screenVisibleFrame.origin.y - screenFrame.origin.y;
  margins.left   = screenVisibleFrame.origin.x - screenFrame.origin.x;

  NSTRACE_MSG ("left:%g right:%g top:%g bottom:%g",
               margins.left,
               margins.right,
               margins.top,
               margins.bottom);

  return margins;
}


/* A screen margin between 1 and DOCK_IGNORE_LIMIT (inclusive) is
   assumed to contain a hidden dock.  macOS currently use 4 pixels for
   this, however, to be future compatible, a larger value is used.  */
#define DOCK_IGNORE_LIMIT 6

static struct EmacsMargins
ns_screen_margins_ignoring_hidden_dock (NSScreen *screen)
/* The parts of SCREEN used by the operating system, excluding the parts
   reserved for a hidden dock.  */
{
  NSTRACE ("ns_screen_margins_ignoring_hidden_dock");

  struct EmacsMargins margins = ns_screen_margins(screen);

  /* macOS (currently) reserved 4 pixels along the edge where a hidden
     dock is located.  Unfortunately, it's not possible to find the
     location and information about if the dock is hidden.  Instead,
     it is assumed that if the margin of an edge is less than
     DOCK_IGNORE_LIMIT, it contains a hidden dock.  */
  if (margins.left <= DOCK_IGNORE_LIMIT)
    {
      margins.left = 0;
    }
  if (margins.right <= DOCK_IGNORE_LIMIT)
    {
      margins.right = 0;
    }
  if (margins.top <= DOCK_IGNORE_LIMIT)
    {
      margins.top = 0;
    }
  /* Note: This doesn't occur in current versions of macOS, but
     included for completeness and future compatibility.  */
  if (margins.bottom <= DOCK_IGNORE_LIMIT)
    {
      margins.bottom = 0;
    }

  NSTRACE_MSG ("left:%g right:%g top:%g bottom:%g",
               margins.left,
               margins.right,
               margins.top,
               margins.bottom);

  return margins;
}


static CGFloat
ns_menu_bar_height (NSScreen *screen)
/* The height of the menu bar, if visible.

   Note: Don't use this when fullscreen is enabled -- the screen
   sometimes includes, sometimes excludes the menu bar area.  */
{
  struct EmacsMargins margins = ns_screen_margins(screen);

  CGFloat res = margins.top;

  NSTRACE ("ns_menu_bar_height " NSTRACE_FMT_RETURN " %.0f", res);

  return res;
}


/* Get the frame rect, in system coordinates, of the parent window or,
   if there is no parent window, the main screen.  */
static inline NSRect
ns_parent_window_rect (struct frame *f)
{
  NSRect parentRect;

  if (FRAME_PARENT_FRAME (f) != NULL)
    {
      EmacsView *parentView = FRAME_NS_VIEW (FRAME_PARENT_FRAME (f));
      parentRect = [parentView convertRect:[parentView frame]
                                    toView:nil];

#if defined (NS_IMPL_COCOA) && !defined (MAC_OS_X_VERSION_10_7)
      parentRect.origin = [[parentView window] convertBaseToScreen:parentRect.origin];
#elif defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED < 1070
      if ([[parentView window]
             respondsToSelector:@selector(convertRectToScreen:)])
        parentRect = [[parentView window] convertRectToScreen:parentRect];
      else
        parentRect.origin = [[parentView window] convertBaseToScreen:parentRect.origin];
#else
      parentRect = [[parentView window] convertRectToScreen:parentRect];
#endif
    }
  else
    parentRect = [[[NSScreen screens] objectAtIndex:0] frame];

  return parentRect;
}

/* Calculate system coordinates of the left and top of the parent
   window or, if there is no parent window, the main screen.  */
#define NS_PARENT_WINDOW_LEFT_POS(f) NSMinX (ns_parent_window_rect (f))
#define NS_PARENT_WINDOW_TOP_POS(f) NSMaxY (ns_parent_window_rect (f))


static NSRect
ns_row_rect (struct window *w, struct glyph_row *row,
               enum glyph_row_area area)
/* Get the row as an NSRect.  */
{
  NSRect rect;
  int window_x, window_y, window_width;

  window_box (w, area, &window_x, &window_y, &window_width, 0);

  rect.origin.x = window_x;
  rect.origin.y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, row->y));
  rect.origin.y = max (rect.origin.y, window_y);
  rect.size.width = window_width;
  rect.size.height = row->visible_height;

  return rect;
}


double
ns_frame_scale_factor (struct frame *f)
{
#if defined (NS_IMPL_GNUSTEP) || !defined (MAC_OS_X_VERSION_10_7)
  return [[FRAME_NS_VIEW (f) window] userSpaceScaleFactor];
#elif MAC_OS_X_VERSION_MIN_REQUIRED < 1070
  if ([[FRAME_NS_VIEW (f) window]
            respondsToSelector:@selector(backingScaleFactor:)])
    return [[FRAME_NS_VIEW (f) window] backingScaleFactor];
  else
    return [[FRAME_NS_VIEW (f) window] userSpaceScaleFactor];
#else
  return [[FRAME_NS_VIEW (f) window] backingScaleFactor];
#endif
}


/* ==========================================================================

    Focus (clipping) and screen update

   ========================================================================== */

//
// Window constraining
// -------------------
//
// To ensure that the windows are not placed under the menu bar, they
// are typically moved by the call-back constrainFrameRect. However,
// by overriding it, it's possible to inhibit this, leaving the window
// in it's original position.
//
// It's possible to hide the menu bar. However, technically, it's only
// possible to hide it when the application is active. To ensure that
// this work properly, the menu bar and window constraining are
// deferred until the application becomes active.
//
// Even though it's not possible to manually move a window above the
// top of the screen, it is allowed if it's done programmatically,
// when the menu is hidden. This allows the editable area to cover the
// full screen height.
//
// Test cases
// ----------
//
// Use the following extra files:
//
//    init.el:
//       ;; Hide menu and place frame slightly above the top of the screen.
//       (setq ns-auto-hide-menu-bar t)
//       (set-frame-position (selected-frame) 0 -20)
//
// Test 1:
//
//    emacs -Q -l init.el
//
//    Result: No menu bar, and the title bar should be above the screen.
//
// Test 2:
//
//    emacs -Q
//
//    Result: Menu bar visible, frame placed immediately below the menu.
//

static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
{
  NSTRACE ("constrain_frame_rect(" NSTRACE_FMT_RECT ")",
             NSTRACE_ARG_RECT (frameRect));

  // --------------------
  // Collect information about the screen the frame is covering.
  //

  NSArray *screens = [NSScreen screens];
  NSUInteger nr_screens = [screens count];

  int i;

  // The height of the menu bar, if present in any screen the frame is
  // displayed in.
  int menu_bar_height = 0;

  // A rectangle covering all the screen the frame is displayed in.
  NSRect multiscreenRect = NSMakeRect(0, 0, 0, 0);
  for (i = 0; i < nr_screens; ++i )
    {
      NSScreen *s = [screens objectAtIndex: i];
      NSRect scrRect = [s frame];

      NSTRACE_MSG ("Screen %d: " NSTRACE_FMT_RECT,
                   i, NSTRACE_ARG_RECT (scrRect));

      if (NSIntersectionRect (frameRect, scrRect).size.height != 0)
        {
          multiscreenRect = NSUnionRect (multiscreenRect, scrRect);

          if (!isFullscreen)
            {
              CGFloat screen_menu_bar_height = ns_menu_bar_height (s);
              menu_bar_height = max(menu_bar_height, screen_menu_bar_height);
            }
        }
    }

  NSTRACE_RECT ("multiscreenRect", multiscreenRect);

  NSTRACE_MSG ("menu_bar_height: %d", menu_bar_height);

  if (multiscreenRect.size.width == 0
      || multiscreenRect.size.height == 0)
    {
      // Failed to find any monitor, give up.
      NSTRACE_MSG ("multiscreenRect empty");
      NSTRACE_RETURN_RECT (frameRect);
      return frameRect;
    }


  // --------------------
  // Find a suitable placement.
  //

  if (ns_menu_bar_should_be_hidden())
    {
      // When the menu bar is hidden, the user may place part of the
      // frame above the top of the screen, for example to hide the
      // title bar.
      //
      // Hence, keep the original position.
    }
  else
    {
      // Ensure that the frame is below the menu bar, or below the top
      // of the screen.
      //
      // This assume that the menu bar is placed at the top in the
      // rectangle that covers the monitors.  (It doesn't have to be,
      // but if it's not it's hard to do anything useful.)
      CGFloat topOfWorkArea = (multiscreenRect.origin.y
                               + multiscreenRect.size.height
                               - menu_bar_height);

      CGFloat topOfFrame = frameRect.origin.y + frameRect.size.height;
      if (topOfFrame > topOfWorkArea)
        {
          frameRect.origin.y -= topOfFrame - topOfWorkArea;
          NSTRACE_RECT ("After placement adjust", frameRect);
        }
    }

  // Include the following section to restrict frame to the screens.
  // (If so, update it to allow the frame to stretch down below the
  // screen.)
#if 0
  // --------------------
  // Ensure frame doesn't stretch below the screens.
  //

  CGFloat diff = multiscreenRect.origin.y - frameRect.origin.y;

  if (diff > 0)
    {
      frameRect.origin.y = multiscreenRect.origin.y;
      frameRect.size.height -= diff;
    }
#endif

  NSTRACE_RETURN_RECT (frameRect);
  return frameRect;
}


static void
ns_constrain_all_frames (void)
/* --------------------------------------------------------------------------
     Ensure that the menu bar doesn't cover any frames.
   -------------------------------------------------------------------------- */
{
  Lisp_Object tail, frame;

  NSTRACE ("ns_constrain_all_frames");

  block_input ();

  FOR_EACH_FRAME (tail, frame)
    {
      struct frame *f = XFRAME (frame);
      if (FRAME_NS_P (f))
        {
          EmacsView *view = FRAME_NS_VIEW (f);

          if (![view isFullscreen])
            {
              [[view window]
                setFrame:constrain_frame_rect([[view window] frame], false)
                 display:NO];
            }
        }
    }

  unblock_input ();
}


static void
ns_update_auto_hide_menu_bar (void)
/* --------------------------------------------------------------------------
     Show or hide the menu bar, based on user setting.
   -------------------------------------------------------------------------- */
{
#ifdef NS_IMPL_COCOA
  NSTRACE ("ns_update_auto_hide_menu_bar");

  block_input ();

  if (NSApp != nil && [NSApp isActive])
    {
      // Note, "setPresentationOptions" triggers an error unless the
      // application is active.
      BOOL menu_bar_should_be_hidden = ns_menu_bar_should_be_hidden ();

      if (menu_bar_should_be_hidden != ns_menu_bar_is_hidden)
        {
          NSApplicationPresentationOptions options
            = NSApplicationPresentationDefault;

          if (menu_bar_should_be_hidden)
            options |= NSApplicationPresentationAutoHideMenuBar
              | NSApplicationPresentationAutoHideDock;

          [NSApp setPresentationOptions: options];

          ns_menu_bar_is_hidden = menu_bar_should_be_hidden;

          if (!ns_menu_bar_is_hidden)
            {
              ns_constrain_all_frames ();
            }
        }
    }

  unblock_input ();
#endif
}


static void
ns_update_begin (struct frame *f)
/* --------------------------------------------------------------------------
   Prepare for a grouped sequence of drawing calls
   external (RIF) call; whole frame, called before gui_update_window_begin
   -------------------------------------------------------------------------- */
{
  EmacsView *view = FRAME_NS_VIEW (f);
  NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_begin");

  ns_update_auto_hide_menu_bar ();

  ns_updating_frame = f;
  [view lockFocus];
}


static void
ns_update_end (struct frame *f)
/* --------------------------------------------------------------------------
   Finished a grouped sequence of drawing calls
   external (RIF) call; for whole frame, called after gui_update_window_end
   -------------------------------------------------------------------------- */
{
  EmacsView *view = FRAME_NS_VIEW (f);

  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;

  block_input ();

  [view unlockFocus];
#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400
  [[view window] flushWindow];
#endif

  unblock_input ();
  ns_updating_frame = NULL;
}

static void
ns_focus (struct frame *f, NSRect *r, int n)
/* --------------------------------------------------------------------------
   Internal: Focus on given frame.  During small local updates this is used to
     draw, however during large updates, ns_update_begin and ns_update_end are
     called to wrap the whole thing, in which case these calls are stubbed out.
     Except, on GNUstep, we accumulate the rectangle being drawn into, because
     the back end won't do this automatically, and will just end up flushing
     the entire window.
   -------------------------------------------------------------------------- */
{
  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_focus");
  if (r != NULL)
    {
      NSTRACE_RECT ("r", *r);
    }

  if (f != ns_updating_frame)
    {
      EmacsView *view = FRAME_NS_VIEW (f);
      [view lockFocus];
    }

  /* clipping */
  if (r)
    {
      NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
      [ctx saveGraphicsState];
#ifdef NS_IMPL_COCOA
      if (n == 2)
        NSRectClipList (r, 2);
      else
        NSRectClip (*r);
#else
      GSRectClipList (ctx, r, n);
#endif
      gsaved = YES;
    }
}


static void
ns_unfocus (struct frame *f)
/* --------------------------------------------------------------------------
     Internal: Remove focus on given frame
   -------------------------------------------------------------------------- */
{
  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_unfocus");

  if (gsaved)
    {
      [[NSGraphicsContext currentContext] restoreGraphicsState];
      gsaved = NO;
    }

  if (f != ns_updating_frame)
    {
      EmacsView *view = FRAME_NS_VIEW (f);
      [view unlockFocus];
#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400
      [[view window] flushWindow];
#endif
    }
}


/* ==========================================================================

    Visible bell and beep.

   ========================================================================== */


// This bell implementation shows the visual bell image asynchronously
// from the rest of Emacs. This is done by adding a NSView to the
// superview of the Emacs window and removing it using a timer.
//
// Unfortunately, some Emacs operations, like scrolling, is done using
// low-level primitives that copy the content of the window, including
// the bell image. To some extent, this is handled by removing the
// image prior to scrolling and marking that the window is in need for
// redisplay.
//
// To test this code, make sure that there is no artifacts of the bell
// image in the following situations. Use a non-empty buffer (like the
// tutorial) to ensure that a scroll is performed:
//
// * Single-window: C-g C-v
//
// * Side-by-windows: C-x 3 C-g C-v
//
// * Windows above each other: C-x 2 C-g C-v

@interface EmacsBell : NSImageView
{
  // Number of currently active bells.
  unsigned int nestCount;
  NSView * mView;
  bool isAttached;
}
- (void)show:(NSView *)view;
- (void)hide;
- (void)remove;
@end

@implementation EmacsBell

- (id)init
{
  NSTRACE ("[EmacsBell init]");
  if ((self = [super init]))
    {
      nestCount = 0;
      isAttached = false;
#ifdef NS_IMPL_GNUSTEP
      // GNUstep doesn't provide named images.  This was reported in
      // 2011, see https://savannah.gnu.org/bugs/?33396
      //
      // As a drop in replacement, a semitransparent gray square is used.
      self.image = [[NSImage alloc] initWithSize:NSMakeSize(32 * 5, 32 * 5)];
      [self.image lockFocus];
      [[NSColor colorForEmacsRed:0.5 green:0.5 blue:0.5 alpha:0.5] set];
      NSRectFill(NSMakeRect(0, 0, 32, 32));
      [self.image unlockFocus];
#else
      self.image = [NSImage imageNamed:NSImageNameCaution];
      [self.image setSize:NSMakeSize(self.image.size.width * 5,
                                     self.image.size.height * 5)];
#endif
    }
  return self;
}

- (void)show:(NSView *)view
{
  NSTRACE ("[EmacsBell show:]");
  NSTRACE_MSG ("nestCount: %u", nestCount);

  // Show the image, unless it's already shown.
  if (nestCount == 0)
    {
      NSRect rect = [view bounds];
      NSPoint pos;
      pos.x = rect.origin.x + (rect.size.width  - self.image.size.width )/2;
      pos.y = rect.origin.y + (rect.size.height - self.image.size.height)/2;

      [self setFrameOrigin:pos];
      [self setFrameSize:self.image.size];

      isAttached = true;
      mView = view;
      [[[view window] contentView] addSubview:self
                                   positioned:NSWindowAbove
                                   relativeTo:nil];
    }

  ++nestCount;

  [self performSelector:@selector(hide) withObject:self afterDelay:0.5];
}


- (void)hide
{
  // Note: Trace output from this method isn't shown, reason unknown.
  // NSTRACE ("[EmacsBell hide]");

  if (nestCount > 0)
    --nestCount;

  // Remove the image once the last bell became inactive.
  if (nestCount == 0)
    {
      [self remove];
    }
}


-(void)remove
{
  NSTRACE ("[EmacsBell remove]");
  if (isAttached)
    {
      NSTRACE_MSG ("removeFromSuperview");
      [self removeFromSuperview];
      mView.needsDisplay = YES;
      isAttached = false;
    }
}

@end


static EmacsBell * bell_view = nil;

static void
ns_ring_bell (struct frame *f)
/* --------------------------------------------------------------------------
     "Beep" routine
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_ring_bell");
  if (visible_bell)
    {
      struct frame *frame = SELECTED_FRAME ();
      NSView *view;

      if (bell_view == nil)
        {
          bell_view = [[EmacsBell alloc] init];
          [bell_view retain];
        }

      block_input ();

      view = FRAME_NS_VIEW (frame);
      if (view != nil)
        {
          [bell_view show:view];
        }

      unblock_input ();
    }
  else
    {
      NSBeep ();
    }
}

#if !defined (NS_IMPL_COCOA) || MAC_OS_X_VERSION_MIN_REQUIRED < 101400
static void
hide_bell (void)
/* --------------------------------------------------------------------------
     Ensure the bell is hidden.
   -------------------------------------------------------------------------- */
{
  NSTRACE ("hide_bell");

  if (bell_view != nil)
    {
      [bell_view remove];
    }
}
#endif


/* ==========================================================================

    Frame / window manager related functions

   ========================================================================== */

static Lisp_Object
ns_get_focus_frame (struct frame *f)
/* --------------------------------------------------------------------------
     External (hook)
   -------------------------------------------------------------------------- */
{
  Lisp_Object lisp_focus;

  struct frame *focus =  FRAME_DISPLAY_INFO (f)->ns_focus_frame;

  if (!focus)
    return Qnil;

  XSETFRAME (lisp_focus, focus);
  return lisp_focus;
}

static void
ns_focus_frame (struct frame *f, bool noactivate)
/* --------------------------------------------------------------------------
     External (hook)
   -------------------------------------------------------------------------- */
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);

  if (dpyinfo->ns_focus_frame != f)
    {
      EmacsView *view = FRAME_NS_VIEW (f);
      block_input ();
      [NSApp activateIgnoringOtherApps: YES];
      [[view window] makeKeyAndOrderFront: view];
      unblock_input ();
    }
}

static void
ns_raise_frame (struct frame *f, BOOL make_key)
/* --------------------------------------------------------------------------
     Bring window to foreground and if make_key is YES, give it focus.
   -------------------------------------------------------------------------- */
{
  NSView *view;

  check_window_system (f);
  view = FRAME_NS_VIEW (f);
  block_input ();
  if (FRAME_VISIBLE_P (f))
    {
      if (make_key)
        [[view window] makeKeyAndOrderFront: NSApp];
      else
        [[view window] orderFront: NSApp];
    }
  unblock_input ();
}


static void
ns_lower_frame (struct frame *f)
/* --------------------------------------------------------------------------
     Send window to back
   -------------------------------------------------------------------------- */
{
  NSView *view;

  check_window_system (f);
  view = FRAME_NS_VIEW (f);
  block_input ();
  [[view window] orderBack: NSApp];
  unblock_input ();
}


static void
ns_frame_raise_lower (struct frame *f, bool raise)
/* --------------------------------------------------------------------------
     External (hook)
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_frame_raise_lower");

  if (raise)
    ns_raise_frame (f, YES);
  else
    ns_lower_frame (f);
}

static void ns_set_frame_alpha (struct frame *f);

static void
ns_frame_rehighlight (struct frame *frame)
/* --------------------------------------------------------------------------
     External (hook): called on things like window switching within frame
   -------------------------------------------------------------------------- */
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
  struct frame *old_highlight = dpyinfo->highlight_frame;

  NSTRACE ("ns_frame_rehighlight");
  if (dpyinfo->ns_focus_frame)
    {
      dpyinfo->highlight_frame
	= (FRAMEP (FRAME_FOCUS_FRAME (dpyinfo->ns_focus_frame))
           ? XFRAME (FRAME_FOCUS_FRAME (dpyinfo->ns_focus_frame))
           : dpyinfo->ns_focus_frame);
      if (!FRAME_LIVE_P (dpyinfo->highlight_frame))
        {
          fset_focus_frame (dpyinfo->ns_focus_frame, Qnil);
          dpyinfo->highlight_frame = dpyinfo->ns_focus_frame;
        }
    }
  else
      dpyinfo->highlight_frame = 0;

  if (dpyinfo->highlight_frame &&
         dpyinfo->highlight_frame != old_highlight)
    {
      if (old_highlight)
	{
          gui_update_cursor (old_highlight, 1);
	  ns_set_frame_alpha (old_highlight);
	}
      if (dpyinfo->highlight_frame)
	{
          gui_update_cursor (dpyinfo->highlight_frame, 1);
          ns_set_frame_alpha (dpyinfo->highlight_frame);
	}
    }
}


void
ns_make_frame_visible (struct frame *f)
/* --------------------------------------------------------------------------
     External: Show the window (X11 semantics)
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_make_frame_visible");
  /* XXX: at some points in past this was not needed, as the only place that
     called this (frame.c:Fraise_frame ()) also called raise_lower;
     if this ends up the case again, comment this out again.  */
  if (!FRAME_VISIBLE_P (f))
    {
      EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
      EmacsWindow *window = (EmacsWindow *)[view window];

      SET_FRAME_VISIBLE (f, 1);
      ns_raise_frame (f, ! FRAME_NO_FOCUS_ON_MAP (f));

      /* Making a new frame from a fullscreen frame will make the new frame
         fullscreen also.  So skip handleFS as this will print an error.  */
      if ([view fsIsNative] && [view isFullscreen])
        {
          return;
        }

      if (f->want_fullscreen != FULLSCREEN_NONE)
        {
          block_input ();
          [view handleFS];
          unblock_input ();
        }

      /* Making a frame invisible seems to break the parent->child
         relationship, so reinstate it.  */
      if ([window parentWindow] == nil && FRAME_PARENT_FRAME (f) != NULL)
        {
          block_input ();
          [window setParentChildRelationships];
          unblock_input ();

          /* If the parent frame moved while the child frame was
             invisible, the child frame's position won't have been
             updated.  Make sure it's in the right place now.  */
          ns_set_offset(f, f->left_pos, f->top_pos, 0);
        }
    }
}


void
ns_make_frame_invisible (struct frame *f)
/* --------------------------------------------------------------------------
     Hide the window (X11 semantics)
   -------------------------------------------------------------------------- */
{
  NSView *view;
  NSTRACE ("ns_make_frame_invisible");
  check_window_system (f);
  view = FRAME_NS_VIEW (f);
  [[view window] orderOut: NSApp];
  SET_FRAME_VISIBLE (f, 0);
  SET_FRAME_ICONIFIED (f, 0);
}

static void
ns_make_frame_visible_invisible (struct frame *f, bool visible)
/* --------------------------------------------------------------------------
     External (hook)
   -------------------------------------------------------------------------- */
{
  if (visible)
    ns_make_frame_visible (f);
  else
    ns_make_frame_invisible (f);
}

void
ns_iconify_frame (struct frame *f)
/* --------------------------------------------------------------------------
     External (hook): Iconify window
   -------------------------------------------------------------------------- */
{
  NSView *view;
  struct ns_display_info *dpyinfo;

  NSTRACE ("ns_iconify_frame");
  check_window_system (f);
  view = FRAME_NS_VIEW (f);
  dpyinfo = FRAME_DISPLAY_INFO (f);

  if (dpyinfo->highlight_frame == f)
    dpyinfo->highlight_frame = 0;

  if ([[view window] windowNumber] <= 0)
    {
      /* The window is still deferred.  Make it very small, bring it
         on screen and order it out.  */
      NSRect s = { { 100, 100}, {0, 0} };
      NSRect t;
      t = [[view window] frame];
      [[view window] setFrame: s display: NO];
      [[view window] orderBack: NSApp];
      [[view window] orderOut: NSApp];
      [[view window] setFrame: t display: NO];
    }

  /* Processing input while Emacs is being minimized can cause a
     crash, so block it for the duration.  */
  block_input();
  [[view window] miniaturize: NSApp];
  unblock_input();
}

/* Free resources of frame F.  */

void
ns_free_frame_resources (struct frame *f)
{
  NSView *view;
  struct ns_display_info *dpyinfo;
  Mouse_HLInfo *hlinfo;

  NSTRACE ("ns_free_frame_resources");
  check_window_system (f);
  view = FRAME_NS_VIEW (f);
  dpyinfo = FRAME_DISPLAY_INFO (f);
  hlinfo = MOUSE_HL_INFO (f);

  [(EmacsView *)view setWindowClosing: YES]; /* may not have been informed */

  block_input ();

  free_frame_menubar (f);
  free_frame_faces (f);

  if (f == dpyinfo->ns_focus_frame)
    dpyinfo->ns_focus_frame = 0;
  if (f == dpyinfo->highlight_frame)
    dpyinfo->highlight_frame = 0;
  if (f == hlinfo->mouse_face_mouse_frame)
    reset_mouse_highlight (hlinfo);

  if (f->output_data.ns->miniimage != nil)
    [f->output_data.ns->miniimage release];

  [[view window] close];
  [view release];

  xfree (f->output_data.ns);
  f->output_data.ns = NULL;

  unblock_input ();
}

static void
ns_destroy_window (struct frame *f)
/* --------------------------------------------------------------------------
     External: Delete the window
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_destroy_window");

  check_window_system (f);

  /* If this frame has a parent window, detach it as not doing so can
     cause a crash in GNUStep.  */
  if (FRAME_PARENT_FRAME (f))
    {
      NSWindow *child = [FRAME_NS_VIEW (f) window];
      NSWindow *parent;

      /* Pacify a incorrect GCC warning about FRAME_PARENT_FRAME (f)
	 being NULL. */
      if (FRAME_PARENT_FRAME (f))
	parent = [FRAME_NS_VIEW (FRAME_PARENT_FRAME (f)) window];
      else
	emacs_abort ();

      [parent removeChildWindow: child];
    }

  [[FRAME_NS_VIEW (f) window] close];
  ns_free_frame_resources (f);
  ns_window_num--;
}


void
ns_set_offset (struct frame *f, int xoff, int yoff, int change_grav)
/* --------------------------------------------------------------------------
     External: Position the window
   -------------------------------------------------------------------------- */
{
  NSView *view = FRAME_NS_VIEW (f);
  NSRect windowFrame = [[view window] frame];
  NSPoint topLeft;

  NSTRACE ("ns_set_offset");

  block_input ();

  /* 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 parentRect = ns_parent_window_rect (f);

  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
    topLeft.x = xoff;

  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.

     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];
  f->size_hint_flags &= ~(XNegative|YNegative);

  unblock_input ();
}


static void
ns_set_window_size (struct frame *f, bool change_gravity,
                    int width, int height)
/* --------------------------------------------------------------------------
     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 frameRect;

  NSTRACE ("ns_set_window_size");

  if (view == nil)
    return;

  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 ();

  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 ();
}

void
ns_set_undecorated (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* --------------------------------------------------------------------------
     Set frame F's `undecorated' parameter.  If non-nil, F's window-system
     window is drawn without decorations, title, minimize/maximize boxes
     and external borders.  This usually means that the window cannot be
     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.
   -------------------------------------------------------------------------- */
{
  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 ();

      FRAME_UNDECORATED (f) = !NILP (new_value);

      newWindow = [[EmacsWindow alloc] initWithEmacsFrame:f];

      if ([oldWindow isKeyWindow])
        [newWindow makeKeyAndOrderFront:NSApp];

      [newWindow setIsVisible:[oldWindow isVisible]];
      if ([oldWindow isMiniaturized])
        [newWindow miniaturize:NSApp];

      [oldWindow close];

      unblock_input ();
    }
}

void
ns_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* --------------------------------------------------------------------------
     Set frame F's `parent-frame' parameter.  If non-nil, make F a child
     frame of the frame specified by that parameter.  Technically, this
     makes F's window-system window a child window of the parent frame's
     window-system window.  If nil, make F's window-system window a
     top-level window--a child of its display's root window.

     A child frame's `left' and `top' parameters specify positions
     relative to the top-left corner of its parent frame's native
     rectangle.  On macOS moving a parent frame moves all its child
     frames too, keeping their position relative to the parent
     unaltered.  When a parent frame is iconified or made invisible, its
     child frames are made invisible.  When a parent frame is deleted,
     its child frames are deleted too.

     Whether a child frame has a tool bar may be window-system or window
     manager dependent.  It's advisable to disable it via the frame
     parameter settings.

     Some window managers may not honor this parameter.
   -------------------------------------------------------------------------- */
{
  struct frame *p = NULL;

  NSTRACE ("ns_set_parent_frame");

  if (!NILP (new_value)
      && (!FRAMEP (new_value)
	  || !FRAME_LIVE_P (p = XFRAME (new_value))
	  || !FRAME_NS_P (p)))
    {
      store_frame_param (f, Qparent_frame, old_value);
      error ("Invalid specification of `parent-frame'");
    }

  fset_parent_frame (f, new_value);

  block_input ();
  [(EmacsWindow *)[FRAME_NS_VIEW (f) window] setParentChildRelationships];
  unblock_input ();
}

void
ns_set_no_focus_on_map (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* Set frame F's `no-focus-on-map' parameter which, if non-nil, means
 * that F's window-system window does not want to receive input focus
 * when it is mapped.  (A frame's window is mapped when the frame is
 * displayed for the first time and when the frame changes its state
 * from `iconified' or `invisible' to `visible'.)
 *
 * Some window managers may not honor this parameter.  */
{
  NSTRACE ("ns_set_no_focus_on_map");

  if (!EQ (new_value, old_value))
    {
      FRAME_NO_FOCUS_ON_MAP (f) = !NILP (new_value);
    }
}

void
ns_set_no_accept_focus (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/*  Set frame F's `no-accept-focus' parameter which, if non-nil, hints
 * that F's window-system window does not want to receive input focus
 * via mouse clicks or by moving the mouse into it.
 *
 * If non-nil, this may have the unwanted side-effect that a user cannot
 * scroll a non-selected frame with the mouse.
 *
 * Some window managers may not honor this parameter.  */
{
  NSTRACE ("ns_set_no_accept_focus");

  if (!EQ (new_value, old_value))
    FRAME_NO_ACCEPT_FOCUS (f) = !NILP (new_value);
}

void
ns_set_z_group (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* Set frame F's `z-group' parameter.  If `above', F's window-system
   window is displayed above all windows that do not have the `above'
   property set.  If nil, F's window is shown below all windows that
   have the `above' property set and above all windows that have the
   `below' property set.  If `below', F's window is displayed below
   all windows that do.

   Some window managers may not honor this parameter.  */
{
  EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
  NSWindow *window = [view window];

  NSTRACE ("ns_set_z_group");

  if (NILP (new_value))
    {
      window.level = NSNormalWindowLevel;
      FRAME_Z_GROUP (f) = z_group_none;
    }
  else if (EQ (new_value, Qabove))
    {
      window.level = NSNormalWindowLevel + 1;
      FRAME_Z_GROUP (f) = z_group_above;
    }
  else if (EQ (new_value, Qabove_suspended))
    {
      /* Not sure what level this should be.  */
      window.level = NSNormalWindowLevel + 1;
      FRAME_Z_GROUP (f) = z_group_above_suspended;
    }
  else if (EQ (new_value, Qbelow))
    {
      window.level = NSNormalWindowLevel - 1;
      FRAME_Z_GROUP (f) = z_group_below;
    }
  else
    error ("Invalid z-group specification");
}

#ifdef NS_IMPL_COCOA
void
ns_set_appearance (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
  EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
  EmacsWindow *window = (EmacsWindow *)[view window];

  NSTRACE ("ns_set_appearance");

  if (NSAppKitVersionNumber < NSAppKitVersionNumber10_10)
    return;

  if (EQ (new_value, Qdark))
    FRAME_NS_APPEARANCE (f) = ns_appearance_vibrant_dark;
  else if (EQ (new_value, Qlight))
    FRAME_NS_APPEARANCE (f) = ns_appearance_aqua;
  else
    FRAME_NS_APPEARANCE (f) = ns_appearance_system_default;

  [window setAppearance];
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 */
}

void
ns_set_transparent_titlebar (struct frame *f, Lisp_Object new_value,
                             Lisp_Object old_value)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
  EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
  NSWindow *window = [view window];

  NSTRACE ("ns_set_transparent_titlebar");

  if ([window respondsToSelector: @selector(titlebarAppearsTransparent)]
      && !EQ (new_value, old_value))
    {
      window.titlebarAppearsTransparent = !NILP (new_value);
      FRAME_NS_TRANSPARENT_TITLEBAR (f) = !NILP (new_value);
    }
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 */
}
#endif /* NS_IMPL_COCOA */

static void
ns_fullscreen_hook (struct frame *f)
{
  EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);

  NSTRACE ("ns_fullscreen_hook");

  if (!FRAME_VISIBLE_P (f))
    return;

   if (! [view fsIsNative] && f->want_fullscreen == FULLSCREEN_BOTH)
    {
      /* Old style fs don't initiate correctly if created from
         init/default-frame alist, so use a timer (not nice...).  */
      [NSTimer scheduledTimerWithTimeInterval: 0.5 target: view
                                     selector: @selector (handleFS)
                                     userInfo: nil repeats: NO];
      return;
    }

  block_input ();
  [view handleFS];
  unblock_input ();
}

/* ==========================================================================

    Color management

   ========================================================================== */


static int
ns_get_color (const char *name, NSColor **col)
/* --------------------------------------------------------------------------
     Parse a color name
   -------------------------------------------------------------------------- */
/* On *Step, we attempt to mimic the X11 platform here, down to installing an
   X11 rgb.txt-compatible color list in Emacs.clr (see ns_term_init()).
   See https://lists.gnu.org/r/emacs-devel/2009-07/msg01203.html.  */
{
  NSColor *new = nil;
  NSString *nsname = [NSString stringWithUTF8String: name];

  NSTRACE ("ns_get_color(%s, **)", name);

  block_input ();

  if ([nsname isEqualToString: @"ns_selection_bg_color"])
    {
#ifdef NS_IMPL_COCOA
      NSString *defname = [[NSUserDefaults standardUserDefaults]
                            stringForKey: @"AppleHighlightColor"];
      if (defname != nil)
        nsname = defname;
      else
#endif
      if ((new = [NSColor selectedTextBackgroundColor]) != nil)
        {
          *col = [new colorUsingDefaultColorSpace];
          unblock_input ();
          return 0;
        }
      else
        nsname = NS_SELECTION_BG_COLOR_DEFAULT;

      name = [nsname UTF8String];
    }
  else if ([nsname isEqualToString: @"ns_selection_fg_color"])
    {
      /* NOTE: macOS applications normally don't set foreground
         selection, but text may be unreadable if we don't.  */
      if ((new = [NSColor selectedTextColor]) != nil)
        {
          *col = [new colorUsingDefaultColorSpace];
          unblock_input ();
          return 0;
        }

      nsname = NS_SELECTION_FG_COLOR_DEFAULT;
      name = [nsname UTF8String];
    }

  /* First, check for some sort of numeric specification.  */
  unsigned short r16, g16, b16;
  if (parse_color_spec (name, &r16, &g16, &b16))
    {
      *col = [NSColor colorForEmacsRed: r16 / 65535.0
                                 green: g16 / 65535.0
                                  blue: b16 / 65535.0
                                 alpha: 1.0];
      unblock_input ();
      return 0;
    }
  else if (name[0] == '0' || name[0] == '1' || name[0] == '.')
    {
      /* RGB decimal */
      NSScanner *scanner = [NSScanner scannerWithString: nsname];
      float r, g, b;
      if (   [scanner scanFloat: &r] && r >= 0 && r <= 1
          && [scanner scanFloat: &g] && g >= 0 && g <= 1
          && [scanner scanFloat: &b] && b >= 0 && b <= 1)
        {
          *col = [NSColor colorForEmacsRed: r green: g blue: b alpha: 1.0];
          unblock_input ();
          return 0;
        }
    }

  /* Otherwise, color is expected to be from a list */
  {
    NSEnumerator *lenum, *cenum;
    NSString *name;
    NSColorList *clist;

#ifdef NS_IMPL_GNUSTEP
    /* XXX: who is wrong, the requestor or the implementation?  */
    if ([nsname compare: @"Highlight" options: NSCaseInsensitiveSearch]
        == NSOrderedSame)
      nsname = @"highlightColor";
#endif

    lenum = [[NSColorList availableColorLists] objectEnumerator];
    while ( (clist = [lenum nextObject]) && new == nil)
      {
        cenum = [[clist allKeys] objectEnumerator];
        while ( (name = [cenum nextObject]) && new == nil )
          {
            if ([name compare: nsname
                      options: NSCaseInsensitiveSearch] == NSOrderedSame )
              new = [clist colorWithKey: name];
          }
      }
  }

  if (new)
    *col = [new colorUsingDefaultColorSpace];
  unblock_input ();
  return new ? 0 : 1;
}


int
ns_lisp_to_color (Lisp_Object color, NSColor **col)
/* --------------------------------------------------------------------------
     Convert a Lisp string object to a NS color.
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_lisp_to_color");
  if (STRINGP (color))
    return ns_get_color (SSDATA (color), col);
  else if (SYMBOLP (color))
    return ns_get_color (SSDATA (SYMBOL_NAME (color)), col);
  return 1;
}

static void
ns_query_color (void *col, Emacs_Color *color_def)
/* --------------------------------------------------------------------------
         Get ARGB values out of NSColor col and put them into color_def
         and set color_def pixel to the ARGB color.
   -------------------------------------------------------------------------- */
{
  EmacsCGFloat r, g, b, a;

  [((NSColor *)col) getRed: &r green: &g blue: &b alpha: &a];
  color_def->red   = r * 65535;
  color_def->green = g * 65535;
  color_def->blue  = b * 65535;

  color_def->pixel = [(NSColor *)col unsignedLong];
}

bool
ns_defined_color (struct frame *f,
                  const char *name,
                  Emacs_Color *color_def,
                  bool alloc,
                  bool _makeIndex)
/* --------------------------------------------------------------------------
         Return true if named color found, and set color_def rgb accordingly.
         Return false if not found.
   -------------------------------------------------------------------------- */
{
  NSColor *col;
  NSTRACE_WHEN (NSTRACE_GROUP_COLOR, "ns_defined_color");

  block_input ();
  if (ns_get_color (name, &col) != 0) /* Color not found  */
    {
      unblock_input ();
      return 0;
    }
  ns_query_color (col, color_def);
  unblock_input ();
  return 1;
}

static void
ns_query_frame_background_color (struct frame *f, Emacs_Color *bgcolor)
/* --------------------------------------------------------------------------
     External (hook): Store F's background color into *BGCOLOR
   -------------------------------------------------------------------------- */
{
  ns_query_color (FRAME_BACKGROUND_COLOR (f), bgcolor);
}

static void
ns_set_frame_alpha (struct frame *f)
/* --------------------------------------------------------------------------
     change the entire-frame transparency
   -------------------------------------------------------------------------- */
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
  double alpha = 1.0;
  double alpha_min = 1.0;

  NSTRACE ("ns_set_frame_alpha");

  if (dpyinfo->highlight_frame == f)
    alpha = f->alpha[0];
  else
    alpha = f->alpha[1];

  if (FLOATP (Vframe_alpha_lower_limit))
    alpha_min = XFLOAT_DATA (Vframe_alpha_lower_limit);
  else if (FIXNUMP (Vframe_alpha_lower_limit))
    alpha_min = (XFIXNUM (Vframe_alpha_lower_limit)) / 100.0;

  if (alpha < 0.0)
    return;
  else if (1.0 < alpha)
    alpha = 1.0;
  else if (0.0 <= alpha && alpha < alpha_min && alpha_min <= 1.0)
    alpha = alpha_min;

  {
    EmacsView *view = FRAME_NS_VIEW (f);
    [[view window] setAlphaValue: alpha];
  }
}


/* ==========================================================================

    Mouse handling

   ========================================================================== */


void
frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
/* --------------------------------------------------------------------------
     Programmatically reposition mouse pointer in pixel coordinates
   -------------------------------------------------------------------------- */
{
  NSTRACE ("frame_set_mouse_pixel_position");

#ifdef NS_IMPL_COCOA
  CGPoint mouse_pos =
    CGPointMake(f->left_pos + pix_x,
                f->top_pos + pix_y +
                FRAME_NS_TITLEBAR_HEIGHT(f) + FRAME_TOOLBAR_HEIGHT(f));
  CGWarpMouseCursorPosition (mouse_pos);
#else
  GSDisplayServer *server = GSServerForWindow ([FRAME_NS_VIEW (f) window]);
  [server setMouseLocation: NSMakePoint (f->left_pos + pix_x,
					 f->top_pos + pix_y
					 + FRAME_NS_TITLEBAR_HEIGHT(f)
					 + FRAME_TOOLBAR_HEIGHT(f))
		  onScreen: [[[FRAME_NS_VIEW (f) window] screen] screenNumber]];
#endif
}

static int
ns_note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y,
			BOOL dragging)
/*   ------------------------------------------------------------------------
     Called by EmacsView on mouseMovement events.  Passes on
     to emacs mainstream code if we moved off of a rect of interest
     known as last_mouse_glyph.
     ------------------------------------------------------------------------ */
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
  NSRect *r;
  BOOL force_update = NO;

  // NSTRACE ("note_mouse_movement");

  dpyinfo->last_mouse_motion_frame = frame;
  r = &dpyinfo->last_mouse_glyph;

  /* If the last rect is too large (ex, xwidget webkit), update at
     every move, or resizing by dragging modeline or vertical split is
     very hard to make its way.  */
  if (dragging && (r->size.width > 32 || r->size.height > 32))
    force_update = YES;

  /* Note, this doesn't get called for enter/leave, since we don't have a
     position.  Those are taken care of in the corresponding NSView methods.  */

  /* Has movement gone beyond last rect we were tracking?  */
  if (force_update || x < r->origin.x || x >= r->origin.x + r->size.width
      || y < r->origin.y || y >= r->origin.y + r->size.height)
    {
      ns_update_begin (frame);
      frame->mouse_moved = 1;
      note_mouse_highlight (frame, x, y);
      remember_mouse_glyph (frame, x, y, r);
      ns_update_end (frame);
      return 1;
    }

  return 0;
}


static void
ns_mouse_position (struct frame **fp, int insist, Lisp_Object *bar_window,
                   enum scroll_bar_part *part, Lisp_Object *x, Lisp_Object *y,
                   Time *time)
/* --------------------------------------------------------------------------
    External (hook): inform emacs about mouse position and hit parts.
    If a scrollbar is being dragged, set bar_window, part, x, y, time.
    x & y should be position in the scrollbar (the whole bar, not the handle)
    and length of scrollbar respectively.
   -------------------------------------------------------------------------- */
{
  id view;
  NSPoint view_position;
  Lisp_Object frame, tail;
  struct frame *f = NULL;
  struct ns_display_info *dpyinfo;
  bool return_no_frame_flag = false;
#ifdef NS_IMPL_COCOA
  NSPoint screen_position;
  NSInteger window_number;
  NSWindow *w;
#endif

  NSTRACE ("ns_mouse_position");

  if (*fp == NULL)
    {
      fputs ("Warning: ns_mouse_position () called with null *fp.\n", stderr);
      return;
    }

  dpyinfo = FRAME_DISPLAY_INFO (*fp);

  block_input ();

  /* Clear the mouse-moved flag for every frame on this display.  */
  FOR_EACH_FRAME (tail, frame)
    if (FRAME_NS_P (XFRAME (frame)))
      XFRAME (frame)->mouse_moved = 0;

  dpyinfo->last_mouse_scroll_bar = nil;

#ifdef NS_IMPL_COCOA
  /* Find the uppermost Emacs frame under the mouse pointer.

     This doesn't work on GNUstep, although in recent versions there
     is compatibility code that makes it a noop.  */

  screen_position = [NSEvent mouseLocation];
  window_number = 0;

  do
    {
      window_number = [NSWindow windowNumberAtPoint: screen_position
                        belowWindowWithWindowNumber: window_number];
      w = [NSApp windowWithWindowNumber: window_number];

      if ((EQ (track_mouse, Qdrag_source)
	   || EQ (track_mouse, Qdropping))
	  && w && [[w delegate] isKindOfClass: [EmacsTooltip class]])
	continue;

      if (w && [[w delegate] isKindOfClass: [EmacsView class]])
        f = ((EmacsView *) [w delegate])->emacsframe;
      else if (EQ (track_mouse, Qdrag_source))
	break;

      if (f && (EQ (track_mouse, Qdrag_source)
		|| EQ (track_mouse, Qdropping))
	  && FRAME_TOOLTIP_P (f))
	continue;
    }
  while (window_number > 0 && !f);
#endif

  if (!f)
    {
      f = (dpyinfo->ns_focus_frame
	   ? dpyinfo->ns_focus_frame : SELECTED_FRAME ());
      return_no_frame_flag = EQ (track_mouse, Qdrag_source);
    }

  if (!FRAME_NS_P (f))
    f = NULL;

  if (f && FRAME_TOOLTIP_P (f))
    f = dpyinfo->last_mouse_frame;

  /* While dropping, use the last mouse frame only if there is no
     currently focused frame.  */
  if (!f && (EQ (track_mouse, Qdropping)
	     || EQ (track_mouse, Qdrag_source))
      && dpyinfo->last_mouse_frame
      && FRAME_LIVE_P (dpyinfo->last_mouse_frame))
    {
      f = dpyinfo->last_mouse_frame;
      return_no_frame_flag = EQ (track_mouse, Qdrag_source);
    }

  if (f && FRAME_NS_P (f))
    {
      view = FRAME_NS_VIEW (f);

      view_position = [[view window] mouseLocationOutsideOfEventStream];
      view_position = [view convertPoint: view_position fromView: nil];
      remember_mouse_glyph (f, view_position.x, view_position.y,
                            &dpyinfo->last_mouse_glyph);
      NSTRACE_POINT ("view_position", view_position);

      if (bar_window) *bar_window = Qnil;
      if (part) *part = scroll_bar_above_handle;

      if (x) XSETINT (*x, lrint (view_position.x));
      if (y) XSETINT (*y, lrint (view_position.y));
      if (time)
        *time = dpyinfo->last_mouse_movement_time;
      *fp = return_no_frame_flag ? NULL : f;
    }

  unblock_input ();
}


static void
ns_frame_up_to_date (struct frame *f)
/* --------------------------------------------------------------------------
    External (hook): Fix up mouse highlighting right after a full update.
    Can't use FRAME_MOUSE_UPDATE due to ns_frame_begin and ns_frame_end calls.
   -------------------------------------------------------------------------- */
{
  NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_frame_up_to_date");

  if (FRAME_NS_P (f))
    {
      Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (f);
      if (f == hlinfo->mouse_face_mouse_frame)
	{
	  block_input ();
	  ns_update_begin(f);
	  note_mouse_highlight (hlinfo->mouse_face_mouse_frame,
				hlinfo->mouse_face_mouse_x,
				hlinfo->mouse_face_mouse_y);
	  ns_update_end(f);
	  unblock_input ();
	}
    }
}


static void
ns_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
/* --------------------------------------------------------------------------
    External (RIF): set frame mouse pointer type.
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_define_frame_cursor");
  if (FRAME_POINTER_TYPE (f) != cursor)
    {
      EmacsView *view = FRAME_NS_VIEW (f);
      FRAME_POINTER_TYPE (f) = cursor;
      [[view window] invalidateCursorRectsForView: view];
    }
}



/* ==========================================================================

    Keyboard handling

   ========================================================================== */


static unsigned
ns_convert_key (unsigned code)
/* --------------------------------------------------------------------------
    Internal call used by NSView-keyDown.
   -------------------------------------------------------------------------- */
{
  const unsigned last_keysym = ARRAYELTS (convert_ns_to_X_keysym);
  unsigned keysym;
  /* An array would be faster, but less easy to read.  */
  for (keysym = 0; keysym < last_keysym; keysym += 2)
    if (code == convert_ns_to_X_keysym[keysym])
      return 0xFF00 | convert_ns_to_X_keysym[keysym+1];
  return 0;
/* if decide to use keyCode and Carbon table, use this line:
     return code > 0xff ? 0 : 0xFF00 | ns_keycode_to_xkeysym_table[code]; */
}


char *
get_keysym_name (int keysym)
/* --------------------------------------------------------------------------
    Called by keyboard.c.  Not sure if the return val is important, except
    that it be unique.
   -------------------------------------------------------------------------- */
{
  static char value[16];
  NSTRACE ("get_keysym_name");
  snprintf (value, 16, "%d", keysym);
  return value;
}

#ifdef NS_IMPL_COCOA
static Lisp_Object
right_mod (Lisp_Object left, Lisp_Object right)
{
  return EQ (right, Qleft) ? left : right;
}

static bool
nil_or_none (Lisp_Object val)
{
  return NILP (val) || EQ (val, Qnone);
}

static UniChar
ns_get_shifted_character (NSEvent *event)
/* Look up the character corresponding to the key pressed on the
   current keyboard layout and the currently configured shift-like
   modifiers.  This ignores the control-like modifiers that cause
   [event characters] to give us the wrong result.

   Although UCKeyTranslate doesn't require the Carbon framework, some
   of the surrounding paraphernalia does, so this function makes
   Carbon a requirement.  */
{
  static UInt32 dead_key_state;

  /* UCKeyTranslate may return up to 255 characters.  If the buffer
     isn't large enough then it produces an error.  What kind of
     keyboard inputs 255 characters in a single keypress?  */
  UniChar buf[255];
  UniCharCount max_string_length = 255;
  UniCharCount actual_string_length = 0;
  OSStatus result;

  CFDataRef layout_ref = (CFDataRef) TISGetInputSourceProperty
    (TISCopyCurrentKeyboardLayoutInputSource (), kTISPropertyUnicodeKeyLayoutData);
  UCKeyboardLayout* layout = (UCKeyboardLayout*) CFDataGetBytePtr (layout_ref);

  UInt32 flags = [event modifierFlags];
  UInt32 modifiers = (flags & NSEventModifierFlagShift) ? shiftKey : 0;

  NSTRACE ("ns_get_shifted_character");

  if ((flags & NSRightAlternateKeyMask) == NSRightAlternateKeyMask
      && nil_or_none (mod_of_kind (right_mod (ns_alternate_modifier,
                                              ns_right_alternate_modifier),
                                   QCordinary)))
    modifiers |= rightOptionKey;

  if ((flags & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask
      && nil_or_none (mod_of_kind (ns_alternate_modifier, QCordinary)))
    modifiers |= optionKey;

  if ((flags & NSRightCommandKeyMask) == NSRightCommandKeyMask
      && nil_or_none (mod_of_kind (right_mod (ns_command_modifier,
                                              ns_right_command_modifier),
                                   QCordinary)))
    /* Carbon doesn't differentiate between left and right command
       keys.  */
    modifiers |= cmdKey;

  if ((flags & NSLeftCommandKeyMask) == NSLeftCommandKeyMask
      && nil_or_none (mod_of_kind (ns_command_modifier, QCordinary)))
    modifiers |= cmdKey;

  result = UCKeyTranslate (layout, [event keyCode], kUCKeyActionDown,
                           (modifiers >> 8) & 0xFF, LMGetKbdType (),
                           kUCKeyTranslateNoDeadKeysBit, &dead_key_state,
                           max_string_length, &actual_string_length, buf);

  if (result != 0)
    {
      NSLog(@"Failed to translate character '%@' with modifiers %x",
            [event characters], modifiers);
      return 0;
    }

  /* FIXME: What do we do if more than one code unit is returned?  */
  if (actual_string_length > 0)
    return buf[0];

  return 0;
}
#endif /* NS_IMPL_COCOA */

/* ==========================================================================

    Block drawing operations

   ========================================================================== */


#ifdef NS_IMPL_GNUSTEP
static void
ns_redraw_scroll_bars (struct frame *f)
{
  int i;
  id view;
  NSArray *subviews = [[FRAME_NS_VIEW (f) superview] subviews];
  NSTRACE ("ns_redraw_scroll_bars");
  for (i =[subviews count]-1; i >= 0; i--)
    {
      view = [subviews objectAtIndex: i];
      if (![view isKindOfClass: [EmacsScroller class]]) continue;
      [view display];
    }
}
#endif


void
ns_clear_frame (struct frame *f)
/* --------------------------------------------------------------------------
      External (hook): Erase the entire frame
   -------------------------------------------------------------------------- */
{
  NSView *view = FRAME_NS_VIEW (f);
  NSRect r;

  NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_clear_frame");

 /* comes on initial frame because we have
    after-make-frame-functions = select-frame */
 if (!FRAME_DEFAULT_FACE (f))
   return;

  mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f)));

  r = [view bounds];

  block_input ();
  ns_focus (f, &r, 1);
  [[NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND
			    (FACE_FROM_ID (f, DEFAULT_FACE_ID))] set];
  NSRectFill (r);
  ns_unfocus (f);

#ifdef NS_IMPL_GNUSTEP
  ns_redraw_scroll_bars (f);
#endif
  unblock_input ();
}


static void
ns_clear_frame_area (struct frame *f, int x, int y, int width, int height)
/* --------------------------------------------------------------------------
    External (RIF):  Clear section of frame
   -------------------------------------------------------------------------- */
{
  NSRect r = NSMakeRect (x, y, width, height);
  NSView *view = FRAME_NS_VIEW (f);
  struct face *face = FRAME_DEFAULT_FACE (f);

  if (!view || !face)
    return;

  NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_clear_frame_area");

  r = NSIntersectionRect (r, [view frame]);
  ns_focus (f, &r, 1);
  [[NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND (face)] set];

  NSRectFill (r);

  ns_unfocus (f);
  return;
}


static void
ns_scroll_run (struct window *w, struct run *run)
/* --------------------------------------------------------------------------
    External (RIF):  Insert or delete n lines at line vpos.
   -------------------------------------------------------------------------- */
{
  struct frame *f = XFRAME (w->frame);
  int x, y, width, height, from_y, to_y, bottom_y;

  NSTRACE ("ns_scroll_run");

  /* begin copy from other terms */
  /* Get frame-relative bounding box of the text display area of W,
     without mode lines.  Include in this box the left and right
     fringe of W.  */
  window_box (w, ANY_AREA, &x, &y, &width, &height);

  from_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->current_y);
  to_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->desired_y);
  bottom_y = y + height;

  if (to_y < from_y)
    {
      /* Scrolling up.  Make sure we don't copy part of the mode
	 line at the bottom.  */
      if (from_y + run->height > bottom_y)
	height = bottom_y - from_y;
      else
	height = run->height;
    }
  else
    {
      /* Scrolling down.  Make sure we don't copy over the mode line.
	 at the bottom.  */
      if (to_y + run->height > bottom_y)
	height = bottom_y - to_y;
      else
	height = run->height;
    }
  /* end copy from other terms */

  if (height == 0)
      return;

  block_input ();

  gui_clear_cursor (w);

  {
    NSRect srcRect = NSMakeRect (x, from_y, width, height);
    NSPoint dest = NSMakePoint (x, to_y);
    NSRect destRect = NSMakeRect (x, from_y, width, height);
    EmacsView *view = FRAME_NS_VIEW (f);

    [view copyRect:srcRect to:dest];
#ifdef NS_IMPL_COCOA
    [view setNeedsDisplayInRect:destRect];
#endif
  }

  unblock_input ();
}


static void
ns_clear_under_internal_border (struct frame *f)
{
  NSTRACE ("ns_clear_under_internal_border");

  if (FRAME_LIVE_P (f) && FRAME_INTERNAL_BORDER_WIDTH (f) > 0)
    {
      int border = FRAME_INTERNAL_BORDER_WIDTH (f);
      int width = FRAME_PIXEL_WIDTH (f);
      int height = FRAME_PIXEL_HEIGHT (f);
      int margin = FRAME_TOP_MARGIN_HEIGHT (f);
      int face_id =
        (FRAME_PARENT_FRAME (f)
         ? (!NILP (Vface_remapping_alist)
            ? lookup_basic_face (NULL, f, CHILD_FRAME_BORDER_FACE_ID)
            : CHILD_FRAME_BORDER_FACE_ID)
         : (!NILP (Vface_remapping_alist)
            ? lookup_basic_face (NULL, f, INTERNAL_BORDER_FACE_ID)
            : INTERNAL_BORDER_FACE_ID));
      struct face *face = FACE_FROM_ID_OR_NULL (f, face_id);

      if (!face)
        face = FRAME_DEFAULT_FACE (f);

      /* Sometimes with new frames we reach this point and have no
         face.  I'm not sure why we have a live frame but no face, so
         just give up.  */
      if (!face)
        return;

      ns_focus (f, NULL, 1);
      [[NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND (face)] set];
      NSRectFill (NSMakeRect (0, margin, width, border));
      NSRectFill (NSMakeRect (0, 0, border, height));
      NSRectFill (NSMakeRect (0, margin, width, border));
      NSRectFill (NSMakeRect (width - border, 0, border, height));
      NSRectFill (NSMakeRect (0, height - border, width, border));
      ns_unfocus (f);
    }
}


static void
ns_after_update_window_line (struct window *w, struct glyph_row *desired_row)
/* --------------------------------------------------------------------------
    External (RIF): preparatory to fringe update after text was updated
   -------------------------------------------------------------------------- */
{
  struct frame *f;
  int width, height;

  NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_after_update_window_line");

  /* begin copy from other terms */
  eassert (w);

  if (!desired_row->mode_line_p && !w->pseudo_window_p)
    desired_row->redraw_fringe_bitmaps_p = 1;

  /* When a window has disappeared, make sure that no rest of
     full-width rows stays visible in the internal border.  */
  if (windows_or_buffers_changed
      && desired_row->full_width_p
      && (f = XFRAME (w->frame),
	  width = FRAME_INTERNAL_BORDER_WIDTH (f),
	  width != 0)
      && (height = desired_row->visible_height,
	  height > 0))
    {
      int y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, desired_row->y));
      int face_id =
        !NILP (Vface_remapping_alist)
        ? lookup_basic_face (NULL, f, INTERNAL_BORDER_FACE_ID)
        : INTERNAL_BORDER_FACE_ID;
      struct face *face = FACE_FROM_ID_OR_NULL (f, face_id);

      block_input ();
      if (face)
        {
          NSRect r = NSMakeRect (0, y, FRAME_PIXEL_WIDTH (f), height);
          ns_focus (f, &r, 1);

          [[NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND (face)] set];
          NSRectFill (NSMakeRect (0, y, width, height));
          NSRectFill (NSMakeRect (FRAME_PIXEL_WIDTH (f) - width,
                                  y, width, height));

          ns_unfocus (f);
        }
      else
        {
          ns_clear_frame_area (f, 0, y, width, height);
          ns_clear_frame_area (f,
                               FRAME_PIXEL_WIDTH (f) - width,
                               y, width, height);
        }
      unblock_input ();
    }
}


static void
ns_shift_glyphs_for_insert (struct frame *f,
                           int x, int y, int width, int height,
                           int shift_by)
/* --------------------------------------------------------------------------
    External (RIF): copy an area horizontally, don't worry about clearing src
   -------------------------------------------------------------------------- */
{
  NSRect srcRect = NSMakeRect (x, y, width, height);
  NSPoint dest = NSMakePoint (x+shift_by, y);

  NSTRACE ("ns_shift_glyphs_for_insert");

  [FRAME_NS_VIEW (f) copyRect:srcRect to:dest];
}



/* ==========================================================================

    Character encoding and metrics

   ========================================================================== */


static void
ns_compute_glyph_string_overhangs (struct glyph_string *s)
/* --------------------------------------------------------------------------
     External (RIF); compute left/right overhang of whole string and set in s
   -------------------------------------------------------------------------- */
{
  if (s->cmp == NULL
      && (s->first_glyph->type == CHAR_GLYPH
	  || s->first_glyph->type == COMPOSITE_GLYPH))
    {
      struct font_metrics metrics;

      if (s->first_glyph->type == CHAR_GLYPH)
	{
	  struct font *font = s->font;
	  font->driver->text_extents (font, s->char2b, s->nchars, &metrics);
	}
      else
	{
	  Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);

	  composition_gstring_width (gstring, s->cmp_from, s->cmp_to, &metrics);
	}
      s->right_overhang = (metrics.rbearing > metrics.width
			   ? metrics.rbearing - metrics.width : 0);
      s->left_overhang = metrics.lbearing < 0 ? - metrics.lbearing : 0;
    }
  else if (s->cmp)
    {
      s->right_overhang = s->cmp->rbearing - s->cmp->pixel_width;
      s->left_overhang = - s->cmp->lbearing;
    }
}



/* ==========================================================================

    Fringe and cursor drawing

   ========================================================================== */

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++)
      {
        bool bit = bits[y] & (1 << (w - x - 1));
        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]];
}


static void
ns_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
                      struct draw_fringe_bitmap_params *p)
/* --------------------------------------------------------------------------
    External (RIF); fringe-related
   -------------------------------------------------------------------------- */
{
  /* Fringe bitmaps comes in two variants, normal and periodic.  A
     periodic bitmap is used to create a continuous pattern.  Since a
     bitmap is rendered one text line at a time, the start offset (dh)
     of the bitmap varies.  Concretely, this is used for the empty
     line indicator.

     For a bitmap, "h + dh" is the full height and is always
     invariant.  For a normal bitmap "dh" is zero.

     For example, when the period is three and the full height is 72
     the following combinations exists:

       h=72 dh=0
       h=71 dh=1
       h=70 dh=2 */

  struct frame *f = XFRAME (WINDOW_FRAME (w));
  struct face *face = p->face;
  NSRect clearRect = 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);

  /* Work out the rectangle we will need to clear.  */
  clearRect = NSMakeRect (p->x, p->y, p->wd, p->h);

  if (p->bx >= 0 && !p->overlay_p)
    clearRect = NSUnionRect (clearRect, NSMakeRect (p->bx, p->by, p->nx, p->ny));

  /* Handle partially visible rows.  */
  clearRect = NSIntersectionRect (clearRect, rowRect);

  /* The visible portion of imageRect will always be contained within
     clearRect.  */
  ns_focus (f, &clearRect, 1);
  if (! NSIsEmptyRect (clearRect))
    {
      NSTRACE_RECT ("clearRect", clearRect);

      [[NSColor colorWithUnsignedLong:face->background] set];
      NSRectFill (clearRect);
    }

  NSBezierPath *bmp = [fringe_bmp objectForKey:[NSNumber numberWithInt:p->which]];

  if (bmp == nil
      && p->which < max_used_fringe_bitmap)
    {
      gui_define_fringe_bitmap (f, p->which);
      bmp = [fringe_bmp objectForKey: [NSNumber numberWithInt: p->which]];
    }

  if (bmp)
    {
      NSAffineTransform *transform = [NSAffineTransform transform];
      NSColor *bm_color;

      /* 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 = [NSColor colorWithUnsignedLong:face->foreground];
      else if (p->overlay_p)
        bm_color = [NSColor colorWithUnsignedLong:face->background];
      else
        bm_color = f->output_data.ns->cursor_color;

      [bm_color set];
      [bmp fill];

      [bmp release];
    }
  ns_unfocus (f);
}


static void
ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
		       int x, int y, enum text_cursor_kinds cursor_type,
		       int cursor_width, bool on_p, bool active_p)
/* --------------------------------------------------------------------------
     External call (RIF): draw cursor.
     Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
   -------------------------------------------------------------------------- */
{
  NSRect r, s;
  int fx, fy, h, cursor_height;
  struct frame *f = WINDOW_XFRAME (w);
  struct glyph *phys_cursor_glyph;
  struct glyph *cursor_glyph;

  /* If cursor is out of bounds, don't draw garbage.  This can happen
     in mini-buffer windows when switching between echo area glyphs
     and mini-buffer.  */

  NSTRACE ("ns_draw_window_cursor (on = %d, cursor_type = %d)",
	   on_p, cursor_type);

  if (!on_p)
    return;

  w->phys_cursor_type = cursor_type;
  w->phys_cursor_on_p = on_p;

  if (cursor_type == NO_CURSOR)
    {
      w->phys_cursor_width = 0;
      return;
    }

  if ((phys_cursor_glyph = get_phys_cursor_glyph (w)) == NULL)
    {
      NSTRACE_MSG ("No phys cursor glyph was found!");

      if (glyph_row->exact_window_width_line_p
          && w->phys_cursor.hpos >= glyph_row->used[TEXT_AREA])
        {
          glyph_row->cursor_in_fringe_p = 1;
          draw_fringe_bitmap (w, glyph_row, 0);
        }
      return;
    }

  get_phys_cursor_geometry (w, glyph_row, phys_cursor_glyph, &fx, &fy, &h);

  /* The above get_phys_cursor_geometry call set w->phys_cursor_width
     to the glyph width; replace with CURSOR_WIDTH for (V)BAR cursors.  */
  if (cursor_type == BAR_CURSOR)
    {
      if (cursor_width < 1)
	cursor_width = max (FRAME_CURSOR_WIDTH (f), 1);

      /* The bar cursor should never be wider than the glyph.  */
      if (cursor_width < w->phys_cursor_width)
        w->phys_cursor_width = cursor_width;
    }
  /* If we have an HBAR, "cursor_width" MAY specify height.  */
  else if (cursor_type == HBAR_CURSOR)
    {
      cursor_height = (cursor_width < 1) ? lrint (0.25 * h) : cursor_width;
      if (cursor_height > glyph_row->height)
        cursor_height = glyph_row->height;
      if (h > cursor_height) // Cursor smaller than line height, move down
        fy += h - cursor_height;
      h = cursor_height;
    }

  r.origin.x = fx, r.origin.y = fy;
  r.size.height = h;
  r.size.width = w->phys_cursor_width;

  /* Prevent the cursor from being drawn outside the text area.  */
  r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA));

  ns_focus (f, NULL, 0);

  NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
  [ctx saveGraphicsState];
#ifdef NS_IMPL_GNUSTEP
  GSRectClipList (ctx, &r, 1);
#else
  NSRectClip (r);
#endif

  [FRAME_CURSOR_COLOR (f) set];

  switch (cursor_type)
    {
    case DEFAULT_CURSOR:
    case NO_CURSOR:
      break;
    case FILLED_BOX_CURSOR:
      /* The call to draw_phys_cursor_glyph can end up undoing the
	 ns_focus, so unfocus here and regain focus later.  */
      [ctx restoreGraphicsState];
      ns_unfocus (f);
      draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
      ns_focus (f, &r, 1);
      break;
    case HOLLOW_BOX_CURSOR:
      /* This works like it does in PostScript, not X Windows.  */
      [NSBezierPath strokeRect: NSInsetRect (r, 0.5, 0.5)];
      [ctx restoreGraphicsState];
      break;
    case HBAR_CURSOR:
      NSRectFill (r);
      [ctx restoreGraphicsState];
      break;
    case BAR_CURSOR:
      s = r;
      /* If the character under cursor is R2L, draw the bar cursor
         on the right of its glyph, rather than on the left.  */
      cursor_glyph = get_phys_cursor_glyph (w);
      if ((cursor_glyph->resolved_level & 1) != 0)
        s.origin.x += cursor_glyph->pixel_width - s.size.width;

      NSRectFill (s);
      [ctx restoreGraphicsState];
      break;
    }

  ns_unfocus (f);
}


static void
ns_draw_vertical_window_border (struct window *w, int x, int y0, int y1)
/* --------------------------------------------------------------------------
     External (RIF): Draw a vertical line.
   -------------------------------------------------------------------------- */
{
  struct frame *f = XFRAME (WINDOW_FRAME (w));
  struct face *face;
  NSRect r = NSMakeRect (x, y0, 1, y1-y0);

  NSTRACE ("ns_draw_vertical_window_border");

  face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);

  ns_focus (f, &r, 1);
  if (face)
    [[NSColor colorWithUnsignedLong:face->foreground] set];

  NSRectFill(r);
  ns_unfocus (f);
}


static void
ns_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
/* --------------------------------------------------------------------------
     External (RIF): Draw a window divider.
   -------------------------------------------------------------------------- */
{
  struct frame *f = XFRAME (WINDOW_FRAME (w));
  struct face *face = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FACE_ID);
  struct face *face_first
    = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FIRST_PIXEL_FACE_ID);
  struct face *face_last
    = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_LAST_PIXEL_FACE_ID);
  unsigned long color = face ? face->foreground : FRAME_FOREGROUND_PIXEL (f);
  unsigned long color_first = (face_first
			       ? face_first->foreground
			       : FRAME_FOREGROUND_PIXEL (f));
  unsigned long color_last = (face_last
			      ? face_last->foreground
			      : FRAME_FOREGROUND_PIXEL (f));
  NSRect divider = NSMakeRect (x0, y0, x1-x0, y1-y0);

  NSTRACE ("ns_draw_window_divider");

  ns_focus (f, &divider, 1);

  if ((y1 - y0 > x1 - x0) && (x1 - x0 >= 3))
    /* A vertical divider, at least three pixels wide: Draw first and
       last pixels differently.  */
    {
      [[NSColor colorWithUnsignedLong:color_first] set];
      NSRectFill(NSMakeRect (x0, y0, 1, y1 - y0));
      [[NSColor colorWithUnsignedLong:color] set];
      NSRectFill(NSMakeRect (x0 + 1, y0, x1 - x0 - 2, y1 - y0));
      [[NSColor colorWithUnsignedLong:color_last] set];
      NSRectFill(NSMakeRect (x1 - 1, y0, 1, y1 - y0));
    }
  else if ((x1 - x0 > y1 - y0) && (y1 - y0 >= 3))
    /* A horizontal divider, at least three pixels high: Draw first and
       last pixels differently.  */
    {
      [[NSColor colorWithUnsignedLong:color_first] set];
      NSRectFill(NSMakeRect (x0, y0, x1 - x0, 1));
      [[NSColor colorWithUnsignedLong:color] set];
      NSRectFill(NSMakeRect (x0, y0 + 1, x1 - x0, y1 - y0 - 2));
      [[NSColor colorWithUnsignedLong:color_last] set];
      NSRectFill(NSMakeRect (x0, y1 - 1, x1 - x0, 1));
    }
  else
    {
      /* In any other case do not draw the first and last pixels
         differently.  */
      [[NSColor colorWithUnsignedLong:color] set];
      NSRectFill(divider);
    }

  ns_unfocus (f);
}


static void
ns_show_hourglass (struct frame *f)
{
  /* TODO: add NSProgressIndicator to all frames.  */
}

static void
ns_hide_hourglass (struct frame *f)
{
  /* TODO: remove NSProgressIndicator from all frames.  */
}

/* ==========================================================================

    Glyph drawing operations

   ========================================================================== */

static int
ns_get_glyph_string_clip_rect (struct glyph_string *s, NativeRectangle *nr)
/* --------------------------------------------------------------------------
    Wrapper utility to account for internal border width on full-width lines,
    and allow top full-width rows to hit the frame top.  nr should be pointer
    to two successive NSRects.  Number of rects actually used is returned.
   -------------------------------------------------------------------------- */
{
  int n = get_glyph_string_clip_rects (s, nr, 2);
  return n;
}

/* --------------------------------------------------------------------
   Draw a wavy line under glyph string s. The wave fills wave_height
   pixels from y.

                    x          wave_length = 2
                                 --
                y    *   *   *   *   *
                     |* * * * * * * * *
    wave_height = 3  | *   *   *   *
  --------------------------------------------------------------------- */

static void
ns_draw_underwave (struct glyph_string *s, EmacsCGFloat width, EmacsCGFloat x)
{
  int wave_height = 3, wave_length = 2;
  int y, dx, dy, odd, xmax;
  NSPoint a, b;
  NSRect waveClip;

  dx = wave_length;
  dy = wave_height - 1;
  y =  s->ybase - wave_height + 3;
  xmax = x + width;

  /* Find and set clipping rectangle */
  waveClip = NSMakeRect (x, y, width, wave_height);
  [[NSGraphicsContext currentContext] saveGraphicsState];
  NSRectClip (waveClip);

  /* Draw the waves */
  a.x = x - ((int)(x) % dx) + (EmacsCGFloat) 0.5;
  b.x = a.x + dx;
  odd = (int)(a.x/dx) % 2;
  a.y = b.y = y + 0.5;

  if (odd)
    a.y += dy;
  else
    b.y += dy;

  while (a.x <= xmax)
    {
      [NSBezierPath strokeLineFromPoint:a toPoint:b];
      a.x = b.x, a.y = b.y;
      b.x += dx, b.y = y + 0.5 + odd*dy;
      odd = !odd;
    }

  /* Restore previous clipping rectangle(s) */
  [[NSGraphicsContext currentContext] restoreGraphicsState];
}



static void
ns_draw_text_decoration (struct glyph_string *s, struct face *face,
                         NSColor *defaultCol, CGFloat width, CGFloat x)
/* --------------------------------------------------------------------------
   Draw underline, overline, and strike-through on glyph string s.
   -------------------------------------------------------------------------- */
{
  if (s->for_overlaps)
    return;

  if (s->hl == DRAW_CURSOR)
    [FRAME_BACKGROUND_COLOR (s->f) set];
  else
    [defaultCol set];

  /* Do underline.  */
  if (face->underline)
    {
      if (s->face->underline == FACE_UNDER_WAVE)
        {
          if (!face->underline_defaulted_p)
            [[NSColor colorWithUnsignedLong:face->underline_color] set];

          ns_draw_underwave (s, width, x);
        }
      else if (s->face->underline == FACE_UNDER_LINE)
        {

          NSRect r;
          unsigned long thickness, position;

          /* If the prev was underlined, match its appearance.  */
          if (s->prev
	      && s->prev->face->underline == FACE_UNDER_LINE
              && s->prev->underline_thickness > 0
	      && (s->prev->face->underline_at_descent_line_p
		  == s->face->underline_at_descent_line_p)
	      && (s->prev->face->underline_pixels_above_descent_line
		  == s->face->underline_pixels_above_descent_line))
            {
              thickness = s->prev->underline_thickness;
              position = s->prev->underline_position;
            }
          else
            {
	      struct font *font = font_for_underline_metrics (s);
              unsigned long descent = s->y + s->height - s->ybase;
              unsigned long minimum_offset;
              BOOL underline_at_descent_line, use_underline_position_properties;
	      Lisp_Object val = (WINDOW_BUFFER_LOCAL_VALUE
				 (Qunderline_minimum_offset, s->w));

	      if (FIXNUMP (val))
		minimum_offset = XFIXNAT (val);
	      else
		minimum_offset = 1;

	      val = (WINDOW_BUFFER_LOCAL_VALUE
		     (Qx_underline_at_descent_line, s->w));
	      underline_at_descent_line = (!(NILP (val) || EQ (val, Qunbound))
					   || s->face->underline_at_descent_line_p);

	      val = (WINDOW_BUFFER_LOCAL_VALUE
		     (Qx_use_underline_position_properties, s->w));
	      use_underline_position_properties
		= !(NILP (val) || EQ (val, Qunbound));

              /* Use underline thickness of font, defaulting to 1.  */
              thickness = (font && font->underline_thickness > 0)
                ? font->underline_thickness : 1;

              /* Determine the offset of underlining from the baseline.  */
              if (underline_at_descent_line)
                position = (descent - thickness
			    - s->face->underline_pixels_above_descent_line);
              else if (use_underline_position_properties
                       && font && font->underline_position >= 0)
                position = font->underline_position;
              else if (font)
                position = lround (font->descent / 2);
              else
                position = minimum_offset;

	      if (!s->face->underline_pixels_above_descent_line)
		position = max (position, minimum_offset);

              /* Ensure underlining is not cropped.  */
              if (descent <= position)
                {
                  position = descent - 1;
                  thickness = 1;
                }
              else if (descent < position + thickness)
                thickness = 1;
            }

          s->underline_thickness = thickness;
          s->underline_position = position;

          r = NSMakeRect (x, s->ybase + position, width, thickness);

          if (!face->underline_defaulted_p)
            [[NSColor colorWithUnsignedLong:face->underline_color] set];

          NSRectFill (r);
        }
    }
  /* Do overline. We follow other terms in using a thickness of 1
     and ignoring overline_margin.  */
  if (face->overline_p)
    {
      NSRect r;
      r = NSMakeRect (x, s->y, width, 1);

      if (!face->overline_color_defaulted_p)
        [[NSColor colorWithUnsignedLong:face->overline_color] set];

      NSRectFill (r);
    }

  /* Do strike-through.  We follow other terms for thickness and
     vertical position.  */
  if (face->strike_through_p)
    {
      NSRect r;
      /* Y-coordinate and height of the glyph string's first glyph.
	 We cannot use s->y and s->height because those could be
	 larger if there are taller display elements (e.g., characters
	 displayed with a larger font) in the same glyph row.  */
      int glyph_y = s->ybase - s->first_glyph->ascent;
      int glyph_height = s->first_glyph->ascent + s->first_glyph->descent;
      /* Strike-through width and offset from the glyph string's
	 top edge.  */
      unsigned long h = 1;
      unsigned long dy;

      dy = lrint ((glyph_height - h) / 2);
      r = NSMakeRect (x, glyph_y + dy, width, 1);

      if (!face->strike_through_color_defaulted_p)
        [[NSColor colorWithUnsignedLong:face->strike_through_color] set];

      NSRectFill (r);
    }
}

static void
ns_draw_box (NSRect r, CGFloat hthickness, CGFloat vthickness,
             NSColor *col, char left_p, char right_p)
/* --------------------------------------------------------------------------
    Draw an unfilled rect inside r, optionally leaving left and/or right open.
    Note we can't just use an NSDrawRect command, because of the possibility
    of some sides not being drawn, and because the rect will be filled.
   -------------------------------------------------------------------------- */
{
  NSRect s = r;
  [col set];

  /* top, bottom */
  s.size.height = hthickness;
  NSRectFill (s);
  s.origin.y += r.size.height - hthickness;
  NSRectFill (s);

  s.size.height = r.size.height;
  s.origin.y = r.origin.y;

  /* left, right (optional) */
  s.size.width = vthickness;
  if (left_p)
    NSRectFill (s);
  if (right_p)
    {
      s.origin.x += r.size.width - vthickness;
      NSRectFill (s);
    }
}

/* Set up colors for the relief lines around glyph string S.  */

static void
ns_setup_relief_colors (struct glyph_string *s)
{
  struct ns_output *di = FRAME_OUTPUT_DATA (s->f);
  NSColor *color;

  if (s->face->use_box_color_for_shadows_p)
    color = [NSColor colorWithUnsignedLong: s->face->box_color];
  else
    color = [NSColor colorWithUnsignedLong: s->face->background];

  if (s->hl == DRAW_CURSOR)
    color = FRAME_CURSOR_COLOR (s->f);

  if (color == nil)
    color = [NSColor grayColor];

  if (color != di->relief_background_color)
    {
      [di->relief_background_color release];
      di->relief_background_color = [color retain];
      [di->light_relief_color release];
      di->light_relief_color = [[color highlightWithLevel: 0.4] retain];
      [di->dark_relief_color release];
      di->dark_relief_color = [[color shadowWithLevel: 0.4] retain];
    }
}

static void
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)
/* --------------------------------------------------------------------------
    Draw a relief rect inside r, optionally leaving some sides open.
    Note we can't just use an NSDrawBezel command, because of the possibility
    of some sides not being drawn, and because the rect will be filled.
   -------------------------------------------------------------------------- */
{
  NSRect inner;
  NSBezierPath *p = nil;

  NSTRACE ("ns_draw_relief");

  /* set up colors */
  ns_setup_relief_colors (s);

  /* Calculate the inner rectangle.  */
  inner = outer;

  if (left_p)
    {
      inner.origin.x += vthickness;
      inner.size.width -= vthickness;
    }

  if (right_p)
    inner.size.width -= vthickness;

  if (top_p)
    {
      inner.origin.y += hthickness;
      inner.size.height -= hthickness;
    }

  if (bottom_p)
    inner.size.height -= hthickness;

  struct ns_output *di = FRAME_OUTPUT_DATA (s->f);

  [(raised_p ? di->light_relief_color : di->dark_relief_color) set];

  if (top_p || left_p)
    {
      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 ? di->dark_relief_color : di->light_relief_color) set];

  if (bottom_p || right_p)
    {
      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];
    }

  /* If one of h/vthickness are more than 1, draw the outermost line
     on the respective sides in the black relief color.  */

  if (p)
    [p removeAllPoints];
  else
    p = [NSBezierPath bezierPath];

  if (hthickness > 1 && top_p)
    {
      [p moveToPoint: NSMakePoint (NSMinX (outer),
				   NSMinY (outer) + 0.5)];
      [p lineToPoint: NSMakePoint (NSMaxX (outer),
				   NSMinY (outer) + 0.5)];
    }

  if (hthickness > 1 && bottom_p)
    {
      [p moveToPoint: NSMakePoint (NSMinX (outer),
				   NSMaxY (outer) - 0.5)];
      [p lineToPoint: NSMakePoint (NSMaxX (outer),
				   NSMaxY (outer) - 0.5)];
    }

  if (vthickness > 1 && left_p)
    {
      [p moveToPoint: NSMakePoint (NSMinX (outer) + 0.5,
				   NSMinY (outer) + 0.5)];
      [p lineToPoint: NSMakePoint (NSMinX (outer) + 0.5,
				   NSMaxY (outer) - 0.5)];
    }

  if (vthickness > 1 && left_p)
    {
      [p moveToPoint: NSMakePoint (NSMinX (outer) + 0.5,
				   NSMinY (outer) + 0.5)];
      [p lineToPoint: NSMakePoint (NSMinX (outer) + 0.5,
				   NSMaxY (outer) - 0.5)];
    }

  [di->dark_relief_color set];
  [p stroke];

  if (vthickness > 1 && hthickness > 1)
    {
      [FRAME_BACKGROUND_COLOR (s->f) set];

      if (left_p && top_p)
	[NSBezierPath fillRect: NSMakeRect (NSMinX (outer),
					    NSMinY (outer),
					    1, 1)];

      if (right_p && top_p)
	[NSBezierPath fillRect: NSMakeRect (NSMaxX (outer) - 1,
					    NSMinY (outer),
					    1, 1)];

      if (right_p && bottom_p)
	[NSBezierPath fillRect: NSMakeRect (NSMaxX (outer) - 1,
					    NSMaxY (outer) - 1,
					    1, 1)];

      if (left_p && bottom_p)
	[NSBezierPath fillRect: NSMakeRect (NSMinX (outer),
					    NSMaxY (outer) - 1,
					    1, 1)];
    }
}


static void
ns_dumpglyphs_box_or_relief (struct glyph_string *s)
/* --------------------------------------------------------------------------
      Function modeled after x_draw_glyph_string_box ().
      Sets up parameters for drawing.
   -------------------------------------------------------------------------- */
{
  int right_x, last_x;
  char left_p, right_p;
  struct glyph *last_glyph;
  NSRect r;
  int hthickness, vthickness;
  struct face *face = s->face;

  vthickness = face->box_vertical_line_width;
  hthickness = face->box_horizontal_line_width;

  NSTRACE ("ns_dumpglyphs_box_or_relief");

  last_x = ((s->row->full_width_p && !s->w->pseudo_window_p)
	    ? WINDOW_RIGHT_EDGE_X (s->w)
	    : window_box_right (s->w, s->area));
  if (s->cmp || s->img)
    last_glyph = s->first_glyph;
  else if (s->first_glyph->type == COMPOSITE_GLYPH
	   && s->first_glyph->u.cmp.automatic)
    {
        struct glyph *end = s->row->glyphs[s->area] + s->row->used[s->area];
	struct glyph *g = s->first_glyph;
	for (last_glyph = g++;
	     g < end && g->u.cmp.automatic && g->u.cmp.id == s->cmp_id
	       && g->slice.cmp.to < s->cmp_to;
	     last_glyph = g++)
	  ;
    }
  else
    last_glyph = s->first_glyph + s->nchars - 1;

  right_x = ((s->row->full_width_p && s->extends_to_end_of_line_p
	      ? last_x - 1 : min (last_x, s->x + s->background_width) - 1));

  left_p = (s->first_glyph->left_box_line_p
	    || (s->hl == DRAW_MOUSE_FACE
		&& (s->prev == NULL || s->prev->hl != s->hl)));
  right_p = (last_glyph->right_box_line_p
	     || (s->hl == DRAW_MOUSE_FACE
		 && (s->next == NULL || s->next->hl != s->hl)));

  r = NSMakeRect (s->x, s->y, right_x - s->x + 1, s->height);

  /* TODO: Sometimes box_color is 0 and this seems wrong; should investigate.  */
  if (s->face->box == FACE_SIMPLE_BOX && s->face->box_color)
    {
      ns_draw_box (r, abs (hthickness), abs (vthickness),
                   [NSColor colorWithUnsignedLong:face->box_color],
                   left_p, right_p);
    }
  else
    {
      ns_draw_relief (r, abs (hthickness), abs (vthickness),
                      s->face->box == FACE_RAISED_BOX,
                      1, 1, left_p, right_p, s);
    }
}


static void
ns_maybe_dumpglyphs_background (struct glyph_string *s, char force_p)
/* --------------------------------------------------------------------------
      Modeled after x_draw_glyph_string_background, which draws BG in
      certain cases.  Others are left to the text rendering routine.
   -------------------------------------------------------------------------- */
{
  NSTRACE ("ns_maybe_dumpglyphs_background");

  if (!s->background_filled_p/* || s->hl == DRAW_MOUSE_FACE*/)
    {
      int box_line_width = max (s->face->box_horizontal_line_width, 0);

      if (FONT_HEIGHT (s->font) < s->height - 2 * box_line_width
	  /* When xdisp.c ignores FONT_HEIGHT, we cannot trust font
	     dimensions, since the actual glyphs might be much
	     smaller.  So in that case we always clear the rectangle
	     with background color.  */
	  || FONT_TOO_HIGH (s->font)
          || s->font_not_found_p || s->extends_to_end_of_line_p || force_p)
	{
          struct face *face = s->face;
          if (!face->stipple)
            {
              if (s->hl != DRAW_CURSOR)
                [(NS_FACE_BACKGROUND (face) != 0
                  ? [NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND (face)]
                  : FRAME_BACKGROUND_COLOR (s->f)) set];
              else if (face && (NS_FACE_BACKGROUND (face)
                                == [(NSColor *) FRAME_CURSOR_COLOR (s->f)
                                                unsignedLong]))
                [[NSColor colorWithUnsignedLong:NS_FACE_FOREGROUND (face)] set];
              else
                [FRAME_CURSOR_COLOR (s->f) set];
            }
          else
            {
              struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (s->f);
              [[dpyinfo->bitmaps[face->stipple-1].img stippleMask] set];
            }

	  NSRect r = NSMakeRect (s->x, s->y + box_line_width,
				 s->background_width,
				 s->height - 2 * box_line_width);
	  NSRectFill (r);

	  s->background_filled_p = 1;
	}
    }
}

static void
ns_draw_image_relief (struct glyph_string *s)
{
  int x1, y1, thick;
  bool raised_p, top_p, bot_p, left_p, right_p;
  int extra_x, extra_y;
  int x = s->x;
  int y = s->ybase - image_ascent (s->img, s->face, &s->slice);

  /* If first glyph of S has a left box line, start drawing it to the
     right of that line.  */
  if (s->face->box != FACE_NO_BOX
      && s->first_glyph->left_box_line_p
      && s->slice.x == 0)
    x += max (s->face->box_vertical_line_width, 0);

  /* If there is a margin around the image, adjust x- and y-position
     by that margin.  */
  if (s->slice.x == 0)
    x += s->img->hmargin;
  if (s->slice.y == 0)
    y += s->img->vmargin;

  if (s->hl == DRAW_IMAGE_SUNKEN
      || s->hl == DRAW_IMAGE_RAISED)
    {
      if (s->face->id == TAB_BAR_FACE_ID)
	thick = (tab_bar_button_relief < 0
		 ? DEFAULT_TAB_BAR_BUTTON_RELIEF
		 : min (tab_bar_button_relief, 1000000));
      else
	thick = (tool_bar_button_relief < 0
		 ? DEFAULT_TOOL_BAR_BUTTON_RELIEF
		 : min (tool_bar_button_relief, 1000000));
      raised_p = s->hl == DRAW_IMAGE_RAISED;
    }
  else
    {
      thick = eabs (s->img->relief);
      raised_p = s->img->relief > 0;
    }

  x1 = x + s->slice.width - 1;
  y1 = y + s->slice.height - 1;

  extra_x = extra_y = 0;
  if (s->face->id == TAB_BAR_FACE_ID)
    {
      if (CONSP (Vtab_bar_button_margin)
	  && FIXNUMP (XCAR (Vtab_bar_button_margin))
	  && FIXNUMP (XCDR (Vtab_bar_button_margin)))
	{
	  extra_x = XFIXNUM (XCAR (Vtab_bar_button_margin)) - thick;
	  extra_y = XFIXNUM (XCDR (Vtab_bar_button_margin)) - thick;
	}
      else if (FIXNUMP (Vtab_bar_button_margin))
	extra_x = extra_y = XFIXNUM (Vtab_bar_button_margin) - thick;
    }

  if (s->face->id == TOOL_BAR_FACE_ID)
    {
      if (CONSP (Vtool_bar_button_margin)
	  && FIXNUMP (XCAR (Vtool_bar_button_margin))
	  && FIXNUMP (XCDR (Vtool_bar_button_margin)))
	{
	  extra_x = XFIXNUM (XCAR (Vtool_bar_button_margin));
	  extra_y = XFIXNUM (XCDR (Vtool_bar_button_margin));
	}
      else if (FIXNUMP (Vtool_bar_button_margin))
	extra_x = extra_y = XFIXNUM (Vtool_bar_button_margin);
    }

  top_p = bot_p = left_p = right_p = false;

  if (s->slice.x == 0)
    x -= thick + extra_x, left_p = true;
  if (s->slice.y == 0)
    y -= thick + extra_y, top_p = true;
  if (s->slice.x + s->slice.width == s->img->width)
    x1 += thick + extra_x, right_p = true;
  if (s->slice.y + s->slice.height == s->img->height)
    y1 += thick + extra_y, bot_p = true;

  ns_draw_relief (NSMakeRect (x, y, x1 - x + 1, y1 - y + 1), thick,
		  thick, raised_p, top_p, bot_p, left_p, right_p, s);
}

static void
ns_dumpglyphs_image (struct glyph_string *s, NSRect r)
/* --------------------------------------------------------------------------
      Renders an image and associated borders.
   -------------------------------------------------------------------------- */
{
  EmacsImage *img = s->img->pixmap;
  int box_line_vwidth = max (s->face->box_horizontal_line_width, 0);
  int x = s->x, y = s->ybase - image_ascent (s->img, s->face, &s->slice);
  int bg_x, bg_y, bg_height;
  NSRect br;
  struct face *face = s->face;
  NSColor *tdCol;

  NSTRACE ("ns_dumpglyphs_image");

  if (s->face->box != FACE_NO_BOX
      && s->first_glyph->left_box_line_p && s->slice.x == 0)
    x += max (s->face->box_vertical_line_width, 0);

  bg_x = x;
  bg_y =  s->slice.y == 0 ? s->y : s->y + box_line_vwidth;
  bg_height = s->height;
  /* other terms have this, but was causing problems w/tabbar mode */
  /* - 2 * box_line_vwidth; */

  if (s->slice.x == 0) x += s->img->hmargin;
  if (s->slice.y == 0) y += s->img->vmargin;

  /* Draw BG: if we need larger area than image itself cleared, do that,
     otherwise, since we composite the image under NS (instead of mucking
     with its background color), we must clear just the image area.  */

  [[NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND (face)] set];

  if (bg_height > s->slice.height || s->img->hmargin || s->img->vmargin
      || s->img->mask || s->img->pixmap == 0 || s->width != s->background_width)
    {
      br = NSMakeRect (bg_x, bg_y, s->background_width, bg_height);
      s->background_filled_p = 1;
    }
  else
    {
      br = NSMakeRect (x, y, s->slice.width, s->slice.height);
    }

  NSRectFill (br);

  /* Draw the image... do we need to draw placeholder if img == nil?  */
  if (img != nil)
    {
      /* The idea here is that the clipped area is set in the normal
         view coordinate system, then we transform the coordinate
         system so that when we draw the image it is rotated, resized
         or whatever as required.  This is kind of backwards, but
         there's no way to apply the transform to the image without
         creating a whole new bitmap.  */
      NSRect dr = NSMakeRect (x, y, s->slice.width, s->slice.height);
      NSRect ir = NSMakeRect (0, 0, [img size].width, [img size].height);

      NSAffineTransform *setOrigin = [NSAffineTransform transform];

      [[NSGraphicsContext currentContext] saveGraphicsState];

      /* Because of the transforms it's difficult to work out what
         portion of the original, untransformed, image will be drawn,
         so the clipping area will ensure we draw only the correct
         bit.  */
      NSRectClip (dr);

      [setOrigin translateXBy:x - s->slice.x yBy:y - s->slice.y];
      [setOrigin concat];

      NSAffineTransform *doTransform = [NSAffineTransform transform];

      /* ImageMagick images don't have transforms.  */
      if (img->transform)
        [doTransform appendTransform:img->transform];

      [doTransform concat];

      /* Smoothing is the default, so if we don't want smoothing we
         have to turn it off.  */
      if (! img->smoothing)
        [[NSGraphicsContext currentContext]
          setImageInterpolation:NSImageInterpolationNone];

      [img drawInRect:ir fromRect:ir
            operation:NSCompositingOperationSourceOver
             fraction:1.0 respectFlipped:YES hints:nil];

      /* Apparently image interpolation is not reset with
         restoreGraphicsState, so we have to manually reset it.  */
      if (! img->smoothing)
        [[NSGraphicsContext currentContext]
          setImageInterpolation:NSImageInterpolationDefault];

      [[NSGraphicsContext currentContext] restoreGraphicsState];
    }

  if (s->hl == DRAW_CURSOR)
    {
      [FRAME_CURSOR_COLOR (s->f) set];
      tdCol = [NSColor colorWithUnsignedLong: NS_FACE_BACKGROUND (face)];
    }
  else
    tdCol = [NSColor colorWithUnsignedLong: NS_FACE_FOREGROUND (face)];

  /* Draw underline, overline, strike-through.  */
  ns_draw_text_decoration (s, face, tdCol, br.size.width, br.origin.x);

  /* If we must draw a relief around the image, do it.  */
  if (s->img->relief
      || s->hl == DRAW_IMAGE_RAISED
      || s->hl == DRAW_IMAGE_SUNKEN)
    ns_draw_image_relief (s);

  /* If there is no mask, the background won't be seen, so draw a
     rectangle on the image for the cursor.  Do this for all images,
     getting transparency right is not reliable.  */
  if (s->hl == DRAW_CURSOR)
    {
      int thickness = abs (s->img->relief);
      if (thickness == 0) thickness = 1;
      ns_draw_box (br, thickness, thickness,
		   FRAME_CURSOR_COLOR (s->f), 1, 1);
    }
}


static void
ns_draw_stretch_glyph_string (struct glyph_string *s)
{
  struct face *face;
  NSColor *fg_color;

  if (s->hl == DRAW_CURSOR
      && !x_stretch_cursor_p)
    {
      /* If `x-stretch-cursor' is nil, don't draw a block cursor as
	 wide as the stretch glyph.  */
      int width, background_width = s->background_width;
      int x = s->x;

      if (!s->row->reversed_p)
	{
	  int left_x = window_box_left_offset (s->w, TEXT_AREA);

	  if (x < left_x)
	    {
	      background_width -= left_x - x;
	      x = left_x;
	    }
	}
      else
	{
	  /* In R2L rows, draw the cursor on the right edge of the
	     stretch glyph.  */
	  int right_x = window_box_right (s->w, TEXT_AREA);

	  if (x + background_width > right_x)
	    background_width -= x - right_x;
	  x += background_width;
	}

      width = min (FRAME_COLUMN_WIDTH (s->f), background_width);
      if (s->row->reversed_p)
	x -= width;

      if (s->hl == DRAW_CURSOR)
	[FRAME_CURSOR_COLOR (s->f) set];
      else
	[[NSColor colorWithUnsignedLong: s->face->foreground] set];

      NSRectFill (NSMakeRect (x, s->y, width, s->height));

      /* Clear rest using the GC of the original non-cursor face.  */
      if (width < background_width)
	{
	  int y = s->y;
	  int w = background_width - width, h = s->height;

	  if (!s->row->reversed_p)
	    x += width;
	  else
	    x = s->x;

	  if (s->row->mouse_face_p
	      && cursor_in_mouse_face_p (s->w))
	    {
	      face = FACE_FROM_ID_OR_NULL (s->f,
					   MOUSE_HL_INFO (s->f)->mouse_face_face_id);

	      if (!s->face)
		face = FACE_FROM_ID (s->f, MOUSE_FACE_ID);
	      prepare_face_for_display (s->f, face);

	      [[NSColor colorWithUnsignedLong: face->background] set];
	    }
	  else
	    [[NSColor colorWithUnsignedLong: s->face->background] set];
	  NSRectFill (NSMakeRect (x, y, w, h));
	}
    }
  else if (!s->background_filled_p)
    {
      int background_width = s->background_width;
      int x = s->x, text_left_x = window_box_left (s->w, TEXT_AREA);

      /* Don't draw into left fringe or scrollbar area except for
         header line and mode line.  */
      if (s->area == TEXT_AREA
	  && x < text_left_x && !s->row->mode_line_p)
	{
	  background_width -= text_left_x - x;
	  x = text_left_x;
	}

      if (!s->row->stipple_p)
	s->row->stipple_p = s->stippled_p;

      if (background_width > 0)
	{
	  if (s->hl == DRAW_CURSOR)
	    [FRAME_CURSOR_COLOR (s->f) set];
	  else
	    [[NSColor colorWithUnsignedLong: s->face->background] set];

	  NSRectFill (NSMakeRect (x, s->y, background_width, s->height));
	}
    }

  /* Draw overlining, etc. on the stretch glyph (or the part of the
     stretch glyph after the cursor).  If the glyph has a box, then
     decorations will be drawn after drawing the box in
     ns_draw_glyph_string, in order to prevent them from being
     overwritten by the box.  */
  if (s->face->box == FACE_NO_BOX)
    {
      fg_color = [NSColor colorWithUnsignedLong:
			    NS_FACE_FOREGROUND (s->face)];
      ns_draw_text_decoration (s, s->face, fg_color,
			       s->background_width, s->x);
    }
}

static void
ns_draw_glyph_string_foreground (struct glyph_string *s)
{
  int x;
  struct font *font = s->font;

  /* If first glyph of S has a left box line, start drawing the text
     of S to the right of that box line.  */
  if (s->face && s->face->box != FACE_NO_BOX
      && s->first_glyph->left_box_line_p)
    x = s->x + max (s->face->box_vertical_line_width, 0);
  else
    x = s->x;

  font->driver->draw
    (s, s->cmp_from, s->nchars, x, s->ybase,
     !s->for_overlaps && !s->background_filled_p);
}


static void
ns_draw_composite_glyph_string_foreground (struct glyph_string *s)
{
  int i, j, x;
  struct font *font = s->font;

  /* If first glyph of S has a left box line, start drawing the text
     of S to the right of that box line.  */
  if (s->face && s->face->box != FACE_NO_BOX
      && s->first_glyph->left_box_line_p)
    x = s->x + max (s->face->box_vertical_line_width, 0);
  else
    x = s->x;

  /* S is a glyph string for a composition.  S->cmp_from is the index
     of the first character drawn for glyphs of this composition.
     S->cmp_from == 0 means we are drawing the very first character of
     this composition.  */

  /* Draw a rectangle for the composition if the font for the very
     first character of the composition could not be loaded.  */
  if (s->font_not_found_p)
    {
      if (s->cmp_from == 0)
        {
          NSRect r = NSMakeRect (s->x, s->y, s->width-1, s->height -1);
          ns_draw_box (r, 1, 1, FRAME_CURSOR_COLOR (s->f), 1, 1);
        }
    }
  else if (! s->first_glyph->u.cmp.automatic)
    {
      int y = s->ybase;

      for (i = 0, j = s->cmp_from; i < s->nchars; i++, j++)
	/* TAB in a composition means display glyphs with padding
	   space on the left or right.  */
	if (COMPOSITION_GLYPH (s->cmp, j) != '\t')
	  {
	    int xx = x + s->cmp->offsets[j * 2];
	    int yy = y - s->cmp->offsets[j * 2 + 1];

	    font->driver->draw (s, j, j + 1, xx, yy, false);
	    if (s->face->overstrike)
	      font->driver->draw (s, j, j + 1, xx + 1, yy, false);
	  }
    }
  else
    {
      Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
      Lisp_Object glyph;
      int y = s->ybase;
      int width = 0;

      for (i = j = s->cmp_from; i < s->cmp_to; i++)
	{
	  glyph = LGSTRING_GLYPH (gstring, i);
	  if (NILP (LGLYPH_ADJUSTMENT (glyph)))
	    width += LGLYPH_WIDTH (glyph);
	  else
	    {
	      int xoff, yoff, wadjust;

	      if (j < i)
		{
		  font->driver->draw (s, j, i, x, y, false);
		  if (s->face->overstrike)
		    font->driver->draw (s, j, i, x + 1, y, false);
		  x += width;
		}
	      xoff = LGLYPH_XOFF (glyph);
	      yoff = LGLYPH_YOFF (glyph);
	      wadjust = LGLYPH_WADJUST (glyph);
	      font->driver->draw (s, i, i + 1, x + xoff, y + yoff, false);
	      if (s->face->overstrike)
		font->driver->draw (s, i, i + 1, x + xoff + 1, y + yoff,
				    false);
	      x += wadjust;
	      j = i + 1;
	      width = 0;
	    }
	}
      if (j < i)
	{
	  font->driver->draw (s, j, i, x, y, false);
	  if (s->face->overstrike)
	    font->driver->draw (s, j, i, x + 1, y, false);
	}
    }
}

/* Draw the foreground of glyph string S for glyphless characters.  */
static void
ns_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
{
  struct glyph *glyph = s->first_glyph;
  NSGlyph char2b[8];
  int x, i, j;

  /* If first glyph of S has a left box line, start drawing the text
     of S to the right of that box line.  */
  if (s->face && s->face->box != FACE_NO_BOX
      && s->first_glyph->left_box_line_p)
    x = s->x + max (s->face->box_vertical_line_width, 0);
  else
    x = s->x;

  s->char2b = char2b;

  for (i = 0; i < s->nchars; i++, glyph++)
    {
#ifdef GCC_LINT
      enum { PACIFY_GCC_BUG_81401 = 1 };
#else
      enum { PACIFY_GCC_BUG_81401 = 0 };
#endif
      char buf[8 + PACIFY_GCC_BUG_81401];
      char *str = NULL;
      int len = glyph->u.glyphless.len;

      if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_ACRONYM)
	{
	  if (len > 0
	      && CHAR_TABLE_P (Vglyphless_char_display)
	      && (CHAR_TABLE_EXTRA_SLOTS (XCHAR_TABLE (Vglyphless_char_display))
		  >= 1))
	    {
	      Lisp_Object acronym
		= (! glyph->u.glyphless.for_no_font
		   ? CHAR_TABLE_REF (Vglyphless_char_display,
				     glyph->u.glyphless.ch)
		   : XCHAR_TABLE (Vglyphless_char_display)->extras[0]);
	      if (CONSP (acronym))
		acronym = XCAR (acronym);
	      if (STRINGP (acronym))
		str = SSDATA (acronym);
	    }
	}
      else if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_HEX_CODE)
	{
	  unsigned int ch = glyph->u.glyphless.ch;
	  eassume (ch <= MAX_CHAR);
	  snprintf (buf, 8, "%0*X", ch < 0x10000 ? 4 : 6, ch);
	  str = buf;
	}

      if (str)
	{
	  int upper_len = (len + 1) / 2;

	  /* It is assured that all LEN characters in STR is ASCII.  */
	  for (j = 0; j < len; j++)
            char2b[j] = s->font->driver->encode_char (s->font, str[j]) & 0xFFFF;
	  s->font->driver->draw (s, 0, upper_len,
				 x + glyph->slice.glyphless.upper_xoff,
				 s->ybase + glyph->slice.glyphless.upper_yoff,
				 false);
	  s->font->driver->draw (s, upper_len, len,
				 x + glyph->slice.glyphless.lower_xoff,
				 s->ybase + glyph->slice.glyphless.lower_yoff,
				 false);
	}
      if (glyph->u.glyphless.method != GLYPHLESS_DISPLAY_THIN_SPACE)
        ns_draw_box (NSMakeRect (x, s->ybase - glyph->ascent,
                                 glyph->pixel_width - 1,
                                 glyph->ascent + glyph->descent - 1),
                     1, 1,
                     [NSColor colorWithUnsignedLong:NS_FACE_FOREGROUND (s->face)],
                     YES, YES);
      x += glyph->pixel_width;
   }

  /* GCC 12 complains even though nothing ever uses s->char2b after
     this function returns.  */
  s->char2b = NULL;
}

static void
ns_draw_glyph_string (struct glyph_string *s)
/* --------------------------------------------------------------------------
      External (RIF): Main draw-text call.
   -------------------------------------------------------------------------- */
{
  /* TODO (optimize): focus for box and contents draw */
  NSRect r[2];
  int n;
  char box_drawn_p = 0;
  struct font *font = s->face->font;
  if (! font) font = FRAME_FONT (s->f);

  NSTRACE ("ns_draw_glyph_string (hl = %u)", s->hl);

  if (s->next && s->right_overhang && !s->for_overlaps)
    {
      int width;
      struct glyph_string *next;

      for (width = 0, next = s->next;
	   next && width < s->right_overhang;
	   width += next->width, next = next->next)
	if (next->first_glyph->type != IMAGE_GLYPH)
          {
	    n = ns_get_glyph_string_clip_rect (s->next, r);
	    ns_focus (s->f, r, n);
            if (next->first_glyph->type != STRETCH_GLYPH)
	      ns_maybe_dumpglyphs_background (s->next, 1);
	    else
	      ns_draw_stretch_glyph_string (s->next);
	    ns_unfocus (s->f);
            next->num_clips = 0;
          }
    }

  if (!s->for_overlaps && s->face->box != FACE_NO_BOX
        && (s->first_glyph->type == CHAR_GLYPH
	    || s->first_glyph->type == COMPOSITE_GLYPH))
    {
      n = ns_get_glyph_string_clip_rect (s, r);
      ns_focus (s->f, r, n);
      ns_maybe_dumpglyphs_background (s, 1);
      ns_dumpglyphs_box_or_relief (s);
      ns_unfocus (s->f);
      box_drawn_p = 1;
    }

  n = ns_get_glyph_string_clip_rect (s, r);

  if (!s->clip_head /* draw_glyphs didn't specify a clip mask. */
      && !s->clip_tail
      && ((s->prev && s->prev->hl != s->hl && s->left_overhang)
	  || (s->next && s->next->hl != s->hl && s->right_overhang)))
    r[0] = NSIntersectionRect (r[0], NSMakeRect (s->x, s->y, s->width, s->height));

  ns_focus (s->f, r, n);

  switch (s->first_glyph->type)
    {

    case IMAGE_GLYPH:
      ns_dumpglyphs_image (s, r[0]);
      break;

    case XWIDGET_GLYPH:
      x_draw_xwidget_glyph_string (s);
      break;

    case STRETCH_GLYPH:
      ns_draw_stretch_glyph_string (s);
      break;

    case CHAR_GLYPH:
    case COMPOSITE_GLYPH:
      {
	BOOL isComposite = s->first_glyph->type == COMPOSITE_GLYPH;
	if (s->for_overlaps || (isComposite
				&& (s->cmp_from > 0
				    && ! s->first_glyph->u.cmp.automatic)))
	  s->background_filled_p = 1;
	else
	  ns_maybe_dumpglyphs_background
	    (s, s->first_glyph->type == COMPOSITE_GLYPH);

	if (isComposite)
	  ns_draw_composite_glyph_string_foreground (s);
	else
	  ns_draw_glyph_string_foreground (s);

	{
	  NSColor *col = (NS_FACE_FOREGROUND (s->face) != 0
			  ? [NSColor colorWithUnsignedLong:NS_FACE_FOREGROUND (s->face)]
			  : FRAME_FOREGROUND_COLOR (s->f));

	  /* Draw underline, overline, strike-through. */
	  ns_draw_text_decoration (s, s->face, col, s->width, s->x);
	}
      }

      break;

    case GLYPHLESS_GLYPH:
      if (s->for_overlaps || (s->cmp_from > 0
			      && ! s->first_glyph->u.cmp.automatic))
        s->background_filled_p = 1;
      else
        ns_maybe_dumpglyphs_background
          (s, s->first_glyph->type == COMPOSITE_GLYPH);
      ns_draw_glyphless_glyph_string_foreground (s);
      break;

    default:
      emacs_abort ();
    }

  /* Draw box if not done already.  */
  if (!s->for_overlaps && !box_drawn_p && s->face->box != FACE_NO_BOX)
    ns_dumpglyphs_box_or_relief (s);

  if (s->face->box != FACE_NO_BOX
      && s->first_glyph->type == STRETCH_GLYPH)
    {
      NSColor *fg_color;

      fg_color = [NSColor colorWithUnsignedLong: NS_FACE_FOREGROUND (s->face)];

      ns_draw_text_decoration (s, s->face, fg_color,
			       s->background_width, s->x);
    }

  ns_unfocus (s->f);

  /* Draw surrounding overhangs. */
  if (s->prev)
    {
      ns_focus (s->f, NULL, 0);
      struct glyph_string *prev;

      for (prev = s->prev; prev; prev = prev->prev)
	if (prev->hl != s->hl
	    && prev->x + prev->width + prev->right_overhang > s->x)
	  {
	    /* As prev was drawn while clipped to its own area, we
	       must draw the right_overhang part using s->hl now.  */
	    enum draw_glyphs_face save = prev->hl;

	    prev->hl = s->hl;
	    NSRect r = NSMakeRect (s->x, s->y, s->width, s->height);
	    NSRect rc;
	    get_glyph_string_clip_rect (s, &rc);
	    [[NSGraphicsContext currentContext] saveGraphicsState];
	    NSRectClip (r);
	    if (n)
	      NSRectClip (rc);
#ifdef NS_IMPL_GNUSTEP
	    DPSgsave ([NSGraphicsContext currentContext]);
	    DPSrectclip ([NSGraphicsContext currentContext], s->x, s->y,
			 s->width, s->height);
	    DPSrectclip ([NSGraphicsContext currentContext], NSMinX (rc),
			 NSMinY (rc), NSWidth (rc), NSHeight (rc));
#endif
	    if (prev->first_glyph->type == CHAR_GLYPH)
	      ns_draw_glyph_string_foreground (prev);
	    else
	      ns_draw_composite_glyph_string_foreground (prev);
#ifdef NS_IMPL_GNUSTEP
	    DPSgrestore ([NSGraphicsContext currentContext]);
#endif
	    [[NSGraphicsContext currentContext] restoreGraphicsState];
	    prev->hl = save;
	  }
      ns_unfocus (s->f);
    }

  if (s->next)
    {
      ns_focus (s->f, NULL, 0);
      struct glyph_string *next;

      for (next = s->next; next; next = next->next)
	if (next->hl != s->hl
	    && next->x - next->left_overhang < s->x + s->width)
	  {
	    /* As next will be drawn while clipped to its own area,
	       we must draw the left_overhang part using s->hl now.  */
	    enum draw_glyphs_face save = next->hl;

	    next->hl = s->hl;
	    NSRect r = NSMakeRect (s->x, s->y, s->width, s->height);
	    NSRect rc;
	    get_glyph_string_clip_rect (s, &rc);
	    [[NSGraphicsContext currentContext] saveGraphicsState];
	    NSRectClip (r);
	    NSRectClip (rc);
#ifdef NS_IMPL_GNUSTEP
	    DPSgsave ([NSGraphicsContext currentContext]);
	    DPSrectclip ([NSGraphicsContext currentContext], s->x, s->y,
			 s->width, s->height);
	    DPSrectclip ([NSGraphicsContext currentContext], NSMinX (rc),
			 NSMinY (rc), NSWidth (rc), NSHeight (rc));
#endif
	    if (next->first_glyph->type == CHAR_GLYPH)
	      ns_draw_glyph_string_foreground (next);
	    else
	      ns_draw_composite_glyph_string_foreground (next);
#ifdef NS_IMPL_GNUSTEP
	    DPSgrestore ([NSGraphicsContext currentContext]);
#endif
	    [[NSGraphicsContext currentContext] restoreGraphicsState];
	    next->hl = save;
	    next->clip_head = s->next;
	  }
      ns_unfocus (s->f);
    }
  s->num_clips = 0;
}



/* ==========================================================================

    Event loop

   ========================================================================== */


static void
ns_send_appdefined (int value)
/* --------------------------------------------------------------------------
    Internal: post an appdefined event which EmacsApp-sendEvent will
              recognize and take as a command to halt the event loop.
   -------------------------------------------------------------------------- */
{
  NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "ns_send_appdefined(%d)", value);

  // GNUstep needs postEvent to happen on the main thread.
  // Cocoa needs nextEventMatchingMask to happen on the main thread too.
  if (! [[NSThread currentThread] isMainThread])
    {
      EmacsApp *app = (EmacsApp *)NSApp;
      app->nextappdefined = value;
      [app performSelectorOnMainThread:@selector (sendFromMainThread:)
                            withObject:nil
                         waitUntilDone:NO];
      return;
    }

  /* Only post this event if we haven't already posted one.  This will end
     the [NXApp run] main loop after having processed all events queued at
     this moment.  */

#ifdef NS_IMPL_COCOA
  if (! send_appdefined)
    {
      /* OS X 10.10.1 swallows the AppDefined event we are sending ourselves
         in certain situations (rapid incoming events).
         So check if we have one, if not add one.  */
      NSEvent *appev = [NSApp nextEventMatchingMask:NSEventMaskApplicationDefined
                                          untilDate:[NSDate distantPast]
                                             inMode:NSDefaultRunLoopMode
                                            dequeue:NO];
      if (! appev) send_appdefined = YES;
    }
#endif

  if (send_appdefined)
    {
      NSEvent *nxev;

      /* We only need one NX_APPDEFINED event to stop NXApp from running.  */
      send_appdefined = NO;

      /* Don't need wakeup timer any more.  */
      if (timed_entry)
        {
          [timed_entry invalidate];
          [timed_entry release];
          timed_entry = nil;
        }

      nxev = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
                                location: NSMakePoint (0, 0)
                           modifierFlags: 0
                               timestamp: 0
                            windowNumber: [[NSApp mainWindow] windowNumber]
                                 context: [NSApp context]
                                 subtype: 0
                                   data1: value
                                   data2: 0];

      /* Post an application defined event on the event queue.  When this is
         received the [NXApp run] will return, thus having processed all
         events which are currently queued.  */
      [NSApp postEvent: nxev atStart: NO];
    }
}

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
static void
check_native_fs ()
{
  Lisp_Object frame, tail;

  if (ns_last_use_native_fullscreen == ns_use_native_fullscreen)
    return;

  ns_last_use_native_fullscreen = ns_use_native_fullscreen;

  FOR_EACH_FRAME (tail, frame)
    {
      struct frame *f = XFRAME (frame);
      if (FRAME_NS_P (f))
        {
          EmacsView *view = FRAME_NS_VIEW (f);
          [view updateCollectionBehavior];
        }
    }
}
#endif


static int
ns_read_socket_1 (struct terminal *terminal, struct input_event *hold_quit,
		  BOOL no_release)
/* --------------------------------------------------------------------------
     External (hook): Post an event to ourself and keep reading events until
     we read it back again.  In effect process all events which were waiting.
     From 21+ we have to manage the event buffer ourselves.

     NO_RELEASE means not to touch the global autorelease pool.
   -------------------------------------------------------------------------- */
{
  struct input_event ev;
  int nevents;

  NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "ns_read_socket");

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  check_native_fs ();
#endif

  if ([NSApp modalWindow] != nil)
    return -1;

  if (hold_event_q.nr > 0)
    {
      int i;
      for (i = 0; i < hold_event_q.nr; ++i)
        kbd_buffer_store_event_hold (&hold_event_q.q[i], hold_quit);
      hold_event_q.nr = 0;
      return i;
    }

  if ([NSThread isMainThread])
    {
      block_input ();
      n_emacs_events_pending = 0;
      ns_init_events (&ev);
      q_event_ptr = hold_quit;

      if (!no_release)
	{
	  /* We manage autorelease pools by allocate/reallocate each time around
	     the loop; strict nesting is occasionally violated but seems not to
	     matter... earlier methods using full nesting caused major memory leaks.  */
	  [outerpool release];
	  outerpool = [[NSAutoreleasePool alloc] init];
	}

      /* If have pending open-file requests, attend to the next one of those.  */
      if (ns_pending_files && [ns_pending_files count] != 0
          && [(EmacsApp *)NSApp openFile: [ns_pending_files objectAtIndex: 0]])
        {
          [ns_pending_files removeObjectAtIndex: 0];
        }
      /* Deal with pending service requests.  */
      else if (ns_pending_service_names && [ns_pending_service_names count] != 0
               && [(EmacsApp *)
                    NSApp fulfillService: [ns_pending_service_names objectAtIndex: 0]
                                 withArg: [ns_pending_service_args objectAtIndex: 0]])
        {
          [ns_pending_service_names removeObjectAtIndex: 0];
          [ns_pending_service_args removeObjectAtIndex: 0];
        }
      else
        {
          /* Run and wait for events.  We must always send one NX_APPDEFINED event
             to ourself, otherwise [NXApp run] will never exit.  */
          send_appdefined = YES;
          ns_send_appdefined (-1);

          [NSApp run];
        }

      nevents = n_emacs_events_pending;
      n_emacs_events_pending = 0;
      ns_finish_events ();
      q_event_ptr = NULL;
      unblock_input ();
    }
  else
    return -1;

  return nevents;
}

static int
ns_read_socket (struct terminal *terminal, struct input_event *hold_quit)
{
  return ns_read_socket_1 (terminal, hold_quit, NO);
}


static int
ns_select_1 (int nfds, fd_set *readfds, fd_set *writefds,
	     fd_set *exceptfds, struct timespec *timeout,
	     sigset_t *sigmask, BOOL run_loop_only)
/* --------------------------------------------------------------------------
     Replacement for select, checking for events
   -------------------------------------------------------------------------- */
{
  int result;
  int t, k, nr = 0;
  struct input_event event;
  char c;

  NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "ns_select");

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  check_native_fs ();
#endif

  if (hold_event_q.nr > 0 && !run_loop_only)
    {
      /* We already have events pending.  */
      raise (SIGIO);
      errno = EINTR;
      return -1;
    }

  eassert (nfds <= FD_SETSIZE);
  for (k = 0; k < nfds; k++)
    {
      if (readfds && FD_ISSET(k, readfds)) ++nr;
      if (writefds && FD_ISSET(k, writefds)) ++nr;
    }

  if (NSApp == nil
      || ![NSThread isMainThread]
      || (timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0))
    return thread_select (pselect, nfds, readfds, writefds,
			  exceptfds, timeout, sigmask);
  else
    {
      struct timespec t = {0, 0};
      thread_select (pselect, 0, NULL, NULL, NULL, &t, sigmask);
    }

  /* FIXME: This draining of outerpool causes a crash when a buffer
     running over tramp is displayed and the user tries to use the
     menus.  I believe some other autorelease pool's lifetime
     straddles this call causing a violation of autorelease pool
     nesting.  There's no good reason to keep these here since the
     pool will be drained some other time anyway, but removing them
     leaves the menus sometimes not opening until the user moves their
     mouse pointer, but that's better than a crash.

     There must be something about running external processes like
     tramp that interferes with the modal menu code.

     See bugs 24472, 37557, 37922.  */

  // [outerpool release];
  // outerpool = [[NSAutoreleasePool alloc] init];


  send_appdefined = YES;
  if (nr > 0)
    {
      pthread_mutex_lock (&select_mutex);
      select_nfds = nfds;
      select_valid = 0;
      if (readfds)
        {
          select_readfds = *readfds;
          select_valid += SELECT_HAVE_READ;
        }
      if (writefds)
        {
          select_writefds = *writefds;
          select_valid += SELECT_HAVE_WRITE;
        }

      if (timeout)
        {
          select_timeout = *timeout;
          select_valid += SELECT_HAVE_TMO;
        }

      pthread_mutex_unlock (&select_mutex);

      /* Inform fd_handler that select should be called.  */
      c = 'g';
      emacs_write_sig (selfds[1], &c, 1);
    }
  else if (nr == 0 && timeout)
    {
      /* No file descriptor, just a timeout, no need to wake fd_handler.  */
      double time = timespectod (*timeout);
      timed_entry = [[NSTimer scheduledTimerWithTimeInterval: time
                                                      target: NSApp
                                                    selector:
                                  @selector (timeout_handler:)
                                                    userInfo: 0
                                                     repeats: NO]
                      retain];
    }
  else /* No timeout and no file descriptors, can this happen?  */
    {
      /* Send appdefined so we exit from the loop.  */
      ns_send_appdefined (-1);
    }

  block_input ();
  ns_init_events (&event);

  [NSApp run];

  ns_finish_events ();
  if (nr > 0 && readfds)
    {
      c = 's';
      emacs_write_sig (selfds[1], &c, 1);
    }
  unblock_input ();

  t = last_appdefined_event_data;

  if (t != NO_APPDEFINED_DATA)
    {
      last_appdefined_event_data = NO_APPDEFINED_DATA;

      if (t == -2)
        {
          /* The NX_APPDEFINED event we received was a timeout.  */
          result = 0;
        }
      else if (t == -1)
        {
          /* The NX_APPDEFINED event we received was the result of
             at least one real input event arriving.  */
          errno = EINTR;
          result = -1;
        }
      else
        {
          /* Received back from select () in fd_handler; copy the results.  */
          pthread_mutex_lock (&select_mutex);
          if (readfds) *readfds = select_readfds;
          if (writefds) *writefds = select_writefds;
          pthread_mutex_unlock (&select_mutex);
          result = t;
        }
    }
  else
    {
      errno = EINTR;
      result = -1;
    }

  return result;
}

int
ns_select (int nfds, fd_set *readfds, fd_set *writefds,
	   fd_set *exceptfds, struct timespec *timeout,
	   sigset_t *sigmask)
{
  return ns_select_1 (nfds, readfds, writefds, exceptfds,
		      timeout, sigmask, NO);
}

#ifdef HAVE_PTHREAD
void
ns_run_loop_break (void)
/* Break out of the NS run loop in ns_select or ns_read_socket.  */
{
  NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "ns_run_loop_break");

  /* If we don't have a GUI, don't send the event.  */
  if (NSApp != NULL)
    ns_send_appdefined(-1);
}
#endif


/* ==========================================================================

    Scrollbar handling

   ========================================================================== */


static void
ns_set_vertical_scroll_bar (struct window *window,
                           int portion, int whole, int position)
/* --------------------------------------------------------------------------
      External (hook): Update or add scrollbar
   -------------------------------------------------------------------------- */
{
  Lisp_Object win;
  NSRect r, v;
  struct frame *f = XFRAME (WINDOW_FRAME (window));
  EmacsView *view = FRAME_NS_VIEW (f);
  EmacsScroller *bar;
  int window_y, window_height;
  int top, left, height, width;
  BOOL update_p = YES;

  /* Optimization; display engine sends WAY too many of these.  */
  if (!NILP (window->vertical_scroll_bar))
    {
      bar = XNS_SCROLL_BAR (window->vertical_scroll_bar);
      if ([bar checkSamePosition: position portion: portion whole: whole])
        {
          if (view->scrollbarsNeedingUpdate == 0)
            {
              if (!windows_or_buffers_changed)
                  return;
            }
          else
            view->scrollbarsNeedingUpdate--;
          update_p = NO;
        }
    }

  NSTRACE ("ns_set_vertical_scroll_bar");

  /* Get dimensions.  */
  window_box (window, ANY_AREA, 0, &window_y, 0, &window_height);
  top = window_y;
  height = window_height;
  width = NS_SCROLL_BAR_WIDTH (f);
  left = WINDOW_SCROLL_BAR_AREA_X (window);

  r = NSMakeRect (left, top, width, height);
  /* The parent view is flipped, so we need to flip y value.  */
  v = [view frame];
  r.origin.y = (v.size.height - r.size.height - r.origin.y);

  XSETWINDOW (win, window);
  block_input ();

  /* We want at least 5 lines to display a scrollbar.  */
  if (WINDOW_TOTAL_LINES (window) < 5)
    {
      if (!NILP (window->vertical_scroll_bar))
        {
          bar = XNS_SCROLL_BAR (window->vertical_scroll_bar);
          [bar removeFromSuperview];
          wset_vertical_scroll_bar (window, Qnil);
          [bar release];
          ns_clear_frame_area (f, left, top, width, height);
        }
      unblock_input ();
      return;
    }

  if (NILP (window->vertical_scroll_bar))
    {
      if (width > 0 && height > 0)
	ns_clear_frame_area (f, left, top, width, height);

      bar = [[EmacsScroller alloc] initFrame: r window: win];
      wset_vertical_scroll_bar (window, make_mint_ptr (bar));
      update_p = YES;
    }
  else
    {
      NSRect oldRect;
      bar = XNS_SCROLL_BAR (window->vertical_scroll_bar);
      oldRect = [bar frame];
      r.size.width = oldRect.size.width;
      if (FRAME_LIVE_P (f) && !NSEqualRects (oldRect, r))
        {
          if (! NSEqualRects (oldRect, r))
              ns_clear_frame_area (f, left, top, width, height);
          [bar setFrame: r];
        }
    }

  if (update_p)
    [bar setPosition: position portion: portion whole: whole];
  unblock_input ();
}


static void
ns_set_horizontal_scroll_bar (struct window *window,
			      int portion, int whole, int position)
/* --------------------------------------------------------------------------
      External (hook): Update or add scrollbar.
   -------------------------------------------------------------------------- */
{
  Lisp_Object win;
  NSRect r, v;
  struct frame *f = XFRAME (WINDOW_FRAME (window));
  EmacsView *view = FRAME_NS_VIEW (f);
  EmacsScroller *bar;
  int top, height, left, width;
  int window_x, window_width;
  BOOL update_p = YES;

  /* Optimization; display engine sends WAY too many of these.  */
  if (!NILP (window->horizontal_scroll_bar))
    {
      bar = XNS_SCROLL_BAR (window->horizontal_scroll_bar);
      if ([bar checkSamePosition: position portion: portion whole: whole])
        {
          if (view->scrollbarsNeedingUpdate == 0)
            {
              if (!windows_or_buffers_changed)
                  return;
            }
          else
            view->scrollbarsNeedingUpdate--;
          update_p = NO;
        }
    }

  NSTRACE ("ns_set_horizontal_scroll_bar");

  /* Get dimensions.  */
  window_box (window, ANY_AREA, &window_x, 0, &window_width, 0);
  left = window_x;
  width = window_width;
  height = NS_SCROLL_BAR_HEIGHT (f);
  top = WINDOW_SCROLL_BAR_AREA_Y (window);

  r = NSMakeRect (left, top, width, height);
  /* The parent view is flipped, so we need to flip y value.  */
  v = [view frame];
  r.origin.y = (v.size.height - r.size.height - r.origin.y);

  XSETWINDOW (win, window);
  block_input ();

  if (NILP (window->horizontal_scroll_bar))
    {
      if (width > 0 && height > 0)
	ns_clear_frame_area (f, left, top, width, height);

      bar = [[EmacsScroller alloc] initFrame: r window: win];
      wset_horizontal_scroll_bar (window, make_mint_ptr (bar));
      update_p = YES;
    }
  else
    {
      NSRect oldRect;
      bar = XNS_SCROLL_BAR (window->horizontal_scroll_bar);
      oldRect = [bar frame];
      if (FRAME_LIVE_P (f) && !NSEqualRects (oldRect, r))
        {
          ns_clear_frame_area (f, left, top, width, height);
          [bar setFrame: r];
          update_p = YES;
        }
    }

  /* If there are both horizontal and vertical scroll-bars they leave
     a square that belongs to neither. We need to clear it otherwise
     it fills with junk.  */
  if (!NILP (window->vertical_scroll_bar))
    ns_clear_frame_area (f, WINDOW_SCROLL_BAR_AREA_X (window), top,
                         NS_SCROLL_BAR_HEIGHT (f), height);

  if (update_p)
    [bar setPosition: position portion: portion whole: whole];
  unblock_input ();
}


static void
ns_condemn_scroll_bars (struct frame *f)
/* --------------------------------------------------------------------------
     External (hook): arrange for all frame's scrollbars to be removed
     at next call to judge_scroll_bars, except for those redeemed.
   -------------------------------------------------------------------------- */
{
  int i;
  id view;
  NSArray *subviews = [[FRAME_NS_VIEW (f) superview] subviews];

  NSTRACE ("ns_condemn_scroll_bars");

  for (i =[subviews count]-1; i >= 0; i--)
    {
      view = [subviews objectAtIndex: i];
      if ([view isKindOfClass: [EmacsScroller class]])
        [view condemn];
    }
}


static void
ns_redeem_scroll_bar (struct window *window)
/* --------------------------------------------------------------------------
     External (hook): arrange to spare this window's scrollbar
     at next call to judge_scroll_bars.
   -------------------------------------------------------------------------- */
{
  id bar;
  NSTRACE ("ns_redeem_scroll_bar");
  if (!NILP (window->vertical_scroll_bar)
      && WINDOW_HAS_VERTICAL_SCROLL_BAR (window))
    {
      bar = XNS_SCROLL_BAR (window->vertical_scroll_bar);
      [bar reprieve];
    }

  if (!NILP (window->horizontal_scroll_bar)
      && WINDOW_HAS_HORIZONTAL_SCROLL_BAR (window))
    {
      bar = XNS_SCROLL_BAR (window->horizontal_scroll_bar);
      [bar reprieve];
    }
}


static void
ns_judge_scroll_bars (struct frame *f)
/* --------------------------------------------------------------------------
     External (hook): destroy all scrollbars on frame that weren't
     redeemed after call to condemn_scroll_bars.
   -------------------------------------------------------------------------- */
{
  int i;
  id view;
  EmacsView *eview = FRAME_NS_VIEW (f);
  NSArray *subviews = [[eview superview] subviews];

  NSTRACE ("ns_judge_scroll_bars");
  for (i = [subviews count]-1; i >= 0; --i)
    {
      view = [subviews objectAtIndex: i];
      if (![view isKindOfClass: [EmacsScroller class]]) continue;
      [view judge];
    }
}

/* ==========================================================================

    Image Hooks

   ========================================================================== */

static void
ns_free_pixmap (struct frame *_f, Emacs_Pixmap pixmap)
{
  ns_release_object (pixmap);
}

/* ==========================================================================

    Initialization

   ========================================================================== */

int
ns_display_pixel_height (struct ns_display_info *dpyinfo)
{
  NSArray *screens = [NSScreen screens];
  NSEnumerator *enumerator = [screens objectEnumerator];
  NSScreen *screen;
  NSRect frame;

  frame = NSZeroRect;
  while ((screen = [enumerator nextObject]) != nil)
    frame = NSUnionRect (frame, [screen frame]);

  return NSHeight (frame);
}

int
ns_display_pixel_width (struct ns_display_info *dpyinfo)
{
  NSArray *screens = [NSScreen screens];
  NSEnumerator *enumerator = [screens objectEnumerator];
  NSScreen *screen;
  NSRect frame;

  frame = NSZeroRect;
  while ((screen = [enumerator nextObject]) != nil)
    frame = NSUnionRect (frame, [screen frame]);

  return NSWidth (frame);
}


static Lisp_Object ns_string_to_lispmod (const char *s)
/* --------------------------------------------------------------------------
     Convert modifier name to lisp symbol.
   -------------------------------------------------------------------------- */
{
  if (!strncmp (SSDATA (SYMBOL_NAME (Qmeta)), s, 10))
    return Qmeta;
  else if (!strncmp (SSDATA (SYMBOL_NAME (Qsuper)), s, 10))
    return Qsuper;
  else if (!strncmp (SSDATA (SYMBOL_NAME (Qcontrol)), s, 10))
    return Qcontrol;
  else if (!strncmp (SSDATA (SYMBOL_NAME (Qalt)), s, 10))
    return Qalt;
  else if (!strncmp (SSDATA (SYMBOL_NAME (Qhyper)), s, 10))
    return Qhyper;
  else if (!strncmp (SSDATA (SYMBOL_NAME (Qnone)), s, 10))
    return Qnone;
  else
    return Qnil;
}


static void
ns_default (const char *parameter, Lisp_Object *result,
           Lisp_Object yesval, Lisp_Object noval,
           BOOL is_float, BOOL is_modstring)
/* --------------------------------------------------------------------------
      Check a parameter value in user's preferences.
   -------------------------------------------------------------------------- */
{
  const char *value = ns_get_defaults_value (parameter);

  if (value)
    {
      double f;
      char *pos;
      if (c_strcasecmp (value, "YES") == 0)
        *result = yesval;
      else if (c_strcasecmp (value, "NO") == 0)
        *result = noval;
      else if (is_float && (f = strtod (value, &pos), pos != value))
        *result = make_float (f);
      else if (is_modstring && value)
        *result = ns_string_to_lispmod (value);
      else fprintf (stderr,
                   "Bad value for default \"%s\": \"%s\"\n", parameter, value);
    }
}


static void
ns_initialize_display_info (struct ns_display_info *dpyinfo)
/* --------------------------------------------------------------------------
      Initialize global info and storage for display.
   -------------------------------------------------------------------------- */
{
    NSScreen *screen = [NSScreen mainScreen];
    NSWindowDepth depth = [screen depth];

    dpyinfo->resx = 72.27; /* used 75.0, but this makes pt == pixel, expected */
    dpyinfo->resy = 72.27;
    dpyinfo->color_p = ![NSDeviceWhiteColorSpace isEqualToString:
                                                  NSColorSpaceFromDepth (depth)]
                && ![NSCalibratedWhiteColorSpace isEqualToString:
                                                 NSColorSpaceFromDepth (depth)];
    dpyinfo->n_planes = NSBitsPerPixelFromDepth (depth);
    dpyinfo->root_window = 42; /* A placeholder.  */
    dpyinfo->highlight_frame = dpyinfo->ns_focus_frame = NULL;
    dpyinfo->n_fonts = 0;
    dpyinfo->smallest_font_height = 1;
    dpyinfo->smallest_char_width = 1;

    reset_mouse_highlight (&dpyinfo->mouse_highlight);
}

/* This currently does nothing, since it's only really needed when
   changing the font-backend, but macOS currently only has one
   possible backend.  This may change if we add HarfBuzz support.  */
static void
ns_default_font_parameter (struct frame *f, Lisp_Object parms)
{
}

#ifdef NS_IMPL_GNUSTEP
static void
ns_update_window_end (struct window *w, bool cursor_on_p,
		      bool mouse_face_overwritten_p)
{
  NSTRACE ("ns_update_window_end (cursor_on_p = %d)", cursor_on_p);

  ns_redraw_scroll_bars (WINDOW_XFRAME (w));
}
#endif

static void
ns_flush_display (struct frame *f)
{
  struct input_event ie;

  EVENT_INIT (ie);
  ns_read_socket_1 (FRAME_TERMINAL (f), &ie, YES);
}

/* This and next define (many of the) public functions in this
   file.  */
/* gui_* are generic versions in xdisp.c that we, and other terms, get
   away with using despite presence in the "system dependent"
   redisplay interface.  In addition, many of the ns_ methods have
   code that is shared with all terms, indicating need for further
   refactoring.  */
extern frame_parm_handler ns_frame_parm_handlers[];
static struct redisplay_interface ns_redisplay_interface =
{
  ns_frame_parm_handlers,
  gui_produce_glyphs,
  gui_write_glyphs,
  gui_insert_glyphs,
  gui_clear_end_of_line,
  ns_scroll_run,
  ns_after_update_window_line,
  NULL, /* update_window_begin */
#ifndef NS_IMPL_GNUSTEP
  NULL, /* update_window_end   */
#else
  ns_update_window_end,
#endif
  ns_flush_display,
  gui_clear_window_mouse_face,
  gui_get_glyph_overhangs,
  gui_fix_overlapping_area,
  ns_draw_fringe_bitmap,
  ns_define_fringe_bitmap,
  ns_destroy_fringe_bitmap,
  ns_compute_glyph_string_overhangs,
  ns_draw_glyph_string,
  ns_define_frame_cursor,
  ns_clear_frame_area,
  ns_clear_under_internal_border, /* clear_under_internal_border */
  ns_draw_window_cursor,
  ns_draw_vertical_window_border,
  ns_draw_window_divider,
  ns_shift_glyphs_for_insert,
  ns_show_hourglass,
  ns_hide_hourglass,
  ns_default_font_parameter
};

#ifdef NS_IMPL_COCOA
static void
ns_displays_reconfigured (CGDirectDisplayID display,
			  CGDisplayChangeSummaryFlags flags,
			  void *user_info)
{
  struct input_event ie;
  union buffered_input_event *ev;
  Lisp_Object new_monitors;

  EVENT_INIT (ie);

  new_monitors = Fns_display_monitor_attributes_list (Qnil);

  if (!NILP (Fequal (new_monitors, last_known_monitors)))
    return;

  last_known_monitors = new_monitors;

  ev = (kbd_store_ptr == kbd_buffer
	? kbd_buffer + KBD_BUFFER_SIZE - 1
	: kbd_store_ptr - 1);

  if (kbd_store_ptr != kbd_fetch_ptr
      && ev->ie.kind == MONITORS_CHANGED_EVENT)
    return;

  ie.kind = MONITORS_CHANGED_EVENT;
  XSETTERMINAL (ie.arg, x_display_list->terminal);

  kbd_buffer_store_event (&ie);
}
#endif

static void
ns_delete_display (struct ns_display_info *dpyinfo)
{
  /* TODO...  */
}


/* This function is called when the last frame on a display is deleted.  */
static void
ns_delete_terminal (struct terminal *terminal)
{
  struct ns_display_info *dpyinfo = terminal->display_info.ns;

  NSTRACE ("ns_delete_terminal");

  /* Protect against recursive calls.  delete_frame in
     delete_terminal calls us back when it deletes our last frame.  */
  if (!terminal->name)
    return;

  block_input ();

#ifdef NS_IMPL_COCOA
  /* Rather than try to clean up the NS environment we can just
     disable the app and leave it waiting for any new frames.  */
  [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
#endif

  image_destroy_all_bitmaps (dpyinfo);
  ns_delete_display (dpyinfo);
  unblock_input ();
}

static Lisp_Object ns_new_font (struct frame *f, Lisp_Object font_object,
                                int fontset);

static struct terminal *
ns_create_terminal (struct ns_display_info *dpyinfo)
/* --------------------------------------------------------------------------
      Set up use of NS before we make the first connection.
   -------------------------------------------------------------------------- */
{
  struct terminal *terminal;

  NSTRACE ("ns_create_terminal");

  terminal = create_terminal (output_ns, &ns_redisplay_interface);

  terminal->display_info.ns = dpyinfo;
  dpyinfo->terminal = terminal;

  terminal->clear_frame_hook = ns_clear_frame;
  terminal->ring_bell_hook = ns_ring_bell;
  terminal->update_begin_hook = ns_update_begin;
  terminal->update_end_hook = ns_update_end;
  terminal->read_socket_hook = ns_read_socket;
  terminal->frame_up_to_date_hook = ns_frame_up_to_date;
  terminal->defined_color_hook = ns_defined_color;
  terminal->query_frame_background_color = ns_query_frame_background_color;
  terminal->mouse_position_hook = ns_mouse_position;
  terminal->get_focus_frame = ns_get_focus_frame;
  terminal->focus_frame_hook = ns_focus_frame;
  terminal->frame_rehighlight_hook = ns_frame_rehighlight;
  terminal->frame_raise_lower_hook = ns_frame_raise_lower;
  terminal->frame_visible_invisible_hook = ns_make_frame_visible_invisible;
  terminal->fullscreen_hook = ns_fullscreen_hook;
  terminal->iconify_frame_hook = ns_iconify_frame;
  terminal->set_window_size_hook = ns_set_window_size;
  terminal->set_frame_offset_hook = ns_set_offset;
  terminal->set_frame_alpha_hook = ns_set_frame_alpha;
  terminal->set_new_font_hook = ns_new_font;
  terminal->implicit_set_name_hook = ns_implicitly_set_name;
  terminal->menu_show_hook = ns_menu_show;
  terminal->popup_dialog_hook = ns_popup_dialog;
  terminal->set_vertical_scroll_bar_hook = ns_set_vertical_scroll_bar;
  terminal->set_horizontal_scroll_bar_hook = ns_set_horizontal_scroll_bar;
  terminal->set_scroll_bar_default_width_hook = ns_set_scroll_bar_default_width;
  terminal->set_scroll_bar_default_height_hook = ns_set_scroll_bar_default_height;
  terminal->condemn_scroll_bars_hook = ns_condemn_scroll_bars;
  terminal->redeem_scroll_bar_hook = ns_redeem_scroll_bar;
  terminal->judge_scroll_bars_hook = ns_judge_scroll_bars;
  terminal->get_string_resource_hook = ns_get_string_resource;
  terminal->free_pixmap = ns_free_pixmap;
  terminal->delete_frame_hook = ns_destroy_window;
  terminal->delete_terminal_hook = ns_delete_terminal;
  terminal->change_tab_bar_height_hook = ns_change_tab_bar_height;
  /* Other hooks are NULL by default.  */

  return terminal;
}


struct ns_display_info *
ns_term_init (Lisp_Object display_name)
/* --------------------------------------------------------------------------
     Start the Application and get things rolling.
   -------------------------------------------------------------------------- */
{
  struct terminal *terminal;
  struct ns_display_info *dpyinfo;
  static int ns_initialized = 0;
  Lisp_Object tmp;

  if (ns_initialized) return x_display_list;
  ns_initialized = 1;

  block_input ();

  NSTRACE ("ns_term_init");

  [outerpool release];
  outerpool = [[NSAutoreleasePool alloc] init];

  /* count object allocs (About, click icon); on macOS use ObjectAlloc tool */
  /*GSDebugAllocationActive (YES); */
  block_input ();

  baud_rate = 38400;
  Fset_input_interrupt_mode (Qnil);

  if (selfds[0] == -1)
    {
      if (emacs_pipe (selfds) != 0)
        {
          fprintf (stderr, "Failed to create pipe: %s\n",
                   emacs_strerror (errno));
          emacs_abort ();
        }

      fcntl (selfds[0], F_SETFL, O_NONBLOCK|fcntl (selfds[0], F_GETFL));
      FD_ZERO (&select_readfds);
      FD_ZERO (&select_writefds);
      pthread_mutex_init (&select_mutex, NULL);
    }

  ns_pending_files = [[NSMutableArray alloc] init];
  ns_pending_service_names = [[NSMutableArray alloc] init];
  ns_pending_service_args = [[NSMutableArray alloc] init];

  /* Start app and create the main menu, window, view.
     Needs to be here because ns_initialize_display_info () uses AppKit classes.
     The view will then ask the NSApp to stop and return to Emacs.  */
  [EmacsApp sharedApplication];
  if (NSApp == nil)
    return NULL;
  [NSApp setDelegate: NSApp];

  /* Start the select thread.  */
  [NSThread detachNewThreadSelector:@selector (fd_handler:)
                           toTarget:NSApp
                         withObject:nil];

  /* debugging: log all notifications */
  /*   [[NSNotificationCenter defaultCenter] addObserver: NSApp
                                         selector: @selector (logNotification:)
                                             name: nil object: nil]; */

  dpyinfo = xzalloc (sizeof *dpyinfo);

  ns_initialize_display_info (dpyinfo);
  terminal = ns_create_terminal (dpyinfo);

  terminal->kboard = allocate_kboard (Qns);
  /* Don't let the initial kboard remain current longer than necessary.
     That would cause problems if a file loaded on startup tries to
     prompt in the mini-buffer.  */
  if (current_kboard == initial_kboard)
    current_kboard = terminal->kboard;
  terminal->kboard->reference_count++;

  dpyinfo->next = x_display_list;
  x_display_list = dpyinfo;

  dpyinfo->name_list_element = Fcons (display_name, Qnil);

  terminal->name = xlispstrdup (display_name);

  gui_init_fringe (terminal->rif);

  unblock_input ();

  if (!inhibit_x_resources)
    {
      ns_default ("GSFontAntiAlias", &ns_antialias_text,
                 Qt, Qnil, NO, NO);
      tmp = Qnil;
      /* this is a standard variable */
      ns_default ("AppleAntiAliasingThreshold", &tmp,
                 make_float (10.0), make_float (6.0), YES, NO);
      ns_antialias_threshold = NILP (tmp) ? 10.0 : extract_float (tmp);
    }

  NSTRACE_MSG ("Colors");

  {
    NSColorList *cl = [NSColorList colorListNamed: @"Emacs"];

    /* There are 752 colors defined in rgb.txt.  */
    if ( cl == nil || [[cl allKeys] count] < 752)
      {
        Lisp_Object color_file, color_map, color, name;
        unsigned long c;

        color_file = Fexpand_file_name (build_string ("rgb.txt"),
                         Fsymbol_value (intern ("data-directory")));

        color_map = Fx_load_color_file (color_file);
        if (NILP (color_map))
          fatal ("Could not read %s.\n", SDATA (color_file));

        cl = [[NSColorList alloc] initWithName: @"Emacs"];
        for ( ; CONSP (color_map); color_map = XCDR (color_map))
          {
            color = XCAR (color_map);
            name = XCAR (color);
            c = XFIXNUM (XCDR (color));
            c |= 0xFF000000;
            [cl setColor:
                  [NSColor colorWithUnsignedLong:c]
                  forKey: [NSString stringWithLispString: name]];
          }

        /* FIXME: Report any errors writing the color file below.  */
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100
        if ([cl respondsToSelector:@selector(writeToURL:error:)])
#endif
          [cl writeToURL:nil error:nil];
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100
        else
#endif
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 */
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100 \
  || defined (NS_IMPL_GNUSTEP)
          [cl writeToFile: nil];
#endif
      }
  }

  NSTRACE_MSG ("Versions");

  delete_keyboard_wait_descriptor (0);

  ns_app_name = [[NSProcessInfo processInfo] processName];

  /* Set up macOS app menu */

  NSTRACE_MSG ("Menu init");

#ifdef NS_IMPL_COCOA
  {
    NSMenu *appMenu;
    NSMenuItem *item;
    /* set up the application menu */
    svcsMenu = [[EmacsMenu alloc] initWithTitle: @"Services"];
    [svcsMenu setAutoenablesItems: NO];
    appMenu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
    [appMenu setAutoenablesItems: NO];
    mainMenu = [[EmacsMenu alloc] initWithTitle: @""];
    dockMenu = [[EmacsMenu alloc] initWithTitle: @""];

    [appMenu insertItemWithTitle: @"About Emacs"
                          action: @selector (orderFrontStandardAboutPanel:)
                   keyEquivalent: @""
                         atIndex: 0];
    [appMenu insertItem: [NSMenuItem separatorItem] atIndex: 1];
    [appMenu insertItemWithTitle: @"Preferences..."
                          action: @selector (showPreferencesWindow:)
                   keyEquivalent: @","
                         atIndex: 2];
    [appMenu insertItem: [NSMenuItem separatorItem] atIndex: 3];
    item = [appMenu insertItemWithTitle: @"Services"
                                 action: @selector (menuDown:)
                          keyEquivalent: @""
                                atIndex: 4];
    [appMenu setSubmenu: svcsMenu forItem: item];
    [appMenu insertItem: [NSMenuItem separatorItem] atIndex: 5];
    [appMenu insertItemWithTitle: @"Hide Emacs"
                          action: @selector (hide:)
                   keyEquivalent: @"h"
                         atIndex: 6];
    item =  [appMenu insertItemWithTitle: @"Hide Others"
                          action: @selector (hideOtherApplications:)
                   keyEquivalent: @"h"
                         atIndex: 7];
    [item setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
    [appMenu insertItem: [NSMenuItem separatorItem] atIndex: 8];
    [appMenu insertItemWithTitle: @"Quit Emacs"
                          action: @selector (terminate:)
                   keyEquivalent: @"q"
                         atIndex: 9];

    item = [mainMenu insertItemWithTitle: ns_app_name
                                  action: @selector (menuDown:)
                           keyEquivalent: @""
                                 atIndex: 0];
    [mainMenu setSubmenu: appMenu forItem: item];
    [dockMenu insertItemWithTitle: @"New Frame"
			   action: @selector (newFrame:)
		    keyEquivalent: @""
			  atIndex: 0];

    [NSApp setMainMenu: mainMenu];
    [NSApp setAppleMenu: appMenu];
    [NSApp setServicesMenu: svcsMenu];
    /* Needed at least on Cocoa, to get dock menu to show windows */
    [NSApp setWindowsMenu: [[NSMenu alloc] init]];
  }
#endif /* macOS menu setup */

  /* Register our external input/output types, used for determining
     applicable services and also drag/drop eligibility.  */

  NSTRACE_MSG ("Input/output types");

  ns_send_types = [[NSArray arrayWithObjects: NSPasteboardTypeString, nil] retain];
  ns_return_types = [[NSArray arrayWithObjects: NSPasteboardTypeString, nil]
                      retain];
  ns_drag_types = [[NSArray arrayWithObjects:
                            NSPasteboardTypeString,
                            NSPasteboardTypeTabularText,
#if NS_USE_NSPasteboardTypeFileURL != 0
                            NSPasteboardTypeFileURL,
#else
                            NSFilenamesPboardType,
#endif
                            NSPasteboardTypeURL, nil] retain];

  /* If fullscreen is in init/default-frame-alist, focus isn't set
     right for fullscreen windows, so set this.  */
  [NSApp activateIgnoringOtherApps:YES];

  NSTRACE_MSG ("Call NSApp run");

  [NSApp run];
  ns_do_open_file = YES;

#ifdef NS_IMPL_GNUSTEP
  /* GNUstep steals SIGCHLD for use in NSTask, but we don't use NSTask.
     We must re-catch it so subprocess works.  */
  catch_child_signal ();
#endif

#ifdef NS_IMPL_COCOA
  /* Begin listening for display reconfiguration, so we can run the
     appropriate hooks.  FIXME: is this called when the resolution of
     a monitor changes?  */

  CGDisplayRegisterReconfigurationCallback (ns_displays_reconfigured,
					    NULL);
#endif
  last_known_monitors = Fns_display_monitor_attributes_list (Qnil);

  NSTRACE_MSG ("ns_term_init done");

  unblock_input ();

  return dpyinfo;
}


void
ns_term_shutdown (int sig)
{
  NSAutoreleasePool *pool;
  /* We also need an autorelease pool here, since this can be called
     during dumping.  */
  pool = [[NSAutoreleasePool alloc] init];
  [[NSUserDefaults standardUserDefaults] synchronize];
  [pool release];

  /* code not reached in emacs.c after this is called by shut_down_emacs: */
  if (STRINGP (Vauto_save_list_file_name))
    unlink (SSDATA (Vauto_save_list_file_name));

  if (sig == 0 || sig == SIGTERM)
    [NSApp terminate: NSApp];
  else /* Force a stack trace to happen.  */
    emacs_abort ();
}


/* ==========================================================================

    EmacsApp implementation

   ========================================================================== */


@implementation EmacsApp

- (id)init
{
#ifdef NS_IMPL_GNUSTEP
  NSNotificationCenter *notification_center;
#endif

  NSTRACE ("[EmacsApp init]");

  if ((self = [super init]))
    {
#ifdef NS_IMPL_COCOA
      self->isFirst = YES;
#endif
#ifdef NS_IMPL_GNUSTEP
      self->applicationDidFinishLaunchingCalled = NO;
#endif
    }

#ifdef NS_IMPL_GNUSTEP
  notification_center = [NSNotificationCenter defaultCenter];
  [notification_center addObserver: self
			  selector: @selector(updateMonitors:)
			      name: NSApplicationDidChangeScreenParametersNotification
			    object: nil];
#endif

  return self;
}

#ifdef NS_IMPL_COCOA
- (void)run
{
  NSTRACE ("[EmacsApp run]");

#ifndef NSAppKitVersionNumber10_9
#define NSAppKitVersionNumber10_9 1265
#endif

  if ((int) NSAppKitVersionNumber != NSAppKitVersionNumber10_9)
    {
      [super run];
      return;
    }

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  if (isFirst) [self finishLaunching];
  isFirst = NO;

  shouldKeepRunning = YES;
  do
    {
      [pool release];
      pool = [[NSAutoreleasePool alloc] init];

      NSEvent *event =
        [self nextEventMatchingMask:NSEventMaskAny
                          untilDate:[NSDate distantFuture]
                             inMode:NSDefaultRunLoopMode
                            dequeue:YES];

      [self sendEvent:event];
      [self updateWindows];
    } while (shouldKeepRunning);

  [pool release];
}

- (void)stop: (id)sender
{
  NSTRACE ("[EmacsApp stop:]");

    shouldKeepRunning = NO;
    // Stop possible dialog also.  Noop if no dialog present.
    // The file dialog still leaks 7k - 10k on 10.9 though.
    [super stop:sender];
}
#endif /* NS_IMPL_COCOA */

- (void)logNotification: (NSNotification *)notification
{
  NSTRACE ("[EmacsApp logNotification:]");

  const char *name = [[notification name] UTF8String];
  if (!strstr (name, "Update") && !strstr (name, "NSMenu")
      && !strstr (name, "WindowNumber"))
    NSLog (@"notification: '%@'", [notification name]);
}


- (void)sendEvent: (NSEvent *)theEvent
/* --------------------------------------------------------------------------
     Called when NSApp is running for each event received.  Used to stop
     the loop when we choose, since there's no way to just run one iteration.
   -------------------------------------------------------------------------- */
{
  int type = [theEvent type];
  NSWindow *window = [theEvent window];

  NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "[EmacsApp sendEvent:]");
  NSTRACE_MSG ("Type: %d", type);

#ifdef NS_IMPL_GNUSTEP
  // Keyboard events aren't propagated to file dialogs for some reason.
  if ([NSApp modalWindow] != nil &&
      (type == NSEventTypeKeyDown || type == NSEventTypeKeyUp || type == NSEventTypeFlagsChanged))
    {
      [[NSApp modalWindow] sendEvent: theEvent];
      return;
    }
#endif

  if (type == NSEventTypeApplicationDefined)
    {
      switch ([theEvent data2])
        {
#ifdef NS_IMPL_COCOA
        case NSAPP_DATA2_RUNASSCRIPT:
          ns_run_ascript ();
          [self stop: self];
          return;
#endif
        case NSAPP_DATA2_RUNFILEDIALOG:
          ns_run_file_dialog ();
          [self stop: self];
          return;
        }
    }

  if (type == NSEventTypeCursorUpdate && window == nil)
    {
      fputs ("Dropping external cursor update event.\n", stderr);
      return;
    }

  if (type == NSEventTypeApplicationDefined)
    {
      /* Events posted by ns_send_appdefined interrupt the run loop here.
         But, if a modal window is up, an appdefined can still come through,
         (e.g., from a makeKeyWindow event) but stopping self also stops the
         modal loop. Just defer it until later.  */
      if ([NSApp modalWindow] == nil)
        {
          last_appdefined_event_data = [theEvent data1];
          [self stop: self];
        }
      else
        {
          send_appdefined = YES;
        }
    }


#ifdef NS_IMPL_COCOA
  /* If no dialog and none of our frames have focus and it is a move, skip it.
     It is a mouse move in an auxiliary menu, i.e. on the top right on macOS,
     such as Wifi, sound, date or similar.
     This prevents "spooky" highlighting in the frame under the menu.  */
  if (type == NSEventTypeMouseMoved && [NSApp modalWindow] == nil)
    {
      struct ns_display_info *di;
      BOOL has_focus = NO;
      for (di = x_display_list; ! has_focus && di; di = di->next)
        has_focus = di->ns_focus_frame != 0;
      if (! has_focus)
        return;
    }
#endif

  NSTRACE_UNSILENCE();

  [super sendEvent: theEvent];
}


- (void)showPreferencesWindow: (id)sender
{
  struct frame *emacsframe = SELECTED_FRAME ();
  NSEvent *theEvent = [NSApp currentEvent];

  if (!emacs_event)
    return;
  emacs_event->kind = NS_NONKEY_EVENT;
  emacs_event->code = KEY_NS_SHOW_PREFS;
  emacs_event->modifiers = 0;
  EV_TRAILER (theEvent);
}


- (void)newFrame: (id)sender
{
  NSTRACE ("[EmacsApp newFrame:]");

  struct frame *emacsframe = SELECTED_FRAME ();
  NSEvent *theEvent = [NSApp currentEvent];

  if (!emacs_event)
    return;
  emacs_event->kind = NS_NONKEY_EVENT;
  emacs_event->code = KEY_NS_NEW_FRAME;
  emacs_event->modifiers = 0;
  EV_TRAILER (theEvent);
}


/* Open a file (used by below, after going into queue read by ns_read_socket).  */
- (BOOL) openFile: (NSString *)fileName
{
  NSTRACE ("[EmacsApp openFile:]");

  struct frame *emacsframe = SELECTED_FRAME ();
  NSEvent *theEvent = [NSApp currentEvent];

  if (!emacs_event)
    return NO;

  emacs_event->kind = NS_NONKEY_EVENT;
  emacs_event->code = KEY_NS_OPEN_FILE_LINE;
  ns_input_file = append2 (ns_input_file, [fileName lispString]);
  ns_input_line = Qnil; /* can be start or cons start,end */
  emacs_event->modifiers =0;
  EV_TRAILER (theEvent);

  return YES;
}

#ifdef NS_IMPL_GNUSTEP
- (void) updateMonitors: (NSNotification *) notification
{
  struct input_event ie;
  union buffered_input_event *ev;
  Lisp_Object new_monitors;

  EVENT_INIT (ie);

  new_monitors = Fns_display_monitor_attributes_list (Qnil);

  if (!NILP (Fequal (new_monitors, last_known_monitors)))
    return;

  last_known_monitors = new_monitors;

  ev = (kbd_store_ptr == kbd_buffer
	? kbd_buffer + KBD_BUFFER_SIZE - 1
	: kbd_store_ptr - 1);

  if (kbd_store_ptr != kbd_fetch_ptr
      && ev->ie.kind == MONITORS_CHANGED_EVENT)
    return;

  ie.kind = MONITORS_CHANGED_EVENT;
  XSETTERMINAL (ie.arg, x_display_list->terminal);

  kbd_buffer_store_event (&ie);
}
#endif

/* **************************************************************************

      EmacsApp delegate implementation

   ************************************************************************** */

- (void)applicationDidFinishLaunching: (NSNotification *)notification
/* --------------------------------------------------------------------------
     When application is loaded, terminate event loop in ns_term_init.
   -------------------------------------------------------------------------- */
{
  NSTRACE ("[EmacsApp applicationDidFinishLaunching:]");

#ifdef NS_IMPL_GNUSTEP
  ((EmacsApp *)self)->applicationDidFinishLaunchingCalled = YES;
#endif
  [NSApp setServicesProvider: NSApp];

  [self antialiasThresholdDidChange:nil];
#ifdef NS_IMPL_COCOA
  [[NSNotificationCenter defaultCenter]
    addObserver:self
       selector:@selector(antialiasThresholdDidChange:)
	   name:NSAntialiasThresholdChangedNotification
	 object:nil];
#endif

#ifdef NS_IMPL_COCOA
  /* Some functions/methods in CoreFoundation/Foundation increase the
     maximum number of open files for the process in their first call.
     We make dummy calls to them and then reduce the resource limit
     here, since pselect cannot handle file descriptors that are
     greater than or equal to FD_SETSIZE.  */
  CFSocketGetTypeID ();
  CFFileDescriptorGetTypeID ();
  [[NSFileHandle alloc] init];
  struct rlimit rlim;
  if (getrlimit (RLIMIT_NOFILE, &rlim) == 0
      && rlim.rlim_cur > FD_SETSIZE)
    {
      rlim.rlim_cur = FD_SETSIZE;
      setrlimit (RLIMIT_NOFILE, &rlim);
    }
  if ([NSApp activationPolicy] == NSApplicationActivationPolicyProhibited) {
    /* Set the app's activation policy to regular when we run outside
       of a bundle.  This is already done for us by Info.plist when we
       run inside a bundle.  */
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    [NSApp setApplicationIconImage:
	     [EmacsImage
	       allocInitFromFile:
		 build_string("icons/hicolor/128x128/apps/emacs.png")]];
  }
#endif

  ns_send_appdefined (-2);
}

- (void)antialiasThresholdDidChange:(NSNotification *)notification
{
#ifdef NS_IMPL_COCOA
  macfont_update_antialias_threshold ();
#endif
}


/* Termination sequences:
    C-x C-c:
    Cmd-Q:
    MenuBar | File | Exit:
    Select Quit from App menubar:
        -terminate
	KEY_NS_POWER_OFF, (save-buffers-kill-emacs)
	ns_term_shutdown()

    Select Quit from Dock menu:
    Logout attempt:
        -appShouldTerminate
          Cancel -> Nothing else
          Accept ->

	  -terminate
	  KEY_NS_POWER_OFF, (save-buffers-kill-emacs)
	  ns_term_shutdown()

*/

- (BOOL) applicationSupportsSecureRestorableState: (NSApplication *)app
{
  return YES;
}

- (void) terminate: (id)sender
{
  struct input_event ie;
  struct frame *f;

  NSTRACE ("[EmacsApp terminate:]");

  f = SELECTED_FRAME ();
  EVENT_INIT (ie);

  ie.kind = NS_NONKEY_EVENT;
  ie.code = KEY_NS_POWER_OFF;
  ie.arg = Qt; /* mark as non-key event */
  XSETFRAME (ie.frame_or_window, f);

  kbd_buffer_store_event (&ie);
}

static bool
runAlertPanel(NSString *title,
              NSString *msgFormat,
              NSString *defaultButton,
              NSString *alternateButton)
{
#ifdef NS_IMPL_GNUSTEP
  return NSRunAlertPanel(title, msgFormat, defaultButton, alternateButton, nil)
    == NSAlertDefaultReturn;
#else
  NSAlert *alert = [[NSAlert alloc] init];
  [alert setAlertStyle: NSAlertStyleCritical];
  [alert setMessageText: msgFormat];
  [alert addButtonWithTitle: defaultButton];
  [alert addButtonWithTitle: alternateButton];
  NSInteger ret = [alert runModal];
  [alert release];
  return ret == NSAlertFirstButtonReturn;
#endif
}


- (NSApplicationTerminateReply)applicationShouldTerminate: (id)sender
{
  NSTRACE ("[EmacsApp applicationShouldTerminate:]");

  bool ret;

  if (NILP (ns_confirm_quit)) //   || ns_shutdown_properly  --> TO DO
    return NSTerminateNow;

  ret = runAlertPanel(ns_app_name,
		      @"Exit requested.  Would you like to Save Buffers and Exit, or Cancel the request?",
		      @"Save Buffers and Exit", @"Cancel");

  return ret ? NSTerminateNow : NSTerminateCancel;
}

static int
not_in_argv (NSString *arg)
{
  int k;
  const char *a = [arg UTF8String];
  for (k = 1; k < initial_argc; ++k)
    if (strcmp (a, initial_argv[k]) == 0) return 0;
  return 1;
}

/* Notification from the Workspace to open a file.  */
- (BOOL)application: sender openFile: (NSString *)file
{
  if (ns_do_open_file || not_in_argv (file))
    [ns_pending_files addObject: file];
  return YES;
}


/* Open a file as a temporary file.  */
- (BOOL)application: sender openTempFile: (NSString *)file
{
  if (ns_do_open_file || not_in_argv (file))
    [ns_pending_files addObject: file];
  return YES;
}


/* Notification from the Workspace to open a file noninteractively (?).  */
- (BOOL)application: sender openFileWithoutUI: (NSString *)file
{
  if (ns_do_open_file || not_in_argv (file))
    [ns_pending_files addObject: file];
  return YES;
}

/* Notification from the Workspace to open multiple files.  */
- (void)application: sender openFiles: (NSArray *)fileList
{
  NSEnumerator *files = [fileList objectEnumerator];
  NSString *file;
  /* Don't open files from the command line unconditionally,
     Cocoa parses the command line wrong, --option value tries to open value
     if --option is the last option.  */
  while ((file = [files nextObject]) != nil)
    if (ns_do_open_file || not_in_argv (file))
      [ns_pending_files addObject: file];

  [self replyToOpenOrPrint: NSApplicationDelegateReplySuccess];

}


/* Handle dock menu requests.  */
- (NSMenu *)applicationDockMenu: (NSApplication *) sender
{
  return dockMenu;
}


/* TODO: these may help w/IO switching between terminal and NSApp.  */
- (void)applicationWillBecomeActive: (NSNotification *)notification
{
  NSTRACE ("[EmacsApp applicationWillBecomeActive:]");
  // ns_app_active=YES;
}

- (void)applicationDidBecomeActive: (NSNotification *)notification
{
  NSTRACE ("[EmacsApp applicationDidBecomeActive:]");

#ifdef NS_IMPL_GNUSTEP
  if (! applicationDidFinishLaunchingCalled)
    [self applicationDidFinishLaunching:notification];
#endif
  // ns_app_active=YES;

  ns_update_auto_hide_menu_bar ();
  // No constraining takes place when the application is not active.
  ns_constrain_all_frames ();
}
- (void)applicationDidResignActive: (NSNotification *)notification
{
  NSTRACE ("[EmacsApp applicationDidResignActive:]");

  // ns_app_active=NO;
  ns_send_appdefined (-1);
}



/* ==========================================================================

    EmacsApp aux handlers for managing event loop

   ========================================================================== */


- (void)timeout_handler: (NSTimer *)timedEntry
/* --------------------------------------------------------------------------
     The timeout specified to ns_select has passed.
   -------------------------------------------------------------------------- */
{
  /* NSTRACE ("timeout_handler"); */
  ns_send_appdefined (-2);
}

- (void)sendFromMainThread:(id)unused
{
  ns_send_appdefined (nextappdefined);
}

- (void)fd_handler:(id)unused
/* --------------------------------------------------------------------------
     Check data waiting on file descriptors and terminate if so.
   -------------------------------------------------------------------------- */
{
  int result;
  int waiting = 1, nfds;
  char c;

  fd_set readfds, writefds, *wfds;
  struct timespec timeout, *tmo;
  NSAutoreleasePool *pool = nil;

  /* NSTRACE ("fd_handler"); */

  for (;;)
    {
      [pool release];
      pool = [[NSAutoreleasePool alloc] init];

      if (waiting)
        {
          fd_set fds;
          FD_ZERO (&fds);
          FD_SET (selfds[0], &fds);
          result = pselect (selfds[0]+1, &fds, NULL, NULL, NULL, NULL);
          if (result > 0 && read (selfds[0], &c, 1) == 1 && c == 'g')
	    waiting = 0;
        }
      else
        {
          pthread_mutex_lock (&select_mutex);
          nfds = select_nfds;

          if (select_valid & SELECT_HAVE_READ)
            readfds = select_readfds;
          else
            FD_ZERO (&readfds);

          if (select_valid & SELECT_HAVE_WRITE)
            {
              writefds = select_writefds;
              wfds = &writefds;
            }
          else
            wfds = NULL;
          if (select_valid & SELECT_HAVE_TMO)
            {
              timeout = select_timeout;
              tmo = &timeout;
            }
          else
            tmo = NULL;

          pthread_mutex_unlock (&select_mutex);

          FD_SET (selfds[0], &readfds);
          if (selfds[0] >= nfds) nfds = selfds[0]+1;

          result = pselect (nfds, &readfds, wfds, NULL, tmo, NULL);

          if (result == 0)
            ns_send_appdefined (-2);
          else if (result > 0)
            {
              if (FD_ISSET (selfds[0], &readfds))
                {
                  if (read (selfds[0], &c, 1) == 1 && c == 's')
		    waiting = 1;
                }
              else
                {
                  pthread_mutex_lock (&select_mutex);
                  if (select_valid & SELECT_HAVE_READ)
                    select_readfds = readfds;
                  if (select_valid & SELECT_HAVE_WRITE)
                    select_writefds = writefds;
                  if (select_valid & SELECT_HAVE_TMO)
                    select_timeout = timeout;
                  pthread_mutex_unlock (&select_mutex);

                  ns_send_appdefined (result);
                }
            }
          waiting = 1;
        }
    }
}



/* ==========================================================================

    Service provision

   ========================================================================== */

/* Called from system: queue for next pass through event loop.  */
- (void)requestService: (NSPasteboard *)pboard
              userData: (NSString *)userData
                 error: (NSString **)error
{
  [ns_pending_service_names addObject: userData];
  [ns_pending_service_args addObject: [NSString stringWithLispString:ns_string_from_pasteboard (pboard)]];
}


/* Called from ns_read_socket to clear queue.  */
- (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
{
  struct frame *emacsframe = SELECTED_FRAME ();
  NSEvent *theEvent = [NSApp currentEvent];

  NSTRACE ("[EmacsApp fulfillService:withArg:]");

  if (!emacs_event)
    return NO;

  emacs_event->kind = NS_NONKEY_EVENT;
  emacs_event->code = KEY_NS_SPI_SERVICE_CALL;
  ns_input_spi_name = [name lispString];
  ns_input_spi_arg = [arg lispString];
  emacs_event->modifiers = EV_MODIFIERS (theEvent);
  EV_TRAILER (theEvent);

  return YES;
}


@end  /* EmacsApp */

static Lisp_Object
ns_font_desc_to_font_spec (NSFontDescriptor *desc, NSFont *font)
{
  NSFontSymbolicTraits traits = [desc symbolicTraits];
  NSDictionary *dict = [desc objectForKey: NSFontTraitsAttribute];
  NSString *family = [font familyName];
  Lisp_Object lwidth, lslant, lweight, lheight;
  NSNumber *tem;

  lwidth = Qnil;
  lslant = Qnil;
  lweight = Qnil;
  lheight = Qnil;

  if (traits & NSFontBoldTrait)
    lweight = Qbold;

  if (traits & NSFontItalicTrait)
    lslant = Qitalic;

  if (traits & NSFontCondensedTrait)
    lwidth = Qcondensed;
  else if (traits & NSFontExpandedTrait)
    lwidth = Qexpanded;

  if (dict != nil)
    {
      tem = [dict objectForKey: NSFontSlantTrait];

      if (tem != nil)
	lslant = ([tem floatValue] > 0
		  ? Qitalic : ([tem floatValue] < 0
			       ? Qreverse_italic
			       : Qnormal));

      tem = [dict objectForKey: NSFontWeightTrait];

#ifdef NS_IMPL_GNUSTEP
      if (tem != nil)
	lweight = ([tem floatValue] > 0
		   ? Qbold : ([tem floatValue] < -0.4f
			      ? Qlight : Qnormal));
#else
      if (tem != nil)
	{
	  if ([tem floatValue] >= 0.4)
	    lweight = Qbold;
	  else if ([tem floatValue] >= 0.24)
	    lweight = Qmedium;
	  else if ([tem floatValue] >= 0)
	    lweight = Qnormal;
	  else if ([tem floatValue] >= -0.24)
	    lweight = Qsemi_light;
	  else
	    lweight = Qlight;
	}
#endif

      tem = [dict objectForKey: NSFontWidthTrait];

      if (tem != nil)
	lwidth = ([tem floatValue] > 0
		  ? Qexpanded : ([tem floatValue] < 0
				 ? Qcondensed : Qnormal));
    }

  lheight = make_float ([font pointSize]);

  return CALLN (Ffont_spec,
		QCwidth, lwidth, QCslant, lslant,
		QCweight, lweight, QCsize, lheight,
		QCfamily, (family
			   ? [family lispString]
			   : Qnil));
}

#ifdef NS_IMPL_COCOA
static NSView *
ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
{
  NSMatrix *matrix;
  NSButtonCell *prototype;
  NSSize cell_size;
  NSRect frame;
  NSButtonCell *cancel, *ok;

  prototype = [[NSButtonCell alloc] init];
  [prototype setBezelStyle: NSBezelStyleRounded];
  [prototype setTitle: @"Cancel"];
  cell_size = [prototype cellSize];
  frame = NSMakeRect (0, 0, cell_size.width * 2,
		      cell_size.height);
  matrix = [[NSMatrix alloc] initWithFrame: frame
				      mode: NSTrackModeMatrix
				 prototype: prototype
			      numberOfRows: 1
			   numberOfColumns: 2];
  [prototype release];

  ok = (NSButtonCell *) [matrix cellAtRow: 0 column: 0];
  cancel = (NSButtonCell *) [matrix cellAtRow: 0 column: 1];

  [ok setTitle: @"OK"];
  [ok setTarget: target];
  [ok setAction: select];
  [ok setButtonType: NSButtonTypeMomentaryPushIn];

  [cancel setTitle: @"Cancel"];
  [cancel setTarget: target];
  [cancel setAction: cancel_action];
  [cancel setButtonType: NSButtonTypeMomentaryPushIn];

  [matrix selectCell: ok];

  return matrix;
}
#endif

/* ==========================================================================

    EmacsView implementation

   ========================================================================== */


@implementation EmacsView

/* Needed to inform when window closed from lisp.  */
- (void) setWindowClosing: (BOOL)closing
{
  NSTRACE ("[EmacsView setWindowClosing:%d]", closing);

  windowClosing = closing;
}


- (void)dealloc
{
  NSTRACE ("[EmacsView dealloc]");

  /* Clear the view resize notification.  */
  [[NSNotificationCenter defaultCenter]
    removeObserver:self
              name:NSViewFrameDidChangeNotification
            object:nil];

  if (fs_state == FULLSCREEN_BOTH)
    [nonfs_window release];
  [super dealloc];
}


/* Called on font panel selection.  */
- (void) changeFont: (id) sender
{
  struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
  NSFont *nsfont;

#ifdef NS_IMPL_GNUSTEP
  nsfont = ((struct nsfont_info *) font)->nsfont;
#else
  nsfont = (NSFont *) macfont_get_nsctfont (font);
#endif

  if (!font_panel_active)
    return;

  if (font_panel_result)
    [font_panel_result release];

  font_panel_result = (NSFont *) [sender convertFont: nsfont];

  if (font_panel_result)
    [font_panel_result retain];

#ifndef NS_IMPL_COCOA
  font_panel_active = NO;
  [NSApp stop: self];
#endif
}

#ifdef NS_IMPL_COCOA
- (void) noteUserSelectedFont
{
  font_panel_active = NO;

  /* If no font was previously selected, use the currently selected
     font.  */

  if (!font_panel_result && FRAME_FONT (emacsframe))
    {
      font_panel_result
	= macfont_get_nsctfont (FRAME_FONT (emacsframe));

      if (font_panel_result)
	[font_panel_result retain];
    }

  [NSApp stop: self];
}

- (void) noteUserCancelledSelection
{
  font_panel_active = NO;

  if (font_panel_result)
    [font_panel_result release];
  font_panel_result = nil;

  [NSApp stop: self];
}
#endif

- (Lisp_Object) showFontPanel
{
  id fm = [NSFontManager sharedFontManager];
  struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
  NSFont *nsfont, *result;
  struct timespec timeout;
#ifdef NS_IMPL_COCOA
  NSView *buttons;
  BOOL canceled;
#endif

#ifdef NS_IMPL_GNUSTEP
  nsfont = ((struct nsfont_info *) font)->nsfont;
#else
  nsfont = (NSFont *) macfont_get_nsctfont (font);
#endif

#ifdef NS_IMPL_COCOA
  buttons
    = ns_create_font_panel_buttons (self,
				    @selector (noteUserSelectedFont),
				    @selector (noteUserCancelledSelection));
  [[fm fontPanel: YES] setAccessoryView: buttons];
  [buttons release];
#endif

  [fm setSelectedFont: nsfont isMultiple: NO];
  [fm orderFrontFontPanel: NSApp];

  font_panel_active = YES;
  timeout = make_timespec (0, 100000000);

  block_input ();
  while (font_panel_active
#ifdef NS_IMPL_COCOA
	 && (canceled = [[fm fontPanel: YES] isVisible])
#else
	 && [[fm fontPanel: YES] isVisible]
#endif
	 )
    ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES);
  unblock_input ();

  if (font_panel_result)
    [font_panel_result autorelease];

#ifdef NS_IMPL_COCOA
  if (!canceled)
    font_panel_result = nil;
#endif

  result = font_panel_result;
  font_panel_result = nil;

  [[fm fontPanel: YES] setIsVisible: NO];
  font_panel_active = NO;

  if (result)
    return ns_font_desc_to_font_spec ([result fontDescriptor],
				      result);

  return Qnil;
}

- (BOOL)acceptsFirstResponder
{
  NSTRACE ("[EmacsView acceptsFirstResponder]");
  return YES;
}

- (void)resetCursorRects
{
  NSRect visible;
  NSCursor *currentCursor;

  /* On macOS 13, [resetCursorRects:] could be called even after the
     window is closed. */
  if (! emacsframe || ! FRAME_OUTPUT_DATA (emacsframe))
    return;

  visible = [self visibleRect];
  currentCursor = FRAME_POINTER_TYPE (emacsframe);
  NSTRACE ("[EmacsView resetCursorRects]");

  if (currentCursor == nil)
    currentCursor = [NSCursor arrowCursor];

  if (!NSIsEmptyRect (visible))
    [self addCursorRect: visible cursor: currentCursor];

#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
  if ([currentCursor respondsToSelector: @selector(setOnMouseEntered)])
#endif
    [currentCursor setOnMouseEntered: YES];
#endif
}



/*****************************************************************************/
/* Keyboard handling.  */
#define NS_KEYLOG 0

- (void)keyDown: (NSEvent *)theEvent
{
  Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
  int code;
  unsigned fnKeysym = 0;
  static NSMutableArray *nsEvArray;
  unsigned int flags = [theEvent modifierFlags];

  NSTRACE ("[EmacsView keyDown:]");

  /* Rhapsody and macOS give up and down events for the arrow keys.  */
  if ([theEvent type] != NSEventTypeKeyDown)
    return;

  if (!emacs_event)
    return;

 if (![[self window] isKeyWindow]
     && [[theEvent window] isKindOfClass: [EmacsWindow class]]
     /* We must avoid an infinite loop here.  */
     && (EmacsView *)[[theEvent window] delegate] != self)
   {
     /* XXX: There is an occasional condition in which, when Emacs display
         updates a different frame from the current one, and temporarily
         selects it, then processes some interrupt-driven input
         (dispnew.c:3878), OS will send the event to the correct NSWindow, but
         for some reason that window has its first responder set to the NSView
         most recently updated (I guess), which is not the correct one.  */
     [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent];
     return;
   }

  if (nsEvArray == nil)
    nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1];

  [NSCursor setHiddenUntilMouseMoves:! NILP (Vmake_pointer_invisible)];

  if (hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight))
    {
      clear_mouse_face (hlinfo);
      hlinfo->mouse_face_hidden = 1;
    }

  if (!processingCompose)
    {
      /* FIXME: What should happen for key sequences with more than
         one character?  */
      code = ([[theEvent charactersIgnoringModifiers] length] == 0) ?
        0 : [[theEvent charactersIgnoringModifiers] characterAtIndex: 0];

      /* Is it a "function key"?  */
      /* Note: Sometimes a plain key will have the NSEventModifierFlagNumericPad
         flag set (this is probably a bug in the OS).  */
      if (code < 0x00ff && (flags&NSEventModifierFlagNumericPad))
        {
          fnKeysym = ns_convert_key ([theEvent keyCode] | NSEventModifierFlagNumericPad);
        }
      if (fnKeysym == 0)
        {
          fnKeysym = ns_convert_key (code);
        }

      if (fnKeysym)
        {
          /* COUNTERHACK: map 'Delete' on upper-right main KB to 'Backspace',
             because Emacs treats Delete and KP-Delete same (in simple.el).  */
          if ((fnKeysym == 0xFFFF && [theEvent keyCode] == 0x33)
#ifdef NS_IMPL_GNUSTEP
              /*  GNUstep uses incompatible keycodes, even for those that are
                  supposed to be hardware independent.  Just check for delete.
                  Keypad delete does not have keysym 0xFFFF.
                  See https://savannah.gnu.org/bugs/?25395  */
              || (fnKeysym == 0xFFFF && code == 127)
#endif
            )
            code = 0xFF08; /* backspace */
          else
            code = fnKeysym;

          /* Function keys (such as the F-keys, arrow keys, etc.) set
             modifiers as though the fn key has been pressed when it
             hasn't.  Also some combinations of fn and a function key
             return a different key than was pressed (e.g. fn-<left>
             gives <home>).  We need to unset the fn key flag in these
             cases.  */
          flags &= ~NS_FUNCTION_KEY_MASK;
        }

      /* The ⌘ and ⌥ modifiers can be either shift-like (for alternate
         character input) or control-like (as command prefix).  If we
         have only shift-like modifiers, then we should use the
         translated characters (returned by the characters method); if
         we have only control-like modifiers, then we should use the
         untranslated characters (returned by the
         charactersIgnoringModifiers method).  An annoyance happens if
         we have both shift-like and control-like modifiers because
         the NSEvent API doesn’t let us ignore only some modifiers.
         In that case we use UCKeyTranslate (ns_get_shifted_character)
         to look up the correct character.  */

      /* EV_MODIFIERS2 uses parse_solitary_modifier on all known
         modifier keys, which returns 0 for shift-like modifiers.
         Therefore its return value is the set of control-like
         modifiers.  */
      Lisp_Object kind = fnKeysym ? QCfunction : QCordinary;
      emacs_event->modifiers = EV_MODIFIERS2 (flags, kind);

#ifndef NS_IMPL_GNUSTEP
      if (NS_KEYLOG)
        fprintf (stderr, "keyDown: code =%x\tfnKey =%x\tflags = %x\tmods = %x\n",
                 code, fnKeysym, flags, emacs_event->modifiers);
#endif

      /* If it was a function key or had control-like modifiers, pass
         it directly to Emacs.  */
      if (fnKeysym || (emacs_event->modifiers
                       && (emacs_event->modifiers != shift_modifier)
                       && [[theEvent charactersIgnoringModifiers] length] > 0))
        {
          emacs_event->kind = NON_ASCII_KEYSTROKE_EVENT;
          /* FIXME: What are the next four lines supposed to do?  */
          if (code < 0x20)
            code |= (1<<28)|(3<<16);
          else if (code == 0x7f)
            code |= (1<<28)|(3<<16);
          else if (!fnKeysym)
            {
#ifdef NS_IMPL_COCOA
              /* We potentially have both shift- and control-like
                 modifiers in use, so find the correct character
                 ignoring any control-like ones.  */
              code = ns_get_shifted_character (theEvent);
#endif

              /* FIXME: This seems wrong, characters in the range
                 [0x80, 0xFF] are not ASCII characters.  Can’t we just
                 use MULTIBYTE_CHAR_KEYSTROKE_EVENT here for all kinds
                 of characters?  */
              emacs_event->kind = code > 0xFF
                ? MULTIBYTE_CHAR_KEYSTROKE_EVENT : ASCII_KEYSTROKE_EVENT;
            }

          emacs_event->code = code;
          EV_TRAILER (theEvent);
          processingCompose = NO;
          return;
        }
    }

  /* If we get here, a non-function key without control-like modifiers
     was hit.  Use interpretKeyEvents, which in turn will call
     insertText; see
     https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html.  */

  if (NS_KEYLOG && !processingCompose)
    fputs ("keyDown: Begin compose sequence.\n", stderr);

  /* FIXME: interpretKeyEvents doesn’t seem to send insertText if ⌘ is
     used as shift-like modifier, at least on El Capitan.  Mask it
     out.  This shouldn’t be needed though; we should figure out what
     the correct way of handling ⌘ is.  */
  if ([theEvent modifierFlags] & NSEventModifierFlagCommand)
    theEvent = [NSEvent keyEventWithType:[theEvent type]
                                location:[theEvent locationInWindow]
                           modifierFlags:[theEvent modifierFlags] & ~NSEventModifierFlagCommand
                               timestamp:[theEvent timestamp]
                            windowNumber:[theEvent windowNumber]
                                 context:nil
                              characters:[theEvent characters]
                        charactersIgnoringModifiers:[theEvent charactersIgnoringModifiers]
                               isARepeat:[theEvent isARepeat]
                                 keyCode:[theEvent keyCode]];

  processingCompose = YES;
  /* FIXME: Use [NSArray arrayWithObject:theEvent]?  */
  [nsEvArray addObject: theEvent];
  [self interpretKeyEvents: nsEvArray];
  [nsEvArray removeObject: theEvent];
}


/* <NSTextInput> implementation (called through [super interpretKeyEvents:]).  */


/* <NSTextInput>: called when done composing;
   NOTE: also called when we delete over working text, followed
   immediately by doCommandBySelector: deleteBackward:  */
- (void)insertText: (id)aString
{
  NSString *s;
  NSUInteger len;

  NSTRACE ("[EmacsView insertText:]");

  if ([aString isKindOfClass:[NSAttributedString class]])
    s = [aString string];
  else
    s = aString;

  len = [s length];

  if (NS_KEYLOG)
    NSLog (@"insertText '%@'\tlen = %lu", aString, (unsigned long) len);
  processingCompose = NO;

  if (!emacs_event)
    return;

  /* First, clear any working text.  */
  if (workingText != nil)
    [self deleteWorkingText];

  /* It might be preferable to use getCharacters:range: below,
     cf. https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CocoaPerformance/Articles/StringDrawing.html#//apple_ref/doc/uid/TP40001445-112378.
     However, we probably can't use SAFE_NALLOCA here because it might
     exit nonlocally.  */

  /* Now insert the string as keystrokes.  */
  for (NSUInteger i = 0; i < len; i++)
    {
      NSUInteger code = [s characterAtIndex:i];
      if (UTF_16_HIGH_SURROGATE_P (code) && i < len - 1)
        {
          unichar low = [s characterAtIndex:i + 1];
          if (UTF_16_LOW_SURROGATE_P (low))
            {
              code = surrogates_to_codepoint (low, code);
              ++i;
            }
        }
      /* TODO: still need this?  */
      if (code == 0x2DC)
        code = '~'; /* 0x7E */
      if (code != 32) /* Space */
        emacs_event->modifiers = 0;
      emacs_event->kind
	= code > 0xFF ? MULTIBYTE_CHAR_KEYSTROKE_EVENT : ASCII_KEYSTROKE_EVENT;
      emacs_event->code = code;
      EV_TRAILER ((id)nil);
    }
}


/* <NSTextInput>: inserts display of composing characters.  */
- (void)setMarkedText: (id)aString selectedRange: (NSRange)selRange
{
  NSString *str = [aString respondsToSelector: @selector (string)] ?
    [aString string] : aString;

  NSTRACE ("[EmacsView setMarkedText:selectedRange:]");

  if (NS_KEYLOG)
    NSLog (@"setMarkedText '%@' len =%lu range %lu from %lu",
           str, (unsigned long)[str length],
           (unsigned long)selRange.length,
           (unsigned long)selRange.location);

  if ([str length] == 0)
    {
      [self deleteWorkingText];
      return;
    }

  if (!emacs_event)
    return;

  processingCompose = YES;
  [workingText release];
  workingText = [str copy];
  ns_working_text = [workingText lispString];

  emacs_event->kind = NS_TEXT_EVENT;
  emacs_event->code = KEY_NS_PUT_WORKING_TEXT;
  EV_TRAILER ((id)nil);
}


/* Delete display of composing characters [not in <NSTextInput>].  */
- (void)deleteWorkingText
{
  NSTRACE ("[EmacsView deleteWorkingText]");

  if (workingText == nil)
    return;
  if (NS_KEYLOG)
    NSLog(@"deleteWorkingText len =%lu\n", (unsigned long)[workingText length]);
  [workingText release];
  workingText = nil;
  processingCompose = NO;

  if (!emacs_event)
    return;

  emacs_event->kind = NS_TEXT_EVENT;
  emacs_event->code = KEY_NS_UNPUT_WORKING_TEXT;
  EV_TRAILER ((id)nil);
}


- (BOOL)hasMarkedText
{
  NSTRACE ("[EmacsView hasMarkedText]");

  return workingText != nil;
}


- (NSRange)markedRange
{
  NSTRACE ("[EmacsView markedRange]");

  NSRange rng = workingText != nil
    ? NSMakeRange (0, [workingText length]) : NSMakeRange (NSNotFound, 0);
  if (NS_KEYLOG)
    NSLog (@"markedRange request");
  return rng;
}


- (void)unmarkText
{
  NSTRACE ("[EmacsView unmarkText]");

  if (NS_KEYLOG)
    NSLog (@"unmark (accept) text");
  [self deleteWorkingText];
  processingCompose = NO;
}

static Lisp_Object
ns_in_echo_area_1 (void *ptr)
{
  Lisp_Object in_echo_area;
  specpdl_ref count;

  count = SPECPDL_INDEX ();
  specbind (Qinhibit_quit, Qt);
  in_echo_area = safe_call (1, Qns_in_echo_area);

  return unbind_to (count, in_echo_area);
}

static Lisp_Object
ns_in_echo_area_2 (enum nonlocal_exit exit, Lisp_Object error)
{
  return Qnil;
}

static bool
ns_in_echo_area (void)
{
  Lisp_Object in_echo_area;

  in_echo_area
    = internal_catch_all (ns_in_echo_area_1, NULL,
			  ns_in_echo_area_2);

  return !NILP (in_echo_area);
}

/* Used to position char selection windows, etc.  */
- (NSRect)firstRectForCharacterRange: (NSRange)theRange
{
  NSRect rect;
  NSPoint pt;
  struct window *win;

  NSTRACE ("[EmacsView firstRectForCharacterRange:]");

  if (NS_KEYLOG)
    NSLog (@"firstRectForCharRange request");

  if (WINDOWP (echo_area_window) && ns_in_echo_area ())
    win = XWINDOW (echo_area_window);
  else
    win = XWINDOW (FRAME_SELECTED_WINDOW (emacsframe));

  rect.size.width = theRange.length * FRAME_COLUMN_WIDTH (emacsframe);
  rect.size.height = FRAME_LINE_HEIGHT (emacsframe);
  pt.x = WINDOW_TEXT_TO_FRAME_PIXEL_X (win, win->phys_cursor.x);
  pt.y = WINDOW_TO_FRAME_PIXEL_Y (win, win->phys_cursor.y
                                       +FRAME_LINE_HEIGHT (emacsframe));

  pt = [self convertPoint: pt toView: nil];

#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
  if ([[self window] respondsToSelector: @selector(convertRectToScreen:)])
    {
#endif
      rect.origin = pt;
      rect = [(EmacsWindow *) [self window] convertRectToScreen: rect];
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
    }
  else
#endif
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 \
  || defined (NS_IMPL_GNUSTEP)
    {
      pt = [[self window] convertBaseToScreen: pt];
      rect.origin = pt;
    }
#endif

  return rect;
}


- (NSInteger)conversationIdentifier
{
  return (NSInteger)self;
}


- (void)doCommandBySelector: (SEL)aSelector
{
  NSTRACE ("[EmacsView doCommandBySelector:]");

  if (NS_KEYLOG)
    NSLog (@"doCommandBySelector: %@", NSStringFromSelector (aSelector));

  processingCompose = NO;
  if (aSelector == @selector (deleteBackward:))
    {
      /* Happens when user backspaces over an ongoing composition:
         throw a 'delete' into the event queue.  */
      if (!emacs_event)
        return;
      emacs_event->kind = NON_ASCII_KEYSTROKE_EVENT;
      emacs_event->code = 0xFF08;
      EV_TRAILER ((id)nil);
    }
}

- (NSArray *)validAttributesForMarkedText
{
  static NSArray *arr = nil;
  if (arr == nil) arr = [NSArray new];
 /* [[NSArray arrayWithObject: NSUnderlineStyleAttributeName] retain]; */
  return arr;
}

- (NSRange)selectedRange
{
  if (NS_KEYLOG)
    NSLog (@"selectedRange request");
  return NSMakeRange (NSNotFound, 0);
}

#if defined (NS_IMPL_COCOA) || GNUSTEP_GUI_MAJOR_VERSION > 0 || \
    GNUSTEP_GUI_MINOR_VERSION > 22
- (NSUInteger)characterIndexForPoint: (NSPoint)thePoint
#else
- (unsigned int)characterIndexForPoint: (NSPoint)thePoint
#endif
{
  if (NS_KEYLOG)
    NSLog (@"characterIndexForPoint request");
  return 0;
}

- (NSAttributedString *)attributedSubstringFromRange: (NSRange)theRange
{
  static NSAttributedString *str = nil;
  if (str == nil) str = [NSAttributedString new];
  if (NS_KEYLOG)
    NSLog (@"attributedSubstringFromRange request");
  return str;
}

/* End <NSTextInput> implementation.  */
/*****************************************************************************/


/* This is what happens when the user presses a mouse button.  */
- (void)mouseDown: (NSEvent *)theEvent
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
  NSPoint p = [self convertPoint: [theEvent locationInWindow] fromView: nil];
  EmacsWindow *window;

  NSTRACE ("[EmacsView mouseDown:]");

  if (!emacs_event)
    return;

  if (FRAME_TOOLTIP_P (emacsframe))
    return;

  dpyinfo->last_mouse_frame = emacsframe;
  /* Appears to be needed to prevent spurious movement events generated on
     button clicks.  */
  emacsframe->mouse_moved = 0;

  window = (EmacsWindow *) [self window];
  [window setLastDragEvent: theEvent];

  if ([theEvent type] == NSEventTypeScrollWheel)
    {
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
      if ([theEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)])
        {
#endif
          /* If the input device is a touchpad or similar, use precise
           * scrolling deltas.  These are measured in pixels, so we
           * have to add them up until they exceed one line height,
           * then we can send a scroll wheel event.
           *
           * If the device only has coarse scrolling deltas, like a
           * real mousewheel, the deltas represent a ratio of whole
           * lines, so round up the number of lines.  This means we
           * always send one scroll event per click, but can still
           * scroll more than one line if the OS tells us to.
           */
          bool horizontal;
          int lines = 0;
          int x = 0, y = 0;
          int scrollUp = NO;

	  static bool end_flag = false;

	  if (!ns_use_mwheel_momentum && !end_flag
	      && [theEvent momentumPhase] != NSEventPhaseNone)
	    {
	      emacs_event->kind = TOUCH_END_EVENT;
	      emacs_event->arg = Qnil;
	      end_flag = [theEvent momentumPhase] != NSEventPhaseNone;
	      XSETINT (emacs_event->x, lrint (p.x));
	      XSETINT (emacs_event->y, lrint (p.y));
	      EV_TRAILER (theEvent);
	      return;
	    }

	  end_flag = [theEvent momentumPhase] != NSEventPhaseNone;

          /* FIXME: At the top or bottom of the buffer we should
           * ignore momentum-phase events.  */
          if (! ns_use_mwheel_momentum
              && [theEvent momentumPhase] != NSEventPhaseNone)
            return;

          if ([theEvent hasPreciseScrollingDeltas])
            {
              static int totalDeltaX, totalDeltaY;
              int lineHeight;

              if (FIXNUMP (ns_mwheel_line_height))
                lineHeight = XFIXNUM (ns_mwheel_line_height);
              else
                {
                  /* FIXME: Use actual line height instead of the default.  */
                  lineHeight = default_line_pixel_height
                    (XWINDOW (FRAME_SELECTED_WINDOW (emacsframe)));
                }

              if ([theEvent phase] == NSEventPhaseBegan)
                {
                  totalDeltaX = 0;
                  totalDeltaY = 0;
                }

              totalDeltaX += [theEvent scrollingDeltaX];
              totalDeltaY += [theEvent scrollingDeltaY];

              /* Calculate the number of lines, if any, to scroll, and
               * reset the total delta for the direction we're NOT
               * scrolling so that small movements don't add up.  */
              if (abs (totalDeltaX) > abs (totalDeltaY)
                  && (!mwheel_coalesce_scroll_events
		      || abs (totalDeltaX) > lineHeight))
                {
                  horizontal = YES;
                  scrollUp = totalDeltaX > 0;

                  lines = abs (totalDeltaX / lineHeight);
		  x = totalDeltaX;
		  if (!mwheel_coalesce_scroll_events)
		    totalDeltaX = 0;
		  else
		    totalDeltaX = totalDeltaX % lineHeight;
                  totalDeltaY = 0;
                }
              else if (abs (totalDeltaY) >= abs (totalDeltaX)
                       && (!mwheel_coalesce_scroll_events
			   || abs (totalDeltaY) > lineHeight))
                {
                  horizontal = NO;
                  scrollUp = totalDeltaY > 0;

                  lines = abs (totalDeltaY / lineHeight);
		  y = totalDeltaY;
		  if (!mwheel_coalesce_scroll_events)
		    totalDeltaY = 0;
		  else
		    totalDeltaY = totalDeltaY % lineHeight;
                  totalDeltaX = 0;
                }

              if (lines > 1 && ! ns_use_mwheel_acceleration)
                lines = 1;
            }
          else
            {
              CGFloat delta;

              if ([theEvent scrollingDeltaY] == 0)
                {
                  horizontal = YES;
                  delta = [theEvent scrollingDeltaX];
                }
              else
                {
                  horizontal = NO;
                  delta = [theEvent scrollingDeltaY];
                }

              lines = (ns_use_mwheel_acceleration)
                ? ceil (fabs (delta)) : 1;

              scrollUp = delta > 0;
	      x = ([theEvent scrollingDeltaX]
		   * FRAME_COLUMN_WIDTH (emacsframe));
	      y = ([theEvent scrollingDeltaY]
		   * FRAME_LINE_HEIGHT (emacsframe));
            }

          if (lines == 0 && mwheel_coalesce_scroll_events)
            return;

	  if (NUMBERP (Vns_scroll_event_delta_factor))
	    {
	      x *= XFLOATINT (Vns_scroll_event_delta_factor);
	      y *= XFLOATINT (Vns_scroll_event_delta_factor);
	    }

          emacs_event->kind = horizontal ? HORIZ_WHEEL_EVENT : WHEEL_EVENT;
          emacs_event->arg = list3 (make_fixnum (lines),
				    make_float (x),
				    make_float (y));

          emacs_event->code = 0;
          emacs_event->modifiers = EV_MODIFIERS (theEvent) |
            (scrollUp ? up_modifier : down_modifier);
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
        }
      else
#endif
#endif /* defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */
#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 1070
        {
          CGFloat delta = [theEvent deltaY];
          /* Mac notebooks send wheel events with delta equal to 0
	     when trackpad scrolling.  */
          if (delta == 0)
            {
              delta = [theEvent deltaX];
              if (delta == 0)
                {
                  NSTRACE_MSG ("deltaIsZero");
                  return;
                }
              emacs_event->kind = HORIZ_WHEEL_EVENT;
            }
          else
            emacs_event->kind = WHEEL_EVENT;

          emacs_event->code = 0;
          emacs_event->modifiers = EV_MODIFIERS (theEvent) |
            ((delta > 0) ? up_modifier : down_modifier);
        }
#endif
    }
  else
    {
      Lisp_Object tab_bar_arg = Qnil;
      bool tab_bar_p = false;

      if (WINDOWP (emacsframe->tab_bar_window)
	  && WINDOW_TOTAL_LINES (XWINDOW (emacsframe->tab_bar_window)))
	{
	  Lisp_Object window;
	  int x = lrint (p.x);
	  int y = lrint (p.y);

	  window = window_from_coordinates (emacsframe, x, y, 0, true, true);
	  tab_bar_p = EQ (window, emacsframe->tab_bar_window);

	  if (tab_bar_p)
	    tab_bar_arg = handle_tab_bar_click (emacsframe, x, y,
						EV_UDMODIFIERS (theEvent) & down_modifier,
						EV_MODIFIERS (theEvent) | EV_UDMODIFIERS (theEvent));
	}

      if (!(tab_bar_p && NILP (tab_bar_arg)))
	emacs_event->kind = MOUSE_CLICK_EVENT;
      emacs_event->arg = tab_bar_arg;
      emacs_event->code = EV_BUTTON (theEvent);
      emacs_event->modifiers = EV_MODIFIERS (theEvent)
                             | EV_UDMODIFIERS (theEvent);

      if (emacs_event->modifiers & down_modifier)
	FRAME_DISPLAY_INFO (emacsframe)->grabbed |= 1 << EV_BUTTON (theEvent);
      else
	FRAME_DISPLAY_INFO (emacsframe)->grabbed &= ~(1 << EV_BUTTON (theEvent));
    }

  XSETINT (emacs_event->x, lrint (p.x));
  XSETINT (emacs_event->y, lrint (p.y));
  EV_TRAILER (theEvent);
  return;
}


- (void)rightMouseDown: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView rightMouseDown:]");
  [self mouseDown: theEvent];
}


- (void)otherMouseDown: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView otherMouseDown:]");
  [self mouseDown: theEvent];
}


- (void)mouseUp: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView mouseUp:]");
  [self mouseDown: theEvent];
}


- (void)rightMouseUp: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView rightMouseUp:]");
  [self mouseDown: theEvent];
}


- (void)otherMouseUp: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView otherMouseUp:]");
  [self mouseDown: theEvent];
}


- (void) scrollWheel: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView scrollWheel:]");
  [self mouseDown: theEvent];
}


/* Tell emacs the mouse has moved.  */
- (void)mouseMoved: (NSEvent *)e
{
  Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
  Lisp_Object frame;
  NSPoint pt;
  BOOL dragging;

  if (FRAME_TOOLTIP_P (emacsframe))
    return;

  NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "[EmacsView mouseMoved:]");

  dpyinfo->last_mouse_movement_time = EV_TIMESTAMP (e);
  pt = [self convertPoint: [e locationInWindow] fromView: nil];
  dpyinfo->last_mouse_motion_x = pt.x;
  dpyinfo->last_mouse_motion_y = pt.y;

  /* Update any mouse face.  */
  if (hlinfo->mouse_face_hidden)
    {
      hlinfo->mouse_face_hidden = 0;
      clear_mouse_face (hlinfo);
    }

  /* Tooltip handling.  */
  previous_help_echo_string = help_echo_string;
  help_echo_string = Qnil;

  if (!NILP (Vmouse_autoselect_window))
    {
      NSTRACE_MSG ("mouse_autoselect_window");
      static Lisp_Object last_mouse_window;
      Lisp_Object window
	= window_from_coordinates (emacsframe, pt.x, pt.y, 0, 0, 0);

      if (WINDOWP (window)
          && !EQ (window, last_mouse_window)
          && !EQ (window, selected_window)
	  && !MINI_WINDOW_P (XWINDOW (selected_window))
          && (!NILP (focus_follows_mouse)
              || (EQ (XWINDOW (window)->frame,
                      XWINDOW (selected_window)->frame))))
        {
          NSTRACE_MSG ("in_window");
          emacs_event->kind = SELECT_WINDOW_EVENT;
          emacs_event->frame_or_window = window;
          EV_TRAILER2 (e);
        }
      /* Remember the last window where we saw the mouse.  */
      last_mouse_window = window;
    }

  dragging = (e.type == NSEventTypeLeftMouseDragged);
  if (!ns_note_mouse_movement (emacsframe, pt.x, pt.y, dragging))
    help_echo_string = previous_help_echo_string;

  XSETFRAME (frame, emacsframe);
  if (!NILP (help_echo_string) || !NILP (previous_help_echo_string))
    {
      /* NOTE: help_echo_{window,pos,object} are set in xdisp.c
         (note_mouse_highlight), which is called through the
         ns_note_mouse_movement () call above.  */
      any_help_event_p = YES;
      gen_help_event (help_echo_string, frame, help_echo_window,
                      help_echo_object, help_echo_pos);
    }

  if (emacsframe->mouse_moved && send_appdefined)
    ns_send_appdefined (-1);
}


- (void)mouseDragged: (NSEvent *)e
{
  NSTRACE ("[EmacsView mouseDragged:]");
  [self mouseMoved: e];
}


- (void)rightMouseDragged: (NSEvent *)e
{
  NSTRACE ("[EmacsView rightMouseDragged:]");
  [self mouseMoved: e];
}


- (void)otherMouseDragged: (NSEvent *)e
{
  NSTRACE ("[EmacsView otherMouseDragged:]");
  [self mouseMoved: e];
}

#if defined NS_IMPL_COCOA && defined MAC_OS_X_VERSION_10_7
- (void) magnifyWithEvent: (NSEvent *) event
{
  NSPoint pt = [self convertPoint: [event locationInWindow] fromView: nil];
  static CGFloat last_scale;

  NSTRACE ("[EmacsView magnifyWithEvent]");
  if (emacs_event)
    {
      emacs_event->kind = PINCH_EVENT;
      emacs_event->modifiers = EV_MODIFIERS (event);
      XSETINT (emacs_event->x, lrint (pt.x));
      XSETINT (emacs_event->y, lrint (pt.y));
      XSETFRAME (emacs_event->frame_or_window, emacsframe);

      if ([event phase] == NSEventPhaseBegan)
	{
	  last_scale = 1.0 + [event magnification];
	  emacs_event->arg = list4 (make_float (0.0),
				    make_float (0.0),
				    make_float (last_scale),
				    make_float (0.0));
	}
      else
	/* Report a tiny change so that Lisp code doesn't think this
	   is the beginning of an event sequence.  This is the best we
	   can do because NS doesn't report pinch events in as much
	   detail as XInput 2 or GTK+ do.  */
	emacs_event->arg = list4 (make_float (0.01),
				  make_float (0.0),
				  make_float (last_scale += [event magnification]),
				  make_float (0.0));
      EV_TRAILER (event);
    }
}
#endif

- (BOOL)windowShouldClose: (id)sender
{
  NSEvent *e =[[self window] currentEvent];

  NSTRACE ("[EmacsView windowShouldClose:]");
  windowClosing = YES;
  if (!emacs_event)
    return NO;
  emacs_event->kind = DELETE_WINDOW_EVENT;
  emacs_event->modifiers = 0;
  emacs_event->code = 0;
  EV_TRAILER (e);
  /* Don't close this window, let this be done from lisp code.  */
  return NO;
}


- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
/* Normalize frame to gridded text size.  */
{
  int extra = 0;
  int cols, rows;

  NSTRACE ("[EmacsView windowWillResize:toSize: " NSTRACE_FMT_SIZE "]",
           NSTRACE_ARG_SIZE (frameSize));
  NSTRACE_RECT   ("[sender frame]", [sender frame]);
  NSTRACE_FSTYPE ("fs_state", fs_state);

  if (!FRAME_LIVE_P (emacsframe))
    return frameSize;

  if (fs_state == FULLSCREEN_MAXIMIZED
      && (maximized_width != (int)frameSize.width
          || maximized_height != (int)frameSize.height))
    [self setFSValue: FULLSCREEN_NONE];
  else if (fs_state == FULLSCREEN_WIDTH
           && maximized_width != (int)frameSize.width)
    [self setFSValue: FULLSCREEN_NONE];
  else if (fs_state == FULLSCREEN_HEIGHT
           && maximized_height != (int)frameSize.height)
    [self setFSValue: FULLSCREEN_NONE];

  if (fs_state == FULLSCREEN_NONE)
    maximized_width = maximized_height = -1;

  if (! [self isFullscreen])
    {
      extra = FRAME_NS_TITLEBAR_HEIGHT (emacsframe)
        + FRAME_TOOLBAR_HEIGHT (emacsframe);
    }

  cols = FRAME_PIXEL_WIDTH_TO_TEXT_COLS (emacsframe, frameSize.width);
  if (cols < MINWIDTH)
    cols = MINWIDTH;

  rows = FRAME_PIXEL_HEIGHT_TO_TEXT_LINES (emacsframe,
                                           frameSize.height - extra);
  if (rows < MINHEIGHT)
    rows = MINHEIGHT;
#ifdef NS_IMPL_COCOA
  {
    /* This sets window title to have size in it; the wm does this under GS.  */
    NSRect r = [[self window] frame];
    if (r.size.height == frameSize.height && r.size.width == frameSize.width)
      {
        if (old_title != 0)
          {
            xfree (old_title);
            old_title = 0;
          }
      }
    else if (fs_state == FULLSCREEN_NONE && ! maximizing_resize
             && [[self window] title] != NULL)
      {
        char *size_title;
        NSWindow *window = [self window];
        if (old_title == 0)
          {
            char *t = strdup ([[[self window] title] UTF8String]);
            char *pos = strstr (t, "  —  ");
            if (pos)
              *pos = '\0';
            old_title = t;
          }
        size_title = xmalloc (strlen (old_title) + 40);
	esprintf (size_title, "%s  —  (%d × %d)", old_title, cols, rows);
        [window setTitle: [NSString stringWithUTF8String: size_title]];
        [window display];
        xfree (size_title);
      }
  }
#endif /* NS_IMPL_COCOA */

  NSTRACE_MSG ("cols: %d  rows: %d", cols, rows);

  /* Restrict the new size to the text grid.

     Don't restrict the width if the user only adjusted the height, and
     vice versa.  (Without this, the frame would shrink, and move
     slightly, if the window was resized by dragging one of its
     borders.)  */
  if (!frame_resize_pixelwise)
    {
      NSRect r = [[self window] frame];

      if (r.size.width != frameSize.width)
        {
          frameSize.width =
            FRAME_TEXT_COLS_TO_PIXEL_WIDTH  (emacsframe, cols);
        }

      if (r.size.height != frameSize.height)
        {
          frameSize.height =
            FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (emacsframe, rows) + extra;
        }
    }

  NSTRACE_RETURN_SIZE (frameSize);

  return frameSize;
}


#ifdef NS_IMPL_COCOA
- (void)viewDidEndLiveResize
{
  NSTRACE ("[EmacsView viewDidEndLiveResize]");

  [super viewDidEndLiveResize];
  if (old_title != 0)
    {
      [[self window] setTitle: [NSString stringWithUTF8String: old_title]];
      xfree (old_title);
      old_title = 0;
    }
  maximizing_resize = NO;
}
#endif /* NS_IMPL_COCOA */


- (void)resizeWithOldSuperviewSize: (NSSize)oldSize
{
  NSRect frame;
  int width, height;

  NSTRACE ("[EmacsView resizeWithOldSuperviewSize:]");

  [super resizeWithOldSuperviewSize:oldSize];

  if (! FRAME_LIVE_P (emacsframe))
    return;

  frame = [[self superview] bounds];
  width = (int)NSWidth (frame);
  height = (int)NSHeight (frame);

  NSTRACE_SIZE ("New size", NSMakeSize (width, height));

  /* Reset the frame size to match the bounds of the superview (the
     NSWindow's contentView).  We need to do this as sometimes the
     view's frame isn't resized correctly, or can end up with the
     wrong origin.  */
  [self setFrame:frame];
  change_frame_size (emacsframe, width, height, false, YES, false);

  SET_FRAME_GARBAGED (emacsframe);
  cancel_mouse_face (emacsframe);
  ns_send_appdefined (-1);
}


- (void)windowDidBecomeKey: (NSNotification *)notification
/* cf. x_detect_focus_change(), x_focus_changed(), x_new_focus_frame() */
{
  [self windowDidBecomeKey];
}


- (void)windowDidBecomeKey      /* for direct calls */
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
  struct frame *old_focus = dpyinfo->ns_focus_frame;
  struct input_event event;

  EVENT_INIT (event);

  NSTRACE ("[EmacsView windowDidBecomeKey]");

  if (emacsframe != old_focus)
    dpyinfo->ns_focus_frame = emacsframe;

  ns_frame_rehighlight (emacsframe);

  event.kind = FOCUS_IN_EVENT;
  XSETFRAME (event.frame_or_window, emacsframe);
  kbd_buffer_store_event (&event);
}


- (void)windowDidResignKey: (NSNotification *)notification
/* cf. x_detect_focus_change(), x_focus_changed(), x_new_focus_frame() */
{
  struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
  BOOL is_focus_frame = dpyinfo->ns_focus_frame == emacsframe;
  NSTRACE ("[EmacsView windowDidResignKey:]");

  if (is_focus_frame)
    dpyinfo->ns_focus_frame = 0;

  emacsframe->mouse_moved = 0;
  ns_frame_rehighlight (emacsframe);

  /* FIXME: for some reason needed on second and subsequent clicks away
            from sole-frame Emacs to get hollow box to show.  */
  if (!windowClosing && [[self window] isVisible] == YES)
    {
      gui_update_cursor (emacsframe, 1);
      ns_set_frame_alpha (emacsframe);
    }

  if (any_help_event_p)
    {
      Lisp_Object frame;
      XSETFRAME (frame, emacsframe);
      help_echo_string = Qnil;
      gen_help_event (Qnil, frame, Qnil, Qnil, 0);
      any_help_event_p = NO;
    }

  if (emacs_event && is_focus_frame)
    {
      emacs_event->kind = FOCUS_OUT_EVENT;
      EV_TRAILER ((id)nil);
    }
}


- (void)windowWillMiniaturize: sender
{
  NSTRACE ("[EmacsView windowWillMiniaturize:]");
}


- (void)setFrame:(NSRect)frameRect
{
  NSTRACE ("[EmacsView setFrame:" NSTRACE_FMT_RECT "]",
           NSTRACE_ARG_RECT (frameRect));

  [super setFrame:(NSRect)frameRect];
}


- (BOOL)isFlipped
{
  return YES;
}


- (BOOL)isOpaque
{
  return NO;
}


- (instancetype) initFrameFromEmacs: (struct frame *)f
{
  NSTRACE ("[EmacsView initFrameFromEmacs:]");
  NSTRACE_MSG ("cols:%d lines:%d", f->text_cols, f->text_lines);

  windowClosing = NO;
  processingCompose = NO;
  scrollbarsNeedingUpdate = 0;
  fs_state = FULLSCREEN_NONE;
  fs_before_fs = next_maximized = -1;

  fs_is_native = NO;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
  if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7)
#endif
    fs_is_native = ns_use_native_fullscreen;
#endif

  maximized_width = maximized_height = -1;
  nonfs_window = nil;

  ns_userRect = NSMakeRect (0, 0, 0, 0);
  [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;
  emacsframe = f;
#ifdef NS_IMPL_COCOA
  old_title = 0;
  maximizing_resize = NO;
#endif

  [[EmacsWindow alloc] initWithEmacsFrame:f];

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
  /* 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];

  /* initWithEmacsFrame can't create the toolbar before the layer is
     set, so have another go at creating the toolbar here.  */
  [(EmacsWindow*)[self window] createToolbar:f];
#endif

  if (ns_drag_types)
    [self registerForDraggedTypes: ns_drag_types];

#if !defined (NS_IMPL_COCOA) \
  || MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
#if MAC_OS_X_VERSION_MAX_ALLOWED > 1090
  if ([self respondsToSelector: @selector(allocateGState)])
#endif
    [self allocateGState];
#endif
  [NSApp registerServicesMenuSendTypes: ns_send_types
                           returnTypes: [NSArray array]];

  ns_window_num++;
  return self;
}


- (void)windowDidMove: sender
{
  NSWindow *win = [self window];
  NSRect r = [win frame];
  NSArray *screens = [NSScreen screens];
  NSScreen *screen = [screens objectAtIndex: 0];

  NSTRACE ("[EmacsView windowDidMove:]");

  if (!emacsframe->output_data.ns)
    return;

  if (screen != nil)
    {
      emacsframe->left_pos = (NSMinX (r)
			      - NS_PARENT_WINDOW_LEFT_POS (emacsframe));
      emacsframe->top_pos = (NS_PARENT_WINDOW_TOP_POS (emacsframe)
			     - NSMaxY (r));

      if (emacs_event)
	{
	  struct input_event ie;
	  EVENT_INIT (ie);
	  ie.kind = MOVE_FRAME_EVENT;
	  XSETFRAME (ie.frame_or_window, emacsframe);
	  XSETINT (ie.x, emacsframe->left_pos);
	  XSETINT (ie.y, emacsframe->top_pos);
	  kbd_buffer_store_event (&ie);
	}
    }
}


/* Called AFTER method below, but before our windowWillResize call there leads
   to windowDidResize -> ns_set_window_size.  Update emacs' notion of frame
   location so set_window_size moves the frame.  */
- (BOOL)windowShouldZoom: (NSWindow *)sender toFrame: (NSRect)newFrame
{
  NSTRACE (("[EmacsView windowShouldZoom:toFrame:" NSTRACE_FMT_RECT "]"
            NSTRACE_FMT_RETURN "YES"),
           NSTRACE_ARG_RECT (newFrame));

  emacsframe->output_data.ns->zooming = 1;
  return YES;
}


/* Override to do something slightly nonstandard, but nice.  First click on
   zoom button will zoom vertically.  Second will zoom completely.  Third
   returns to original.  */
- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender
                        defaultFrame:(NSRect)defaultFrame
{
  // TODO: Rename to "currentFrame" and assign "result" properly in
  // all paths.
  NSRect result = [sender frame];

  NSTRACE (("[EmacsView windowWillUseStandardFrame:defaultFrame:"
            NSTRACE_FMT_RECT "]"),
           NSTRACE_ARG_RECT (defaultFrame));
  NSTRACE_FSTYPE ("fs_state", fs_state);
  NSTRACE_FSTYPE ("fs_before_fs", fs_before_fs);
  NSTRACE_FSTYPE ("next_maximized", next_maximized);
  NSTRACE_RECT   ("ns_userRect", ns_userRect);
  NSTRACE_RECT   ("[sender frame]", [sender frame]);

  if (fs_before_fs != -1) /* Entering fullscreen */
    {
      NSTRACE_MSG ("Entering fullscreen");
      result = defaultFrame;
    }
  else
    {
      // Save the window size and position (frame) before the resize.
      if (fs_state != FULLSCREEN_MAXIMIZED
          && fs_state != FULLSCREEN_WIDTH)
        {
          ns_userRect.size.width = result.size.width;
          ns_userRect.origin.x   = result.origin.x;
        }

      if (fs_state != FULLSCREEN_MAXIMIZED
          && fs_state != FULLSCREEN_HEIGHT)
        {
          ns_userRect.size.height = result.size.height;
          ns_userRect.origin.y    = result.origin.y;
        }

      NSTRACE_RECT ("ns_userRect (2)", ns_userRect);

      if (next_maximized == FULLSCREEN_HEIGHT
          || (next_maximized == -1
              && abs ((int)(defaultFrame.size.height - result.size.height))
              > FRAME_LINE_HEIGHT (emacsframe)))
        {
          /* first click */
          NSTRACE_MSG ("FULLSCREEN_HEIGHT");
          maximized_height = result.size.height = defaultFrame.size.height;
          maximized_width = -1;
          result.origin.y = defaultFrame.origin.y;
          if (ns_userRect.size.height != 0)
            {
              result.origin.x = ns_userRect.origin.x;
              result.size.width = ns_userRect.size.width;
            }
          [self setFSValue: FULLSCREEN_HEIGHT];
#ifdef NS_IMPL_COCOA
          maximizing_resize = YES;
#endif
        }
      else if (next_maximized == FULLSCREEN_WIDTH)
        {
          NSTRACE_MSG ("FULLSCREEN_WIDTH");
          maximized_width = result.size.width = defaultFrame.size.width;
          maximized_height = -1;
          result.origin.x = defaultFrame.origin.x;
          if (ns_userRect.size.width != 0)
            {
              result.origin.y = ns_userRect.origin.y;
              result.size.height = ns_userRect.size.height;
            }
          [self setFSValue: FULLSCREEN_WIDTH];
        }
      else if (next_maximized == FULLSCREEN_MAXIMIZED
               || (next_maximized == -1
                   && abs ((int)(defaultFrame.size.width - result.size.width))
                   > FRAME_COLUMN_WIDTH (emacsframe)))
        {
          NSTRACE_MSG ("FULLSCREEN_MAXIMIZED");

          result = defaultFrame; /* second click */
          maximized_width = result.size.width;
          maximized_height = result.size.height;
          [self setFSValue: FULLSCREEN_MAXIMIZED];
#ifdef NS_IMPL_COCOA
          maximizing_resize = YES;
#endif
        }
      else
        {
          /* restore */
          NSTRACE_MSG ("Restore");
          result = ns_userRect.size.height ? ns_userRect : result;
          NSTRACE_RECT ("restore (2)", result);
          ns_userRect = NSMakeRect (0, 0, 0, 0);
#ifdef NS_IMPL_COCOA
          maximizing_resize = fs_state != FULLSCREEN_NONE;
#endif
          [self setFSValue: FULLSCREEN_NONE];
          maximized_width = maximized_height = -1;
        }
    }

  if (fs_before_fs == -1) next_maximized = -1;

  NSTRACE_RECT   ("Final ns_userRect", ns_userRect);
  NSTRACE_MSG    ("Final maximized_width: %d", maximized_width);
  NSTRACE_MSG    ("Final maximized_height: %d", maximized_height);
  NSTRACE_FSTYPE ("Final next_maximized", next_maximized);

  [self windowWillResize: sender toSize: result.size];

  NSTRACE_RETURN_RECT (result);

  return result;
}


- (void)windowDidDeminiaturize: sender
{
  NSTRACE ("[EmacsView windowDidDeminiaturize:]");
  if (!emacsframe->output_data.ns)
    return;

  SET_FRAME_ICONIFIED (emacsframe, 0);
  SET_FRAME_VISIBLE (emacsframe, 1);
  windows_or_buffers_changed = 63;

  if (emacs_event)
    {
      emacs_event->kind = DEICONIFY_EVENT;
      EV_TRAILER ((id)nil);
    }
}


- (void)windowDidExpose: sender
{
  NSTRACE ("[EmacsView windowDidExpose:]");
  if (!emacsframe->output_data.ns)
    return;

  SET_FRAME_VISIBLE (emacsframe, 1);
  SET_FRAME_GARBAGED (emacsframe);

  if (send_appdefined)
    ns_send_appdefined (-1);
}


- (void)windowDidMiniaturize: sender
{
  NSTRACE ("[EmacsView windowDidMiniaturize:]");
  if (!emacsframe->output_data.ns)
    return;

  SET_FRAME_ICONIFIED (emacsframe, 1);
  SET_FRAME_VISIBLE (emacsframe, 0);

  if (emacs_event)
    {
      emacs_event->kind = ICONIFY_EVENT;
      EV_TRAILER ((id)nil);
    }
}

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
- (NSApplicationPresentationOptions)window:(NSWindow *)window
      willUseFullScreenPresentationOptions:
  (NSApplicationPresentationOptions)proposedOptions
{
  return proposedOptions|NSApplicationPresentationAutoHideToolbar;
}
#endif

- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
  NSTRACE ("[EmacsView windowWillEnterFullScreen:]");
  [self windowWillEnterFullScreen];
}
- (void)windowWillEnterFullScreen /* provided for direct calls */
{
  NSTRACE ("[EmacsView windowWillEnterFullScreen]");
  fs_before_fs = fs_state;
}

- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
  NSTRACE ("[EmacsView windowDidEnterFullScreen:]");
  [self windowDidEnterFullScreen];
}

- (void)windowDidEnterFullScreen /* provided for direct calls */
{
  NSTRACE ("[EmacsView windowDidEnterFullScreen]");
  [self setFSValue: FULLSCREEN_BOTH];
  if (! [self fsIsNative])
    {
      [self windowDidBecomeKey];
      [nonfs_window orderOut:self];
    }
  else
    {
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 \
  && MAC_OS_X_VERSION_MIN_REQUIRED <= 1070
      unsigned val = (unsigned)[NSApp presentationOptions];

      // Mac OS X 10.7 bug fix, the menu won't appear without this.
      // val is non-zero on other macOS versions.
      if (val == 0)
        {
          NSApplicationPresentationOptions options
            = NSApplicationPresentationAutoHideDock
            | NSApplicationPresentationAutoHideMenuBar
            | NSApplicationPresentationFullScreen
            | NSApplicationPresentationAutoHideToolbar;

          [NSApp setPresentationOptions: options];
        }
#endif
    }
}

- (void)windowWillExitFullScreen:(NSNotification *)notification
{
  NSTRACE ("[EmacsView windowWillExitFullScreen:]");
  [self windowWillExitFullScreen];
}

- (void)windowWillExitFullScreen /* provided for direct calls */
{
  NSTRACE ("[EmacsView windowWillExitFullScreen]");
  if (!FRAME_LIVE_P (emacsframe))
    {
      NSTRACE_MSG ("Ignored (frame dead)");
      return;
    }
  if (next_maximized != -1)
    fs_before_fs = next_maximized;
}

- (void)windowDidExitFullScreen:(NSNotification *)notification
{
  NSTRACE ("[EmacsView windowDidExitFullScreen:]");
  [self windowDidExitFullScreen];
}

- (void)windowDidExitFullScreen /* provided for direct calls */
{
  NSTRACE ("[EmacsView windowDidExitFullScreen]");
  if (!FRAME_LIVE_P (emacsframe))
    {
      NSTRACE_MSG ("Ignored (frame dead)");
      return;
    }
  [self setFSValue: fs_before_fs];
  fs_before_fs = -1;
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  [self updateCollectionBehavior];
#endif

  if (next_maximized != -1)
    [[self window] performZoom:self];
}

- (BOOL)fsIsNative
{
  return fs_is_native;
}

- (BOOL)isFullscreen
{
  BOOL res;

  if (! fs_is_native)
    {
      res = (nonfs_window != nil);
    }
  else
    {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
      res = (([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0);
#else
      res = NO;
#endif
    }

  NSTRACE ("[EmacsView isFullscreen] " NSTRACE_FMT_RETURN " %d",
           (int) res);

  return res;
}

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
- (void)updateCollectionBehavior
{
  NSTRACE ("[EmacsView updateCollectionBehavior]");

  if (! [self isFullscreen])
    {
      NSWindow *win = [self window];
      NSWindowCollectionBehavior b = [win collectionBehavior];
      if (ns_use_native_fullscreen)
        {
          if (FRAME_PARENT_FRAME (emacsframe))
            {
              b &= ~NSWindowCollectionBehaviorFullScreenPrimary;
              b |= NSWindowCollectionBehaviorFullScreenAuxiliary;
            }
          else
            {
              b |= NSWindowCollectionBehaviorFullScreenPrimary;
              b &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
            }
        }
      else
        {
          b &= ~NSWindowCollectionBehaviorFullScreenPrimary;
        }

      [win setCollectionBehavior: b];
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
      if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7)
#endif
        fs_is_native = ns_use_native_fullscreen;
    }
}
#endif

- (void)toggleFullScreen: (id)sender
{
  EmacsWindow *w, *fw;
  BOOL onFirstScreen;
  struct frame *f;
  NSRect r;
  NSColor *col;

  NSTRACE ("[EmacsView toggleFullScreen:]");

  if (fs_is_native)
    {
#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];
#endif
      return;
    }

  w = (EmacsWindow *)[self window];
  onFirstScreen = [[w screen] isEqual:[[NSScreen screens] objectAtIndex:0]];
  f = emacsframe;
  col = [NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND
				 (FACE_FROM_ID (f, DEFAULT_FACE_ID))];

  if (fs_state != FULLSCREEN_BOTH)
    {
      NSScreen *screen = [w screen];

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
      /* Hide ghost menu bar on secondary monitor?  */
      if (! onFirstScreen
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090
          && [NSScreen respondsToSelector: @selector(screensHaveSeparateSpaces)]
#endif
          )
        onFirstScreen = [NSScreen screensHaveSeparateSpaces];
#endif
      /* Hide dock and menubar if we are on the primary screen.  */
      if (onFirstScreen)
        {
#ifdef NS_IMPL_COCOA
          NSApplicationPresentationOptions options
            = NSApplicationPresentationAutoHideDock
            | NSApplicationPresentationAutoHideMenuBar;

          [NSApp setPresentationOptions: options];
#else
          [NSMenu setMenuBarVisible:NO];
#endif
        }

      fw = [[EmacsWindow alloc] initWithEmacsFrame:emacsframe
                                        fullscreen:YES
                                            screen:screen];

      f->border_width = 0;

      nonfs_window = w;

      [self windowWillEnterFullScreen];
      [fw makeKeyAndOrderFront:NSApp];
      [w orderOut:self];
      r = [fw frameRectForContentRect:[screen frame]];
      [fw setFrame: r display:YES animate:ns_use_fullscreen_animation];
      [self windowDidEnterFullScreen];
      [fw display];
    }
  else
    {
      fw = w;
      w = nonfs_window;
      nonfs_window = nil;

      if (onFirstScreen)
        {
#ifdef NS_IMPL_COCOA
          [NSApp setPresentationOptions: NSApplicationPresentationDefault];
#else
          [NSMenu setMenuBarVisible:YES];
#endif
        }

      [w setContentView:[fw contentView]];
      [w setBackgroundColor: col];
      if ([col alphaComponent] != (EmacsCGFloat) 1.0)
        [w setOpaque: NO];

      f->border_width = [w borderWidth];

      // To do: consider using [NSNotificationCenter postNotificationName:] to
      // send notifications.

      [self windowWillExitFullScreen];
      [fw setFrame:[[w contentView] frame]
                    display:YES animate:ns_use_fullscreen_animation];
      [fw close];
      [w makeKeyAndOrderFront:NSApp];
      [self windowDidExitFullScreen];
    }
}

- (void)handleFS
{
  NSTRACE ("[EmacsView handleFS]");

  if (fs_state != emacsframe->want_fullscreen)
    {
      if (fs_state == FULLSCREEN_BOTH)
        {
          NSTRACE_MSG ("fs_state == FULLSCREEN_BOTH");
          [self toggleFullScreen:self];
        }

      switch (emacsframe->want_fullscreen)
        {
        case FULLSCREEN_BOTH:
          NSTRACE_MSG ("FULLSCREEN_BOTH");
          [self toggleFullScreen:self];
          break;
        case FULLSCREEN_WIDTH:
          NSTRACE_MSG ("FULLSCREEN_WIDTH");
          next_maximized = FULLSCREEN_WIDTH;
          if (fs_state != FULLSCREEN_BOTH)
            [[self window] performZoom:self];
          break;
        case FULLSCREEN_HEIGHT:
          NSTRACE_MSG ("FULLSCREEN_HEIGHT");
          next_maximized = FULLSCREEN_HEIGHT;
          if (fs_state != FULLSCREEN_BOTH)
            [[self window] performZoom:self];
          break;
        case FULLSCREEN_MAXIMIZED:
          NSTRACE_MSG ("FULLSCREEN_MAXIMIZED");
          next_maximized = FULLSCREEN_MAXIMIZED;
          if (fs_state != FULLSCREEN_BOTH)
            [[self window] performZoom:self];
          break;
        case FULLSCREEN_NONE:
          NSTRACE_MSG ("FULLSCREEN_NONE");
          if (fs_state != FULLSCREEN_BOTH)
            {
              next_maximized = FULLSCREEN_NONE;
              [[self window] performZoom:self];
            }
          break;
        }

      emacsframe->want_fullscreen = FULLSCREEN_NONE;
    }

}

- (void) setFSValue: (int)value
{
  NSTRACE ("[EmacsView setFSValue:" NSTRACE_FMT_FSTYPE "]",
           NSTRACE_ARG_FSTYPE(value));

  Lisp_Object lval = Qnil;
  switch (value)
    {
    case FULLSCREEN_BOTH:
      lval = Qfullboth;
      break;
    case FULLSCREEN_WIDTH:
      lval = Qfullwidth;
      break;
    case FULLSCREEN_HEIGHT:
      lval = Qfullheight;
      break;
    case FULLSCREEN_MAXIMIZED:
      lval = Qmaximized;
      break;
    }
  store_frame_param (emacsframe, Qfullscreen, lval);
  fs_state = value;
}

- (void)mouseEntered: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsView mouseEntered:]");
  if (emacsframe)
    FRAME_DISPLAY_INFO (emacsframe)->last_mouse_movement_time
      = EV_TIMESTAMP (theEvent);
}


- (void)mouseExited: (NSEvent *)theEvent
{
  Mouse_HLInfo *hlinfo = emacsframe ? MOUSE_HL_INFO (emacsframe) : NULL;

  NSTRACE ("[EmacsView mouseExited:]");

  if (!hlinfo)
    return;

  FRAME_DISPLAY_INFO (emacsframe)->last_mouse_movement_time
    = EV_TIMESTAMP (theEvent);

  if (emacsframe == hlinfo->mouse_face_mouse_frame)
    {
      clear_mouse_face (hlinfo);
      hlinfo->mouse_face_mouse_frame = 0;
    }
}


- (instancetype)menuDown: sender
{
  NSTRACE ("[EmacsView menuDown:]");
  if (context_menu_value == -1)
    context_menu_value = [sender tag];
  else
    {
      NSInteger tag = [sender tag];
      find_and_call_menu_selection (emacsframe, emacsframe->menu_bar_items_used,
                                    emacsframe->menu_bar_vector,
                                    (void *)tag);
    }

  ns_send_appdefined (-1);
  return self;
}


/* This gets called on toolbar button click.  */
- (instancetype)toolbarClicked: (id)item
{
  NSEvent *theEvent;
  int idx = [item tag] * TOOL_BAR_ITEM_NSLOTS;

  NSTRACE ("[EmacsView toolbarClicked:]");

  if (!emacs_event)
    return self;

  theEvent = [[self window] currentEvent];
  emacs_event->kind = TOOL_BAR_EVENT;
  /* XSETINT (emacs_event->code, 0); */
  emacs_event->arg = AREF (emacsframe->tool_bar_items,
			   idx + TOOL_BAR_ITEM_KEY);
  emacs_event->modifiers = EV_MODIFIERS (theEvent);
  EV_TRAILER (theEvent);
  return self;
}

- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
{
  return [toolbarItem isEnabled];
}

- (instancetype)toggleToolbar: (id)sender
{
  NSTRACE ("[EmacsView toggleToolbar:]");

  if (!emacs_event)
    return self;

  emacs_event->kind = NS_NONKEY_EVENT;
  emacs_event->code = KEY_NS_TOGGLE_TOOLBAR;
  EV_TRAILER ((id)nil);
  return self;
}


#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
- (CALayer *)makeBackingLayer
{
  EmacsLayer *l = [[EmacsLayer alloc]
                    initWithColorSpace:[[[self window] colorSpace] CGColorSpace]];
  [l setDelegate:(id)self];
  [l setContentsScale:[[self window] backingScaleFactor]];

  return l;
}


- (void)lockFocus
{
  NSTRACE ("[EmacsView lockFocus]");

  CGContextRef context = [(EmacsLayer*)[self layer] getContext];

  [NSGraphicsContext
        setCurrentContext:[NSGraphicsContext
                            graphicsContextWithCGContext:context
                                                 flipped:YES]];
}


- (void)unlockFocus
{
  NSTRACE ("[EmacsView unlockFocus]");

  [NSGraphicsContext setCurrentContext:nil];
  [self setNeedsDisplay:YES];
}


- (void)windowDidChangeBackingProperties:(NSNotification *)notification
  /* Update the drawing buffer when the backing properties change.  */
{
  NSTRACE ("EmacsView windowDidChangeBackingProperties:]");

  NSRect frame = [self frame];
  EmacsLayer *layer = (EmacsLayer *)[self layer];

  [layer setContentsScale:[[notification object] backingScaleFactor]];
  [layer setColorSpace:[(id) [[notification object] colorSpace] CGColorSpace]];

  ns_clear_frame (emacsframe);
  expose_frame (emacsframe, 0, 0, NSWidth (frame), NSHeight (frame));
}
#endif


- (void)copyRect:(NSRect)srcRect to:(NSPoint)dest
{
  NSTRACE ("[EmacsView copyRect:To:]");
  NSTRACE_RECT ("Source", srcRect);
  NSTRACE_POINT ("Destination", dest);

  NSRect dstRect = NSMakeRect (dest.x, dest.y, NSWidth (srcRect),
                               NSHeight (srcRect));

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
  double scale = [[self window] backingScaleFactor];
  CGContextRef context = [(EmacsLayer *)[self layer] getContext];
  int bpp = CGBitmapContextGetBitsPerPixel (context) / 8;
  void *pixels = CGBitmapContextGetData (context);
  int rowSize = CGBitmapContextGetBytesPerRow (context);
  int srcRowSize = NSWidth (srcRect) * scale * bpp;
  void *srcPixels = (char *) pixels
    + (int) (NSMinY (srcRect) * scale * rowSize
             + NSMinX (srcRect) * scale * bpp);
  void *dstPixels = (char *) pixels
    + (int) (dest.y * scale * rowSize
             + dest.x * scale * bpp);

  if (NSIntersectsRect (srcRect, dstRect)
      && NSMinY (srcRect) < NSMinY (dstRect))
    for (int y = NSHeight (srcRect) * scale - 1 ; y >= 0 ; y--)
      memmove ((char *) dstPixels + y * rowSize,
               (char *) srcPixels + y * rowSize,
               srcRowSize);
  else
    for (int y = 0 ; y < NSHeight (srcRect) * scale ; y++)
      memmove ((char *) dstPixels + y * rowSize,
               (char *) srcPixels + y * rowSize,
               srcRowSize);

#else
  hide_bell();              // Ensure the bell image isn't scrolled.

  ns_focus (emacsframe, &dstRect, 1);
  [self scrollRect: srcRect
                by: NSMakeSize (dstRect.origin.x - srcRect.origin.x,
                                dstRect.origin.y - srcRect.origin.y)];
  ns_unfocus (emacsframe);
#endif
}


#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
/* 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 ();

      redisplay ();

      unblock_input ();
      waiting_for_input = owfi;
    }
}
#endif

- (void)drawRect: (NSRect)rect
{
  NSTRACE ("[EmacsView drawRect:" NSTRACE_FMT_RECT "]",
           NSTRACE_ARG_RECT(rect));

  if (!emacsframe || !emacsframe->output_data.ns)
    return;

  int x = NSMinX (rect), y = NSMinY (rect);
  int width = NSWidth (rect), height = NSHeight (rect);

  ns_clear_frame_area (emacsframe, x, y, width, height);
  block_input ();
  expose_frame (emacsframe, x, y, width, height);
  unblock_input ();
}


/* NSDraggingDestination protocol methods.  Actually this is not really a
   protocol, but a category of Object.  O well...  */

-(NSDragOperation) draggingEntered: (id <NSDraggingInfo>) sender
{
  id source;

  NSTRACE ("[EmacsView draggingEntered:]");

  source = [sender draggingSource];

  if (source && [source respondsToSelector: @selector(mustNotDropOn:)]
      && [source mustNotDropOn: self])
    return NSDragOperationNone;

  return NSDragOperationGeneric;
}


-(BOOL) prepareForDragOperation: (id <NSDraggingInfo>) sender
{
  id source;

  source = [sender draggingSource];

  if (source && [source respondsToSelector: @selector(mustNotDropOn:)]
      && [source mustNotDropOn: self])
    return NO;

  return YES;
}

- (BOOL) wantsPeriodicDraggingUpdates
{
  return YES;
}

- (NSDragOperation) draggingUpdated: (id <NSDraggingInfo>) sender
{
#ifdef NS_IMPL_GNUSTEP
  struct input_event ie;
#else
  Lisp_Object frame;
#endif
  NSPoint position;
  int x, y;
  NSAutoreleasePool *ap;
  specpdl_ref count;

  ap = [[NSAutoreleasePool alloc] init];
  count = SPECPDL_INDEX ();
  record_unwind_protect_ptr (ns_release_autorelease_pool, ap);

#ifdef NS_IMPL_GNUSTEP
  EVENT_INIT (ie);
  ie.kind = DRAG_N_DROP_EVENT;
#endif

  /* Get rid of mouse face.  */
  [self mouseExited: [[self window] currentEvent]];

  position = [self convertPoint: [sender draggingLocation]
		       fromView: nil];
  x = lrint (position.x);
  y = lrint (position.y);

#ifdef NS_IMPL_GNUSTEP
  XSETINT (ie.x, x);
  XSETINT (ie.y, y);
  XSETFRAME (ie.frame_or_window, emacsframe);
  ie.arg = Qlambda;
  ie.modifiers = 0;

  kbd_buffer_store_event (&ie);
#else
  /* Input events won't be processed until the drop happens on macOS,
     so call this function instead.  */
  XSETFRAME (frame, emacsframe);

  safe_call (4, Vns_drag_motion_function, frame,
	     make_fixnum (x), make_fixnum (y));

  redisplay ();
#endif

  unbind_to (count, Qnil);
  return NSDragOperationGeneric;
}

- (BOOL) performDragOperation: (id <NSDraggingInfo>) sender
{
  id pb, source;
  int x, y;
  NSString *type;
  NSPoint position;
  NSDragOperation op = [sender draggingSourceOperationMask];
  Lisp_Object operations = Qnil;
  Lisp_Object strings = Qnil;
  Lisp_Object type_sym;
  struct input_event ie;

  NSTRACE (@"[EmacsView performDragOperation:]");

  source = [sender draggingSource];

  if (source && [source respondsToSelector: @selector(mustNotDropOn:)]
      && [source mustNotDropOn: self])
    return NO;

  position = [self convertPoint: [sender draggingLocation] fromView: nil];
  x = lrint (position.x);
  y = lrint (position.y);

  pb = [sender draggingPasteboard];
  type = [pb availableTypeFromArray: ns_drag_types];

  /* We used to convert these drag operations to keyboard modifiers,
     but because they can be set by the sending program as well as the
     keyboard modifiers it was difficult to work out a sensible key
     mapping for drag and drop.  */
  if (op & NSDragOperationLink)
    operations = Fcons (Qns_drag_operation_link, operations);
  if (op & NSDragOperationCopy)
    operations = Fcons (Qns_drag_operation_copy, operations);
  if (op & NSDragOperationGeneric || NILP (operations))
    operations = Fcons (Qns_drag_operation_generic, operations);

  if (!type)
    return NO;
#if NS_USE_NSPasteboardTypeFileURL
  else if ([type isEqualToString: NSPasteboardTypeFileURL])
    {
      type_sym = Qfile;

      NSArray *urls = [pb readObjectsForClasses: @[[NSURL self]]
                                        options: nil];
      NSEnumerator *uenum = [urls objectEnumerator];
      NSURL *url;
      while ((url = [uenum nextObject]))
        strings = Fcons ([[url path] lispString], strings);
    }
#else  // !NS_USE_NSPasteboardTypeFileURL
  else if ([type isEqualToString: NSFilenamesPboardType])
    {
      id files;
      NSEnumerator *fenum;
      NSString *file;

      files = [pb propertyListForType: type];

      if (!files)
        return NO;

      type_sym = Qfile;

      /* On GNUstep, files might be a string.  */

      if ([files respondsToSelector: @selector (objectEnumerator:)])
	{
	  fenum = [files objectEnumerator];

	  while ((file = [fenum nextObject]))
	    strings = Fcons ([file lispString], strings);
	}
      else
	/* Then `files' is an NSString.  */
	strings = list1 ([files lispString]);
    }
#endif   // !NS_USE_NSPasteboardTypeFileURL
  else if ([type isEqualToString: NSPasteboardTypeURL])
    {
      NSURL *url = [NSURL URLFromPasteboard: pb];
      if (url == nil) return NO;

      type_sym = Qurl;

      strings = list1 ([[url absoluteString] lispString]);
    }
  else if ([type isEqualToString: NSPasteboardTypeString]
           || [type isEqualToString: NSPasteboardTypeTabularText])
    {
      NSString *data;

      data = [pb stringForType: type];

      if (!data)
        return NO;

      type_sym = Qnil;
      strings = list1 ([data lispString]);
    }
  else
    return NO;

  EVENT_INIT (ie);
  ie.kind = DRAG_N_DROP_EVENT;
  ie.arg = Fcons (type_sym, Fcons (operations,
				   strings));
  XSETINT (ie.x, x);
  XSETINT (ie.y, y);
  XSETFRAME (ie.frame_or_window, emacsframe);

  kbd_buffer_store_event (&ie);
  return YES;
}


- (id) validRequestorForSendType: (NSString *)typeSent
                      returnType: (NSString *)typeReturned
{
  NSTRACE ("[EmacsView validRequestorForSendType:returnType:]");
  if (typeSent != nil && [ns_send_types indexOfObject: typeSent] != NSNotFound
      && typeReturned == nil)
    {
      if (! NILP (ns_get_local_selection (QPRIMARY, QUTF8_STRING)))
        return self;
    }

  return [super validRequestorForSendType: typeSent
                               returnType: typeReturned];
}


/* The next two methods are part of NSServicesRequests informal protocol,
   supposedly called when a services menu item is chosen from this app.
   But this should not happen because we override the services menu with our
   own entries which call ns-perform-service.
   Nonetheless, it appeared to happen (under strange circumstances): bug#1435.
   So let's at least stub them out until further investigation can be done.  */

- (BOOL) readSelectionFromPasteboard: (NSPasteboard *)pb
{
  /* We could call ns_string_from_pasteboard(pboard) here but then it should
     be written into the buffer in place of the existing selection.
     Ordinary service calls go through functions defined in ns-win.el.  */
  return NO;
}

- (BOOL) writeSelectionToPasteboard: (NSPasteboard *)pb types: (NSArray *)types
{
  NSArray *typesDeclared;
  Lisp_Object val;

  NSTRACE ("[EmacsView writeSelectionToPasteboard:types:]");

  /* We only support NSPasteboardTypeString.  */
  if ([types containsObject:NSPasteboardTypeString] == NO) {
    return NO;
  }

  val = ns_get_local_selection (QPRIMARY, QUTF8_STRING);
  if (CONSP (val) && SYMBOLP (XCAR (val)))
    {
      val = XCDR (val);
      if (CONSP (val) && NILP (XCDR (val)))
        val = XCAR (val);
    }
  if (! STRINGP (val))
    return NO;

  typesDeclared = [NSArray arrayWithObject:NSPasteboardTypeString];
  [pb declareTypes:typesDeclared owner:nil];
  ns_string_to_pasteboard (pb, val);
  return YES;
}


/* setMini = YES means set from internal (gives a finder icon), NO means set nil
   (gives a miniaturized version of the window); currently we use the latter for
   frames whose active buffer doesn't correspond to any file
   (e.g., '*scratch*').  */
- (instancetype)setMiniwindowImage: (BOOL) setMini
{
  id image = [[self window] miniwindowImage];
  NSTRACE ("[EmacsView setMiniwindowImage:%d]", setMini);

  /* NOTE: under Cocoa miniwindowImage always returns nil, documentation
     about "AppleDockIconEnabled" notwithstanding, however the set message
     below has its effect nonetheless.  */
  if (image != emacsframe->output_data.ns->miniimage)
    {
      if (image && [image isKindOfClass: [EmacsImage class]])
        [image release];
      [[self window] setMiniwindowImage:
                       setMini ? emacsframe->output_data.ns->miniimage : nil];
    }

  return self;
}


- (int) fullscreenState
{
  return fs_state;
}

@end  /* EmacsView */



/* ==========================================================================

    EmacsWindow implementation

   ========================================================================== */

@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;
  int width, height;

  NSTRACE ("[EmacsWindow initWithEmacsFrame:fullscreen:screen:]");

  if (fullscreen)
    styleMask = NSWindowStyleMaskBorderless;
  else if (FRAME_UNDECORATED (f))
    {
      styleMask = NSWindowStyleMaskBorderless;
#ifdef NS_IMPL_COCOA
      styleMask |= NSWindowStyleMaskResizable;
#endif
    }
  else if (f->tooltip)
    styleMask = 0;
  else
    styleMask = (NSWindowStyleMaskTitled
		 | NSWindowStyleMaskResizable
		 | NSWindowStyleMaskMiniaturizable
		 | NSWindowStyleMaskClosable);

  last_drag_event = nil;

  width = FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, f->text_cols);
  height = FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, f->text_lines);

  self = [super initWithContentRect: NSMakeRect (0, 0, width, height)
                          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 = [NSColor colorWithUnsignedLong:NS_FACE_BACKGROUND
                                     (FACE_FROM_ID (f, DEFAULT_FACE_ID))];
      [self setBackgroundColor:col];
      if ([col alphaComponent] != (EmacsCGFloat) 1.0)
        [self setOpaque:NO];

      /* toolbar support */
      [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
{
  if (FRAME_UNDECORATED (f) || !FRAME_EXTERNAL_TOOL_BAR (f) || [self toolbar] != nil)
    return;

  EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);

#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
  /* If the view's layer isn't an EmacsLayer then we can't create the
     toolbar yet.  */
  if (! [[view layer] isKindOfClass:[EmacsLayer class]])
    return;
#endif

  EmacsToolbar *toolbar = [[EmacsToolbar alloc]
                            initForView:view
                            withIdentifier:[NSString stringWithFormat:@"%p", f]];

  [self setToolbar:toolbar];
  update_frame_tool_bar_1 (f, toolbar);

#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 setToolbar: nil];
  [[self toolbar] release];

  /* Also the last button press event .  */
  if (last_drag_event)
    [last_drag_event 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 accessibility 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 ([self respondsToSelector:@selector(setAccessibilitySubrole:)])
#endif
    /* Set the accessibility subroles.  */
    if (parentFrame)
      [self setAccessibilitySubrole:NSAccessibilityFloatingWindowSubrole];
    else
      [self setAccessibilitySubrole:NSAccessibilityStandardWindowSubrole];

#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  [ourView updateCollectionBehavior];
#endif

  /* Child frames are often used in ways that may mean they should
     "disappear" into the contents of the parent frame.  macOs's
     drop-shadows break this effect, so remove them on undecorated
     child frames.  */
  if (parentFrame && FRAME_UNDECORATED (ourFrame))
    [self setHasShadow:NO];
  else
    [self setHasShadow:YES];
#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.  */

- (void)orderFront:(id)sender
{
  NSTRACE ("[EmacsWindow orderFront:]");

  NSWindow *parent = [self parentWindow];
  if (parent)
    {
      [parent removeChildWindow:self];
      [parent addChildWindow:self ordered:NSWindowAbove];
    }
  else
    [super orderFront:sender];
}

- (void)makeKeyAndOrderFront:(id)sender
{
  NSTRACE ("[EmacsWindow makeKeyAndOrderFront:]");

  if ([self parentWindow])
    {
      [self orderFront:sender];
      [self makeKeyWindow];
    }
  else
    [super makeKeyAndOrderFront:sender];
}


#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.  */
static NSInteger
nswindow_orderedIndex_sort (id w1, id w2, void *c)
{
  NSInteger i1 = [w1 orderedIndex];
  NSInteger i2 = [w2 orderedIndex];

  if (i1 > i2)
    return NSOrderedAscending;
  if (i1 < i2)
    return NSOrderedDescending;

  return NSOrderedSame;
}

- (void)orderBack:(id)sender
{
  NSTRACE ("[EmacsWindow orderBack:]");

  NSWindow *parent = [self parentWindow];
  if (parent)
    {
      NSArray *children = [[parent childWindows]
                            sortedArrayUsingFunction:nswindow_orderedIndex_sort
                                              context:nil];
      [parent removeChildWindow:self];
      [parent addChildWindow:self ordered:NSWindowAbove];

      for (NSWindow *win in children)
        {
          if (win != self)
            {
              [parent removeChildWindow:win];
              [parent addChildWindow:win ordered:NSWindowAbove];
            }
        }
    }
  else
    [super orderBack:sender];
}

- (BOOL)restackWindow:(NSWindow *)win above:(BOOL)above
{
  NSTRACE ("[EmacsWindow restackWindow:above:]");

  /* If parent windows don't match we can't restack these frames
     without changing the parents.  */
  if ([self parentWindow] != [win parentWindow])
    return NO;
  else if (![self parentWindow])
    [self orderWindow:(above ? NSWindowAbove : NSWindowBelow)
           relativeTo:[win windowNumber]];
  else
    {
      NSInteger index;
      NSWindow *parent = [self parentWindow];
      NSMutableArray *children = [[[parent childWindows]
                                   sortedArrayUsingFunction:nswindow_orderedIndex_sort
                                                    context:nil]
                                   mutableCopy];
      [children removeObject:self];
      index = [children indexOfObject:win];
      [children insertObject:self atIndex:(above ? index+1 : index)];

      for (NSWindow *w in children)
        {
          [parent removeChildWindow:w];
          [parent addChildWindow:w ordered:NSWindowAbove];
        }
    }

  return YES;
}

#ifdef NS_IMPL_COCOA
- (id)accessibilityAttributeValue:(NSString *)attribute
{
  Lisp_Object str = Qnil;
  struct frame *f = SELECTED_FRAME ();
  struct buffer *curbuf = XBUFFER (XWINDOW (f->selected_window)->contents);

  NSTRACE ("[EmacsWindow accessibilityAttributeValue:]");

  if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
    return NSAccessibilityTextFieldRole;

  if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]
      && curbuf && ! NILP (BVAR (curbuf, mark_active)))
    {
      str = ns_get_local_selection (QPRIMARY, QUTF8_STRING);
    }
  else if (curbuf && [attribute isEqualToString:NSAccessibilityValueAttribute])
    {
      if (! NILP (BVAR (curbuf, mark_active)))
          str = ns_get_local_selection (QPRIMARY, QUTF8_STRING);

      if (NILP (str))
        {
          ptrdiff_t start_byte = BUF_BEGV_BYTE (curbuf);
          ptrdiff_t byte_range = BUF_ZV_BYTE (curbuf) - start_byte;
          ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf);

          if (! NILP (BVAR (curbuf, enable_multibyte_characters)))
            str = make_uninit_multibyte_string (range, byte_range);
          else
            str = make_uninit_string (range);
          /* To check: This returns emacs-utf-8, which is a superset of utf-8.
             Is this a problem?  */
          memcpy (SDATA (str), BYTE_POS_ADDR (start_byte), byte_range);
        }
    }


  if (! NILP (str))
    {
      if (CONSP (str) && SYMBOLP (XCAR (str)))
        {
          str = XCDR (str);
          if (CONSP (str) && NILP (XCDR (str)))
            str = XCAR (str);
        }
      if (STRINGP (str))
        {
          return [NSString stringWithLispString:str];
        }
    }

  return [super accessibilityAttributeValue:attribute];
}
#endif /* NS_IMPL_COCOA */

/* Constrain size and placement of a frame.

   By returning the original "frameRect", the frame is not
   constrained. This can lead to unwanted situations where, for
   example, the menu bar covers the frame.

   The default implementation (accessed using "super") constrains the
   frame to the visible area of SCREEN, minus the menu bar (if
   present) and the Dock.  Note that default implementation also calls
   windowWillResize, with the frame it thinks should have.  (This can
   make the frame exit maximized mode.)

   Note that this should work in situations where multiple monitors
   are present.  Common configurations are side-by-side monitors and a
   monitor on top of another (e.g. when a laptop is placed under a
   large screen).  */
- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
{
  NSTRACE ("[EmacsWindow constrainFrameRect:" NSTRACE_FMT_RECT " toScreen:]",
             NSTRACE_ARG_RECT (frameRect));

#ifdef NS_IMPL_COCOA
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
  // If separate spaces is on, it is like each screen is independent.  There is
  // no spanning of frames across screens.
  if (
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090
      [NSScreen respondsToSelector: @selector(screensHaveSeparateSpaces)] &&
#endif
      [NSScreen screensHaveSeparateSpaces])
    {
      NSTRACE_MSG ("Screens have separate spaces");
      frameRect = [super constrainFrameRect:frameRect toScreen:screen];
      NSTRACE_RETURN_RECT (frameRect);
      return frameRect;
    }
  else
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1090 */

    // Check that the proposed frameRect is visible in at least one
    // screen.  If it is not, ask the system to reposition it (only
    // for non-child windows).

    if (!FRAME_PARENT_FRAME (((EmacsView *)[self delegate])->emacsframe))
    {
      NSArray *screens = [NSScreen screens];
      NSUInteger nr_screens = [screens count];

      int i;
      BOOL frame_on_screen = NO;

      for (i = 0; i < nr_screens; ++i)
        {
          NSScreen *s = [screens objectAtIndex: i];
          NSRect scrRect = [s frame];

          if (NSIntersectsRect(frameRect, scrRect))
            {
              frame_on_screen = YES;
              break;
            }
        }

      if (!frame_on_screen)
        {
          NSTRACE_MSG ("Frame outside screens; constraining");
          frameRect = [super constrainFrameRect:frameRect toScreen:screen];
          NSTRACE_RETURN_RECT (frameRect);
          return frameRect;
        }
    }
#endif

  return constrain_frame_rect(frameRect,
                              [(EmacsView *)[self delegate] isFullscreen]);
}


- (void)performZoom:(id)sender
{
  NSTRACE ("[EmacsWindow performZoom:]");

  return [super performZoom:sender];
}

- (void)zoom:(id)sender
{
  NSTRACE ("[EmacsWindow zoom:]");

  ns_update_auto_hide_menu_bar();

  // Below are three zoom implementations.  In the final commit, the
  // idea is that the last should be included.

#if 0
  // Native zoom done using the standard zoom animation.  Size of the
  // resulting frame reduced to accommodate the Dock and, if present,
  // the menu-bar.
  [super zoom:sender];

#elif 0
  // Native zoom done using the standard zoom animation, plus an
  // explicit resize to cover the full screen, except the menu-bar and
  // dock, if present.
  [super zoom:sender];

  // After the native zoom, resize the resulting frame to fill the
  // entire screen, except the menu-bar.
  //
  // This works for all practical purposes.  (The only minor oddity is
  // when transiting from full-height frame to a maximized, the
  // animation reduces the height of the frame slightly (to the 4
  // pixels needed to accommodate the Doc) before it snaps back into
  // full height.  The user would need a very trained eye to spot
  // this.)
  NSScreen * screen = [self screen];
  if (screen != nil)
    {
      int fs_state = [(EmacsView *)[self delegate] fullscreenState];

      NSTRACE_FSTYPE ("fullscreenState", fs_state);

      NSRect sr = [screen frame];
      struct EmacsMargins margins
        = ns_screen_margins_ignoring_hidden_dock(screen);

      NSRect wr = [self frame];
      NSTRACE_RECT ("Rect after zoom", wr);

      NSRect newWr = wr;

      if (fs_state == FULLSCREEN_MAXIMIZED
          || fs_state == FULLSCREEN_HEIGHT)
        {
          newWr.origin.y = sr.origin.y + margins.bottom;
          newWr.size.height = sr.size.height - margins.top - margins.bottom;
        }

      if (fs_state == FULLSCREEN_MAXIMIZED
          || fs_state == FULLSCREEN_WIDTH)
        {
          newWr.origin.x = sr.origin.x + margins.left;
          newWr.size.width = sr.size.width - margins.right - margins.left;
        }

      if (newWr.size.width     != wr.size.width
          || newWr.size.height != wr.size.height
          || newWr.origin.x    != wr.origin.x
          || newWr.origin.y    != wr.origin.y)
        {
          NSTRACE_MSG ("New frame different");
          [self setFrame: newWr display: NO];
        }
    }
#else
  // Non-native zoom which is done instantaneously.  The resulting
  // frame covers the entire screen, except the menu-bar and dock, if
  // present.
  NSScreen * screen = [self screen];
  if (screen != nil)
    {
      NSRect sr = [screen frame];
      struct EmacsMargins margins
        = ns_screen_margins_ignoring_hidden_dock(screen);

      sr.size.height -= (margins.top + margins.bottom);
      sr.size.width  -= (margins.left + margins.right);
      sr.origin.x += margins.left;
      sr.origin.y += margins.bottom;

      sr = [[self delegate] windowWillUseStandardFrame:self
                                          defaultFrame:sr];
      [self setFrame: sr display: NO];
    }
#endif
}

- (void)setAppearance
{
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
  struct frame *f = ((EmacsView *)[self delegate])->emacsframe;
  NSAppearance *appearance = nil;

  NSTRACE ("[EmacsWindow setAppearance]");

#ifndef NSAppKitVersionNumber10_10
#define NSAppKitVersionNumber10_10 1343
#endif

  if (NSAppKitVersionNumber < NSAppKitVersionNumber10_10)
    return;

  if (FRAME_NS_APPEARANCE (f) == ns_appearance_vibrant_dark)
    appearance =
      [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark];
  else if (FRAME_NS_APPEARANCE (f) == ns_appearance_aqua)
    appearance =
      [NSAppearance appearanceNamed:NSAppearanceNameAqua];

  [self setAppearance:appearance];
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 */
}

- (void)setFrame:(NSRect)windowFrame
         display:(BOOL)displayViews
{
  NSTRACE ("[EmacsWindow setFrame:" NSTRACE_FMT_RECT " display:%d]",
           NSTRACE_ARG_RECT (windowFrame), displayViews);

  [super setFrame:windowFrame display:displayViews];
}

- (void)setFrame:(NSRect)windowFrame
         display:(BOOL)displayViews
         animate:(BOOL)performAnimation
{
  NSTRACE ("[EmacsWindow setFrame:" NSTRACE_FMT_RECT
           " display:%d performAnimation:%d]",
           NSTRACE_ARG_RECT (windowFrame), displayViews, performAnimation);

  [super setFrame:windowFrame display:displayViews animate:performAnimation];
}

- (void)setFrameTopLeftPoint:(NSPoint)point
{
  NSTRACE ("[EmacsWindow setFrameTopLeftPoint:" NSTRACE_FMT_POINT "]",
           NSTRACE_ARG_POINT (point));

  [super setFrameTopLeftPoint:point];
}

- (BOOL)canBecomeKeyWindow
{
  return !FRAME_NO_ACCEPT_FOCUS (((EmacsView *)[self delegate])->emacsframe);
}

- (BOOL)canBecomeMainWindow
  /* Required for fullscreen and undecorated windows.  */
{
  return YES;
}

- (void) setLastDragEvent: (NSEvent *) event
{
  if (last_drag_event)
    [last_drag_event release];
  last_drag_event = [event copy];
}

- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL) is_local
{
  return drag_op;
}

- (void) draggedImage: (NSImage *) image
	      endedAt: (NSPoint) screen_point
	    operation: (NSDragOperation) operation
{
  selected_op = operation;
}

- (void) draggedImage: (NSImage *) dragged_image
	      movedTo: (NSPoint) screen_point
{
  NSPoint mouse_loc;
#ifdef NS_IMPL_COCOA
  NSInteger window_number;
  NSWindow *w;
#endif

  mouse_loc = [NSEvent mouseLocation];

#ifdef NS_IMPL_COCOA
  if (dnd_mode != RETURN_FRAME_NEVER)
    {
      window_number = [NSWindow windowNumberAtPoint: mouse_loc
			belowWindowWithWindowNumber: 0];
      w = [NSApp windowWithWindowNumber: window_number];

      if (!w || w != self)
	dnd_mode = RETURN_FRAME_NOW;

      if (dnd_mode != RETURN_FRAME_NOW
	  || ![[w delegate] isKindOfClass: [EmacsView class]]
	  || ((EmacsView *) [w delegate])->emacsframe->tooltip)
	goto out;

      dnd_return_frame = ((EmacsView *) [w delegate])->emacsframe;

      /* FIXME: there must be a better way to leave the event loop.  */
      [NSException raise: @""
		  format: @"Must return DND frame"];
    }

 out:
#endif

  if (dnd_move_tooltip_with_frame)
    ns_move_tooltip_to_mouse_location (mouse_loc);
}

- (BOOL) mustNotDropOn: (NSView *) receiver
{
  return ([receiver window] == self
	  ? !dnd_allow_same_frame : NO);
}

- (NSDragOperation) beginDrag: (NSDragOperation) op
		forPasteboard: (NSPasteboard *) pasteboard
		     withMode: (enum ns_return_frame_mode) mode
		returnFrameTo: (struct frame **) frame_return
		 prohibitSame: (BOOL) prohibit_same_frame
		followTooltip: (BOOL) follow_tooltip
{
  NSImage *image;
#ifdef NS_IMPL_COCOA
  NSInteger window_number;
  NSWindow *w;
#endif
  drag_op = op;
  selected_op = NSDragOperationNone;
  image = [[NSImage alloc] initWithSize: NSMakeSize (1.0, 1.0)];
  dnd_mode = mode;
  dnd_return_frame = NULL;
  dnd_allow_same_frame = !prohibit_same_frame;
  dnd_move_tooltip_with_frame = follow_tooltip;

  /* Now draw transparency onto the image.  */
  [image lockFocus];
  [[NSColor colorWithUnsignedLong: 0] set];
  NSRectFillUsingOperation (NSMakeRect (0, 0, 1, 1),
			    NSCompositingOperationCopy);
  [image unlockFocus];

  block_input ();
#ifdef NS_IMPL_COCOA
  if (mode == RETURN_FRAME_NOW)
    {
      window_number = [NSWindow windowNumberAtPoint: [NSEvent mouseLocation]
			belowWindowWithWindowNumber: 0];
      w = [NSApp windowWithWindowNumber: window_number];

      if (w && [[w delegate] isKindOfClass: [EmacsView class]]
	  && !((EmacsView *) [w delegate])->emacsframe->tooltip)
	{
	  *frame_return = ((EmacsView *) [w delegate])->emacsframe;
	  [image release];
	  unblock_input ();

	  return NSDragOperationNone;
	}
    }

  @try
    {
#endif
      if (last_drag_event)
	[self dragImage: image
		     at: NSMakePoint (0, 0)
		 offset: NSMakeSize (0, 0)
		  event: last_drag_event
	     pasteboard: pasteboard
		 source: self
	      slideBack: NO];
#ifdef NS_IMPL_COCOA
    }
  @catch (NSException *e)
    {
      /* Ignore.  This is probably the wrong way to leave the
	 drag-and-drop run loop.  */
    }
#endif
  unblock_input ();

  /* The drop happened, so delete the tooltip.  */
  if (follow_tooltip)
    Fx_hide_tip ();

  /* Assume all buttons have been released since the drag-and-drop
     operation is now over.  */
  if (!dnd_return_frame)
    x_display_list->grabbed = 0;

  [image release];

  *frame_return = dnd_return_frame;
  return selected_op;
}

@end /* EmacsWindow */


/* ==========================================================================

    EmacsScroller implementation

   ========================================================================== */


@implementation EmacsScroller

/* for repeat button push */
#define SCROLL_BAR_FIRST_DELAY 0.5
#define SCROLL_BAR_CONTINUOUS_DELAY (1.0 / 15)

+ (CGFloat) scrollerWidth
{
  /* TODO: if we want to allow variable widths, this is the place to do it,
           however neither GNUstep nor Cocoa support it very well.  */
  CGFloat r;
#if defined (NS_IMPL_COCOA) \
  && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
  if ([NSScroller respondsToSelector:
                    @selector(scrollerWidthForControlSize:scrollerStyle:)])
#endif
    r = [NSScroller scrollerWidthForControlSize: NSControlSizeRegular
                                  scrollerStyle: NSScrollerStyleLegacy];
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
  else
#endif
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 \
  || defined (NS_IMPL_GNUSTEP)
    r = [NSScroller scrollerWidth];
#endif
  return r;
}

- (instancetype)initFrame: (NSRect )r window: (Lisp_Object)nwin
{
  NSTRACE ("[EmacsScroller initFrame: window:]");

  if (r.size.width > r.size.height)
      horizontal = YES;
  else
      horizontal = NO;

  [super initWithFrame: r/*NSMakeRect (0, 0, 0, 0)*/];
  [self setContinuous: YES];
  [self setEnabled: YES];

  /* Ensure auto resizing of scrollbars occurs within the emacs frame's view
     locked against the top and bottom edges, and right edge on macOS, where
     scrollers are on right.  */
#ifdef NS_IMPL_GNUSTEP
  [self setAutoresizingMask: NSViewMaxXMargin | NSViewHeightSizable];
#else
  [self setAutoresizingMask: NSViewMinXMargin | NSViewHeightSizable];
#endif

  window = XWINDOW (nwin);
  condemned = NO;
  if (horizontal)
    pixel_length = NSWidth (r);
  else
    pixel_length = NSHeight (r);
  if (pixel_length == 0) pixel_length = 1;
  min_portion = 20 / pixel_length;

  frame = XFRAME (window->frame);
  if (FRAME_LIVE_P (frame))
    {
      int i;
      EmacsView *view = FRAME_NS_VIEW (frame);
      NSView *sview = [[view window] contentView];
      NSArray *subs = [sview subviews];

      /* Disable optimization stopping redraw of other scrollbars.  */
      view->scrollbarsNeedingUpdate = 0;
      for (i =[subs count]-1; i >= 0; i--)
        if ([[subs objectAtIndex: i] isKindOfClass: [EmacsScroller class]])
          view->scrollbarsNeedingUpdate++;
      [sview addSubview: self];
    }

  /* [self setFrame: r]; */

  return self;
}


- (void)setFrame: (NSRect)newRect
{
  NSTRACE ("[EmacsScroller setFrame:]");

  /* block_input (); */
  if (horizontal)
    pixel_length = NSWidth (newRect);
  else
    pixel_length = NSHeight (newRect);
  if (pixel_length == 0) pixel_length = 1;
  min_portion = 20 / pixel_length;
  [super setFrame: newRect];
  /* unblock_input (); */
}


- (void)dealloc
{
  NSTRACE ("[EmacsScroller dealloc]");
  if (window)
    {
      if (horizontal)
        wset_horizontal_scroll_bar (window, Qnil);
      else
        wset_vertical_scroll_bar (window, Qnil);
    }
  window = 0;
  [super dealloc];
}


- (instancetype)condemn
{
  NSTRACE ("[EmacsScroller condemn]");
  condemned =YES;
  return self;
}


- (instancetype)reprieve
{
  NSTRACE ("[EmacsScroller reprieve]");
  condemned =NO;
  return self;
}


-(bool)judge
{
  NSTRACE ("[EmacsScroller judge]");
  bool ret = condemned;
  if (condemned)
    {
      EmacsView *view;
      block_input ();
      /* Ensure other scrollbar updates after deletion.  */
      view = (EmacsView *)FRAME_NS_VIEW (frame);
      if (view != nil)
        view->scrollbarsNeedingUpdate++;
      if (window)
        {
          if (horizontal)
            wset_horizontal_scroll_bar (window, Qnil);
          else
            wset_vertical_scroll_bar (window, Qnil);
        }
      window = 0;
      [self removeFromSuperview];
      [self release];
      unblock_input ();
    }
  return ret;
}

- (void) mark
{
  if (window)
    {
      Lisp_Object win;
      XSETWINDOW (win, window);
      mark_object (win);
    }
}


- (void)resetCursorRects
{
  NSRect visible = [self visibleRect];
  NSTRACE ("[EmacsScroller resetCursorRects]");

  if (!NSIsEmptyRect (visible))
    [self addCursorRect: visible cursor: [NSCursor arrowCursor]];

#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
  if ([[NSCursor arrowCursor] respondsToSelector:
                                @selector(setOnMouseEntered)])
#endif
    [[NSCursor arrowCursor] setOnMouseEntered: YES];
#endif
}


- (int) checkSamePosition: (int) position portion: (int) portion
                    whole: (int) whole
{
  return em_position ==position && em_portion ==portion && em_whole ==whole
    && portion != whole; /* Needed for resizing empty buffer.  */
}


- (instancetype)setPosition: (int)position portion: (int)portion whole: (int)whole
{
  NSTRACE ("[EmacsScroller setPosition:portion:whole:]");

  em_position = position;
  em_portion = portion;
  em_whole = whole;

  if (portion >= whole)
    {
#ifdef NS_IMPL_COCOA
      [self setKnobProportion: 1.0];
      [self setDoubleValue: 1.0];
#else
      [self setFloatValue: 0.0 knobProportion: 1.0];
#endif
    }
  else
    {
      float pos;
      CGFloat por;
      portion = max ((float)whole*min_portion/pixel_length, portion);
      pos = (float)position / (whole - portion);
      por = (CGFloat)portion/whole;
#ifdef NS_IMPL_COCOA
      [self setKnobProportion: por];
      [self setDoubleValue: pos];
#else
      [self setFloatValue: pos knobProportion: por];
#endif
    }

  return self;
}

/* Set up emacs_event.  */
- (void) sendScrollEventAtLoc: (float)loc fromEvent: (NSEvent *)e
{
  Lisp_Object win;

  NSTRACE ("[EmacsScroller sendScrollEventAtLoc:fromEvent:]");

  if (!emacs_event)
    return;

  emacs_event->part = last_hit_part;
  emacs_event->code = 0;
  emacs_event->modifiers = EV_MODIFIERS (e) | down_modifier;
  XSETWINDOW (win, window);
  emacs_event->frame_or_window = win;
  emacs_event->timestamp = EV_TIMESTAMP (e);
  emacs_event->arg = Qnil;

  if (horizontal)
    {
      emacs_event->kind = HORIZONTAL_SCROLL_BAR_CLICK_EVENT;
      XSETINT (emacs_event->x, em_whole * loc / pixel_length);
      XSETINT (emacs_event->y, em_whole);
    }
  else
    {
      emacs_event->kind = SCROLL_BAR_CLICK_EVENT;
      XSETINT (emacs_event->x, loc);
      XSETINT (emacs_event->y, pixel_length-20);
    }

  if (q_event_ptr)
    {
      n_emacs_events_pending++;
      kbd_buffer_store_event_hold (emacs_event, q_event_ptr);
    }
  else
    hold_event (emacs_event);
  EVENT_INIT (*emacs_event);
  ns_send_appdefined (-1);
}


/* Called manually through timer to implement repeated button action
   with hold-down.  */
- (instancetype)repeatScroll: (NSTimer *)scrollEntry
{
  NSEvent *e = [[self window] currentEvent];
  NSPoint p =  [[self window] mouseLocationOutsideOfEventStream];
  BOOL inKnob = [self testPart: p] == NSScrollerKnob;

  NSTRACE ("[EmacsScroller repeatScroll:]");

  /* Clear timer if need be.  */
  if (inKnob || [scroll_repeat_entry timeInterval] == SCROLL_BAR_FIRST_DELAY)
    {
        [scroll_repeat_entry invalidate];
        [scroll_repeat_entry release];
        scroll_repeat_entry = nil;

        if (inKnob)
          return self;

        scroll_repeat_entry
	  = [[NSTimer scheduledTimerWithTimeInterval:
			SCROLL_BAR_CONTINUOUS_DELAY
                                            target: self
                                          selector: @selector (repeatScroll:)
                                          userInfo: 0
                                           repeats: YES]
	      retain];
    }

  [self sendScrollEventAtLoc: 0 fromEvent: e];
  return self;
}


/* Asynchronous mouse tracking for scroller.  This allows us to dispatch
   mouseDragged events without going into a modal loop.  */
- (void)mouseDown: (NSEvent *)e
{
  NSRect sr, kr;
  /* hitPart is only updated AFTER event is passed on.  */
  NSScrollerPart part = [self testPart: [e locationInWindow]];
  CGFloat loc, kloc, pos UNINIT;
  int edge = 0;

  NSTRACE ("[EmacsScroller mouseDown:]");

  switch (part)
    {
    case NSScrollerDecrementPage:
      last_hit_part = horizontal ? scroll_bar_before_handle : scroll_bar_above_handle; break;
    case NSScrollerIncrementPage:
      last_hit_part = horizontal ? scroll_bar_after_handle : scroll_bar_below_handle; break;
#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 1070
    case NSScrollerDecrementLine:
      last_hit_part = horizontal ? scroll_bar_left_arrow : scroll_bar_up_arrow; break;
    case NSScrollerIncrementLine:
      last_hit_part = horizontal ? scroll_bar_right_arrow : scroll_bar_down_arrow; break;
#endif
    case NSScrollerKnob:
      last_hit_part = horizontal ? scroll_bar_horizontal_handle : scroll_bar_handle; break;
    case NSScrollerKnobSlot:  /* GNUstep-only */
      last_hit_part = scroll_bar_move_ratio; break;
    default:  /* NSScrollerNoPart? */
      fprintf (stderr, "EmacsScroller-mouseDown: unexpected part %ld\n",
               (long) part);
      return;
    }

  if (part == NSScrollerKnob || part == NSScrollerKnobSlot)
    {
      /* handle, or on GNUstep possibly slot */
      NSEvent *fake_event;
      int length;

      /* compute float loc in slot and mouse offset on knob */
      sr = [self convertRect: [self rectForPart: NSScrollerKnobSlot]
                      toView: nil];
      if (horizontal)
        {
          length = NSWidth (sr);
          loc = ([e locationInWindow].x - NSMinX (sr));
        }
      else
        {
          length = NSHeight (sr);
          loc = length - ([e locationInWindow].y - NSMinY (sr));
        }

      if (loc <= 0.0)
        {
          loc = 0.0;
          edge = -1;
        }
      else if (loc >= length)
        {
          loc = length;
          edge = 1;
        }

      if (edge)
        kloc = 0.5 * edge;
      else
        {
          kr = [self convertRect: [self rectForPart: NSScrollerKnob]
                          toView: nil];
          if (horizontal)
            kloc = ([e locationInWindow].x - NSMinX (kr));
          else
            kloc = NSHeight (kr) - ([e locationInWindow].y - NSMinY (kr));
        }
      last_mouse_offset = kloc;

      /* if knob, tell emacs a location offset by knob pos
         (to indicate top of handle) */
      if (part == NSScrollerKnob)
        pos = (loc - last_mouse_offset);
      else
        /* else this is a slot click on GNUstep: go straight there */
        pos = loc;

      /* If there are buttons in the scroller area, we need to
         recalculate pos as emacs expects the scroller slot to take up
         the entire available length.  */
      if (length != pixel_length)
        pos = pos * pixel_length / length;

      /* send a fake mouse-up to super to preempt modal -trackKnob: mode */
      fake_event = [NSEvent mouseEventWithType: NSEventTypeLeftMouseUp
                                      location: [e locationInWindow]
                                 modifierFlags: [e modifierFlags]
                                     timestamp: [e timestamp]
                                  windowNumber: [e windowNumber]
                                       context: nil
                                   eventNumber: [e eventNumber]
                                    clickCount: [e clickCount]
                                      pressure: [e pressure]];
      [super mouseUp: fake_event];
    }
  else
    {
      pos = 0; /* ignored */

      /* Set a timer to repeat, as we can't let superclass do this modally.  */
      scroll_repeat_entry
	= [[NSTimer scheduledTimerWithTimeInterval: SCROLL_BAR_FIRST_DELAY
                                            target: self
                                          selector: @selector (repeatScroll:)
                                          userInfo: 0
                                           repeats: YES]
	    retain];
    }

  if (part != NSScrollerKnob)
    [self sendScrollEventAtLoc: pos fromEvent: e];
}


/* Called as we manually track scroller drags, rather than superclass.  */
- (void)mouseDragged: (NSEvent *)e
{
    NSRect sr;
    double loc, pos;
    int length;

    NSTRACE ("[EmacsScroller mouseDragged:]");

      sr = [self convertRect: [self rectForPart: NSScrollerKnobSlot]
                      toView: nil];

      if (horizontal)
        {
          length = NSWidth (sr);
          loc = ([e locationInWindow].x - NSMinX (sr));
        }
      else
        {
          length = NSHeight (sr);
          loc = length - ([e locationInWindow].y - NSMinY (sr));
        }

      if (loc <= 0.0)
        {
          loc = 0.0;
        }
      else if (loc >= length + last_mouse_offset)
        {
          loc = length + last_mouse_offset;
        }

      pos = (loc - last_mouse_offset);

      /* If there are buttons in the scroller area, we need to
         recalculate pos as emacs expects the scroller slot to take up
         the entire available length.  */
      if (length != pixel_length)
        pos = pos * pixel_length / length;

      [self sendScrollEventAtLoc: pos fromEvent: e];
}


- (void)mouseUp: (NSEvent *)e
{
  NSTRACE ("[EmacsScroller mouseUp:]");

  if (scroll_repeat_entry)
    {
      [scroll_repeat_entry invalidate];
      [scroll_repeat_entry release];
      scroll_repeat_entry = nil;
    }
  last_hit_part = scroll_bar_above_handle;
}


/* Treat scrollwheel events in the bar as though they were in the main window.  */
- (void) scrollWheel: (NSEvent *)theEvent
{
  NSTRACE ("[EmacsScroller scrollWheel:]");

  EmacsView *view = (EmacsView *)FRAME_NS_VIEW (frame);
  [view mouseDown: theEvent];
}

@end  /* EmacsScroller */


#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400

/* ==========================================================================

   A class to handle the screen buffer.

   ========================================================================== */

@implementation EmacsLayer


/* An IOSurface is a pixel buffer that is efficiently copied to VRAM
   for display.  In order to use an IOSurface we must first lock it,
   write to it, then unlock it.  At this point it is transferred to
   VRAM and if we modify it during this transfer we may see corruption
   of the output.  To avoid this problem we can check if the surface
   is "in use", and if it is then avoid using it.  Unfortunately to
   avoid writing to a surface that's in use, but still maintain the
   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 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.  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.  */

#define CACHE_MAX_SIZE 2

- (id) initWithColorSpace: (CGColorSpaceRef)cs
{
  NSTRACE ("[EmacsLayer initWithColorSpace:]");

  self = [super init];
  if (self)
    {
      cache = [[NSMutableArray arrayWithCapacity:CACHE_MAX_SIZE] retain];
      [self setColorSpace:cs];
    }
  else
    {
      return nil;
    }

  return self;
}


- (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.  */
  if (cs)
    colorSpace = cs;
  else
    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
}


- (void) dealloc
{
  [self releaseSurfaces];
  [cache release];

  [super dealloc];
}


- (void) releaseSurfaces
{
  [self setContents:nil];
  [self releaseContext];

  if (currentSurface)
    {
      CFRelease (currentSurface);
      currentSurface = nil;
    }

  if (cache)
    {
      for (id object in cache)
        CFRelease ((IOSurfaceRef)object);

      [cache removeAllObjects];
    }
}


/* Check whether the current bounds match the IOSurfaces we are using.
   If they do return YES, otherwise NO.  */
- (BOOL) checkDimensions
{
  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.  */
- (CGContextRef) getContext
{
  CGFloat scale = [self contentsScale];

  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer getContext]");
  NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (currentSurface ? 1 : 0));

  if (![self checkDimensions])
    [self releaseSurfaces];

  if (!context)
    {
      IOSurfaceRef surface = NULL;
      int width = NSWidth ([self bounds]) * scale;
      int height = NSHeight ([self bounds]) * scale;

      for (id object in cache)
        {
          if (!IOSurfaceIsInUse ((IOSurfaceRef)object))
            {
              surface = (IOSurfaceRef)object;
              [cache removeObject:object];
              break;
            }
        }

      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']});
        }

      if (!surface)
        {
          NSLog (@"Failed to create IOSurface for frame %@", [self delegate]);
          return nil;
        }

      IOReturn lockStatus = IOSurfaceLock (surface, 0, nil);
      if (lockStatus != kIOReturnSuccess)
        NSLog (@"Failed to lock surface: %x", lockStatus);

      [self copyContentsTo:surface];

      currentSurface = surface;

      context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface),
                                       IOSurfaceGetWidth (currentSurface),
                                       IOSurfaceGetHeight (currentSurface),
                                       8,
                                       IOSurfaceGetBytesPerRow (currentSurface),
                                       colorSpace,
                                       (kCGImageAlphaPremultipliedFirst
                                        | kCGBitmapByteOrder32Host));

      if (!context)
        {
          NSLog (@"Failed to create context for frame %@", [self delegate]);
          IOSurfaceUnlock (currentSurface, 0, nil);
          CFRelease (currentSurface);
          currentSurface = nil;
          return nil;
        }

      CGContextTranslateCTM(context, 0, IOSurfaceGetHeight (currentSurface));
      CGContextScaleCTM(context, scale, -scale);
    }

  return context;
}


/* Releases the CGGraphicsContext and unlocks the associated
   IOSurface, so it will be sent to VRAM.  */
- (void) releaseContext
{
  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer releaseContext]");

  if (!context)
    return;

  CGContextRelease (context);
  context = NULL;

  IOReturn lockStatus = IOSurfaceUnlock (currentSurface, 0, nil);
  if (lockStatus != kIOReturnSuccess)
    NSLog (@"Failed to unlock surface: %x", lockStatus);
}


- (void) display
{
  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];
    }
}


/* Copy the contents of lastSurface to DESTINATION.  This is required
   every time we want to use an IOSurface as its contents are probably
   blanks (if it's new), or stale.  */
- (void) copyContentsTo: (IOSurfaceRef) destination
{
  IOReturn lockStatus;
  IOSurfaceRef source = (IOSurfaceRef)[self contents];
  void *sourceData, *destinationData;
  int numBytes = IOSurfaceGetAllocSize (destination);

  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer copyContentsTo:]");

  if (!source || source == destination)
    return;

  lockStatus = IOSurfaceLock (source, kIOSurfaceLockReadOnly, nil);
  if (lockStatus != kIOReturnSuccess)
    NSLog (@"Failed to lock source surface: %x", lockStatus);

  sourceData = IOSurfaceGetBaseAddress (source);
  destinationData = IOSurfaceGetBaseAddress (destination);

  /* Since every IOSurface should have the exact same settings, a
     memcpy seems like the fastest way to copy the data from one to
     the other.  */
  memcpy (destinationData, sourceData, numBytes);

  lockStatus = IOSurfaceUnlock (source, kIOSurfaceLockReadOnly, nil);
  if (lockStatus != kIOReturnSuccess)
    NSLog (@"Failed to unlock source surface: %x", lockStatus);
}

#undef CACHE_MAX_SIZE

@end /* EmacsLayer */


#endif /* NS_IMPL_COCOA */


#ifdef NS_IMPL_GNUSTEP
/* Dummy class to get rid of startup warnings.  */
@implementation EmacsDocument

@end
#endif


/* ==========================================================================

   Font-related functions; these used to be in nsfaces.m

   ========================================================================== */


static Lisp_Object
ns_new_font (struct frame *f, Lisp_Object font_object, int fontset)
{
  /* --------------------------------------------------------------------------
     External (hook)
     -------------------------------------------------------------------------- */
  struct font *font = XFONT_OBJECT (font_object);
  EmacsView *view = FRAME_NS_VIEW (f);
  int font_ascent, font_descent;

  if (fontset < 0)
    fontset = fontset_from_font (font_object);
  FRAME_FONTSET (f) = fontset;

  if (FRAME_FONT (f) == font)
    /* This font is already set in frame F.  There's nothing more to
       do.  */
    return font_object;

  FRAME_FONT (f) = font;

  FRAME_BASELINE_OFFSET (f) = font->baseline_offset;
  FRAME_COLUMN_WIDTH (f) = font->average_width;
  get_font_ascent_descent (font, &font_ascent, &font_descent);
  FRAME_LINE_HEIGHT (f) = font_ascent + font_descent;

  /* Compute the scroll bar width in character columns.  */
  if (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0)
    {
      int wid = FRAME_COLUMN_WIDTH (f);
      FRAME_CONFIG_SCROLL_BAR_COLS (f)
	= (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) + wid - 1) / wid;
    }
  else
    {
      int wid = FRAME_COLUMN_WIDTH (f);
      FRAME_CONFIG_SCROLL_BAR_COLS (f) = (14 + wid - 1) / wid;
    }

  /* Compute the scroll bar height in character lines.  */
  if (FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) > 0)
    {
      int height = FRAME_LINE_HEIGHT (f);
      FRAME_CONFIG_SCROLL_BAR_LINES (f)
	= (FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) + height - 1) / height;
    }
  else
    {
      int height = FRAME_LINE_HEIGHT (f);
      FRAME_CONFIG_SCROLL_BAR_LINES (f) = (14 + height - 1) / height;
    }

  /* Now make the frame display the given font.  */
  if (FRAME_NS_WINDOW (f) != 0 && ! [view isFullscreen])
    adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f),
		       FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), 3,
		       false, Qfont);

  return font_object;
}


/* XLFD: -foundry-family-weight-slant-swidth-adstyle-pxlsz-ptSz-resx-resy-spc-avgWidth-rgstry-encoding */
/* Note: ns_font_to_xlfd and ns_fontname_to_xlfd no longer needed, removed
         in 1.43.  */

const char *
ns_xlfd_to_fontname (const char *xlfd)
/* --------------------------------------------------------------------------
    Convert an X font name (XLFD) to an NS font name.
    Only family is used.
    The string returned is temporarily allocated.
   -------------------------------------------------------------------------- */
{
  char *name = xmalloc (180);
  int i, len;
  const char *ret;

  if (!strncmp (xlfd, "--", 2))
    sscanf (xlfd, "--%*[^-]-%179[^-]-", name);
  else
    sscanf (xlfd, "-%*[^-]-%179[^-]-", name);

  /* stopgap for malformed XLFD input */
  if (!*name)
    strcpy (name, "Monaco");

  /* undo hack in ns_fontname_to_xlfd, converting '$' to '-', '_' to ' '
     also uppercase after '-' or ' ' */
  name[0] = c_toupper (name[0]);
  for (len =strlen (name), i =0; i<len; i++)
    {
      if (name[i] == '$')
        {
          name[i] = '-';
          if (i+1<len)
            name[i+1] = c_toupper (name[i+1]);
        }
      else if (name[i] == '_')
        {
          name[i] = ' ';
          if (i+1<len)
            name[i+1] = c_toupper (name[i+1]);
        }
    }
  /* fprintf (stderr, "converted '%s' to '%s'\n",xlfd,name); */
  ret = [[NSString stringWithUTF8String: name] UTF8String];
  xfree (name);
  return ret;
}

void
mark_nsterm (void)
{
  NSTRACE ("mark_nsterm");
  Lisp_Object tail, frame;
  FOR_EACH_FRAME (tail, frame)
    {
      struct frame *f = XFRAME (frame);
      if (FRAME_NS_P (f))
	{
	  NSArray *subviews = [[FRAME_NS_VIEW (f) superview] subviews];
	  for (int i = [subviews count] - 1; i >= 0; --i)
	    {
	      id scroller = [subviews objectAtIndex: i];
	      if ([scroller isKindOfClass: [EmacsScroller class]])
                  [scroller mark];
	    }
	}
    }
}

void
syms_of_nsterm (void)
{
  NSTRACE ("syms_of_nsterm");

  ns_antialias_threshold = 10.0;
  PDUMPER_REMEMBER_SCALAR (ns_antialias_threshold);

  /* From 23+ we need to tell emacs what modifiers there are.  */
  DEFSYM (Qmodifier_value, "modifier-value");
  DEFSYM (Qalt, "alt");
  DEFSYM (Qhyper, "hyper");
  DEFSYM (Qmeta, "meta");
  DEFSYM (Qsuper, "super");
  DEFSYM (Qcontrol, "control");
  DEFSYM (QUTF8_STRING, "UTF8_STRING");

  DEFSYM (Qfile, "file");
  DEFSYM (Qurl, "url");

  DEFSYM (Qns_drag_operation_copy, "ns-drag-operation-copy");
  DEFSYM (Qns_drag_operation_link, "ns-drag-operation-link");
  DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
  DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");

  Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
  Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
  Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
  Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
  Fput (Qcontrol, Qmodifier_value, make_fixnum (ctrl_modifier));

 DEFVAR_LISP ("ns-input-font", ns_input_font,
   doc: /* The font specified in the last NS event. */);
 ns_input_font = Qnil;

 DEFVAR_LISP ("ns-input-fontsize", ns_input_fontsize,
   doc: /* The fontsize specified in the last NS event. */);
 ns_input_fontsize = Qnil;

 DEFVAR_LISP ("ns-input-line", ns_input_line,
   doc: /* The line specified in the last NS event. */);
 ns_input_line = Qnil;

 DEFVAR_LISP ("ns-input-spi-name", ns_input_spi_name,
   doc: /* The service name specified in the last NS event. */);
 ns_input_spi_name = Qnil;

 DEFVAR_LISP ("ns-input-spi-arg", ns_input_spi_arg,
   doc: /* The service argument specified in the last NS event. */);
  ns_input_spi_arg = Qnil;

  DEFVAR_LISP ("ns-input-file", ns_input_file,
    doc: /* The file specified in the last NS event.  */);
  ns_input_file = Qnil;

  DEFVAR_LISP ("ns-working-text", ns_working_text,
    doc: /* String for visualizing working composition sequence.  */);
  ns_working_text = Qnil;

  DEFVAR_LISP ("ns-alternate-modifier", ns_alternate_modifier,
    doc: /* This variable describes the behavior of the alternate or option key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_alternate_modifier = Qmeta;

  DEFVAR_LISP ("ns-right-alternate-modifier", ns_right_alternate_modifier,
    doc: /* This variable describes the behavior of the right alternate or option key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.
It can also be `left' to use the value of `ns-alternate-modifier' instead.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_right_alternate_modifier = Qleft;

  DEFVAR_LISP ("ns-command-modifier", ns_command_modifier,
    doc: /* This variable describes the behavior of the command key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_command_modifier = Qsuper;

  DEFVAR_LISP ("ns-right-command-modifier", ns_right_command_modifier,
    doc: /* This variable describes the behavior of the right command key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.
It can also be `left' to use the value of `ns-command-modifier' instead.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_right_command_modifier = Qleft;

  DEFVAR_LISP ("ns-control-modifier", ns_control_modifier,
    doc: /* This variable describes the behavior of the control key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_control_modifier = Qcontrol;

  DEFVAR_LISP ("ns-right-control-modifier", ns_right_control_modifier,
    doc: /* This variable describes the behavior of the right control key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.
It can also be `left' to use the value of `ns-control-modifier' instead.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_right_control_modifier = Qleft;

  DEFVAR_LISP ("ns-function-modifier", ns_function_modifier,
    doc: /* This variable describes the behavior of the function (fn) key.
Either SYMBOL, describing the behavior for any event,
or (:ordinary SYMBOL :function SYMBOL :mouse SYMBOL), describing behavior
separately for ordinary keys, function keys, and mouse events.

Each SYMBOL is `control', `meta', `alt', `super', `hyper' or `none'.
If `none', the key is ignored by Emacs and retains its standard meaning.  */);
  ns_function_modifier = Qnone;

  DEFVAR_LISP ("ns-antialias-text", ns_antialias_text,
    doc: /* Non-nil (the default) means to render text antialiased.  */);
  ns_antialias_text = Qt;

  DEFVAR_LISP ("ns-use-thin-smoothing", ns_use_thin_smoothing,
    doc: /* Non-nil turns on a font smoothing method that produces thinner strokes.  */);
  ns_use_thin_smoothing = Qnil;

  DEFVAR_LISP ("ns-confirm-quit", ns_confirm_quit,
    doc: /* Whether to confirm application quit using dialog.  */);
  ns_confirm_quit = Qnil;

  DEFVAR_LISP ("ns-auto-hide-menu-bar", ns_auto_hide_menu_bar,
               doc: /* Non-nil means that the menu bar is hidden, but appears when the mouse is near.
Only works on Mac OS X.  */);
  ns_auto_hide_menu_bar = Qnil;

  DEFVAR_BOOL ("ns-use-native-fullscreen", ns_use_native_fullscreen,
     doc: /* Non-nil means to use native fullscreen on Mac OS X 10.7 and later.
Nil means use fullscreen the old (< 10.7) way.  The old way works better with
multiple monitors, but lacks tool bar.  This variable is ignored on
Mac OS X < 10.7.  Default is t.  */);
  ns_use_native_fullscreen = YES;
  ns_last_use_native_fullscreen = ns_use_native_fullscreen;

  DEFVAR_BOOL ("ns-use-fullscreen-animation", ns_use_fullscreen_animation,
     doc: /* Non-nil means use animation on non-native fullscreen.
For native fullscreen, this does nothing.
Default is nil.  */);
  ns_use_fullscreen_animation = NO;

  DEFVAR_BOOL ("ns-use-srgb-colorspace", ns_use_srgb_colorspace,
     doc: /* Non-nil means to use sRGB colorspace on Mac OS X 10.7 and later.
Note that this does not apply to images.
This variable is ignored on Mac OS X < 10.7 and GNUstep.  */);
  ns_use_srgb_colorspace = YES;

  DEFVAR_BOOL ("ns-use-mwheel-acceleration",
               ns_use_mwheel_acceleration,
     doc: /* Non-nil means use macOS's standard mouse wheel acceleration.
This variable is ignored on macOS < 10.7 and GNUstep.  Default is t.  */);
  ns_use_mwheel_acceleration = YES;

  DEFVAR_LISP ("ns-mwheel-line-height", ns_mwheel_line_height,
               doc: /* The number of pixels touchpad scrolling considers one line.
Nil or a non-number means use the default frame line height.
This variable is ignored on macOS < 10.7 and GNUstep.  Default is nil.  */);
  ns_mwheel_line_height = Qnil;

  DEFVAR_BOOL ("ns-use-mwheel-momentum", ns_use_mwheel_momentum,
               doc: /* Non-nil means mouse wheel scrolling uses momentum.
This variable is ignored on macOS < 10.7 and GNUstep.  Default is t.  */);
  ns_use_mwheel_momentum = YES;

  /* TODO: Move to common code.  */
  DEFVAR_LISP ("x-toolkit-scroll-bars", Vx_toolkit_scroll_bars,
	       doc: /* SKIP: real doc in xterm.c.  */);
  Vx_toolkit_scroll_bars = Qt;

  DEFVAR_BOOL ("x-use-underline-position-properties",
	       x_use_underline_position_properties,
     doc: /* SKIP: real doc in xterm.c.  */);
  x_use_underline_position_properties = 0;
  DEFSYM (Qx_use_underline_position_properties,
	  "x-use-underline-position-properties");

  DEFVAR_BOOL ("x-underline-at-descent-line",
	       x_underline_at_descent_line,
     doc: /* SKIP: real doc in xterm.c.  */);
  x_underline_at_descent_line = 0;

  DEFSYM (Qx_underline_at_descent_line, "x-underline-at-descent-line");

  DEFVAR_LISP ("ns-scroll-event-delta-factor", Vns_scroll_event_delta_factor,
	       doc: /* A factor to apply to pixel deltas reported in scroll events.
 This is only effective for pixel deltas generated from touch pads or
 mice with smooth scrolling capability.  */);
  Vns_scroll_event_delta_factor = make_float (1.0);

  DEFVAR_LISP ("ns-drag-motion-function", Vns_drag_motion_function,
    doc: /* Function called when another program drags items over Emacs.

It is called with three arguments FRAME, X, and Y, whenever the user
moves the mouse over an Emacs frame as part of a drag-and-drop
operation.  FRAME is the frame the mouse is on top of, and X and Y are
the frame-relative positions of the mouse in the X and Y axes
respectively.  */);
  Vns_drag_motion_function = Qns_handle_drag_motion;

  /* Tell Emacs about this window system.  */
  Fprovide (Qns, Qnil);

  DEFSYM (Qcocoa, "cocoa");
  DEFSYM (Qgnustep, "gnustep");
  DEFSYM (QCordinary, ":ordinary");
  DEFSYM (QCfunction, ":function");
  DEFSYM (QCmouse, ":mouse");
  DEFSYM (Qcondensed, "condensed");
  DEFSYM (Qreverse_italic, "reverse-italic");
  DEFSYM (Qexpanded, "expanded");
  DEFSYM (Qns_in_echo_area, "ns-in-echo-area");

#ifdef NS_IMPL_COCOA
  Fprovide (Qcocoa, Qnil);
  syms_of_macfont ();
#else
  Fprovide (Qgnustep, Qnil);
  syms_of_nsfont ();
#endif

  last_known_monitors = Qnil;
  staticpro (&last_known_monitors);
}
