send_status_direct (ctrl_t ctrl, const char *keyword, const char *args)
{
  assuan_context_t ctx = ctrl->server_local->assuan_ctx;

  if (strchr (args, '\n'))
    log_error ("error: LF detected in status line - not sending\n");
  else
    assuan_write_status (ctx, keyword, args);
}


send_status_info (ctrl_t ctrl, const char *keyword, ...)
{
  va_list arg_ptr;
  const unsigned char *value;
  size_t valuelen;
  char buf[950], *p;
  size_t n;
  assuan_context_t ctx = ctrl->server_local->assuan_ctx;

  va_start (arg_ptr, keyword);

  p = buf;
  n = 0;
  while ( (value = va_arg (arg_ptr, const unsigned char *)) )
    {
      valuelen = va_arg (arg_ptr, size_t);
      if (!valuelen)
        continue; /* empty buffer */
      if (n)
        {
          *p++ = ' ';
          n++;
        }
      for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
        {
          if (*value < ' ' || *value == '+')
            {
              sprintf (p, "%%%02X", *value);
              p += 3;
            }
          else if (*value == ' ')
            *p++ = '+';
          else
            *p++ = *value;
        }
    }
  *p = 0;
  assuan_write_status (ctx, keyword, buf);

  va_end (arg_ptr);
}

scd: Avoid double-free on error condition in scd

* scd/command.c (cmd_readkey): avoid double-free of cert

--

When ksba_cert_new() fails, cert will be double-freed.

Debian-Bug-Id: 773471

Original patch changed by wk to do the free only at leave.


cmd_passwd (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  char *chvnostr;
  unsigned int flags = 0;

  if (has_option (line, "--reset"))
    flags |= APP_CHANGE_FLAG_RESET;
  if (has_option (line, "--nullpin"))
    flags |= APP_CHANGE_FLAG_NULLPIN;

  if ( IS_LOCKED (ctrl) )
    return gpg_error (GPG_ERR_LOCKED);

  line = skip_options (line);

  if (!*line)
    return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given");
  chvnostr = line;
  while (*line && !spacep (line))
    line++;
  *line = 0;

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  if (!ctrl->app_ctx)
    return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);

  chvnostr = xtrystrdup (chvnostr);
  if (!chvnostr)
    return out_of_core ();
  rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx);
  if (rc)
    log_error ("command passwd failed: %s\n", gpg_strerror (rc));
  xfree (chvnostr);

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


cmd_writecert (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  char *certid;
  unsigned char *certdata;
  size_t certdatalen;

  if ( IS_LOCKED (ctrl) )
    return gpg_error (GPG_ERR_LOCKED);

  line = skip_options (line);

  if (!*line)
    return set_error (GPG_ERR_ASS_PARAMETER, "no certid given");
  certid = line;
  while (*line && !spacep (line))
    line++;
  *line = 0;

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  if (!ctrl->app_ctx)
    return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);

  certid = xtrystrdup (certid);
  if (!certid)
    return out_of_core ();

  /* Now get the actual keydata. */
  rc = assuan_inquire (ctx, "CERTDATA",
                       &certdata, &certdatalen, MAXLEN_CERTDATA);
  if (rc)
    {
      xfree (certid);
      return rc;
    }

  /* Write the certificate to the card. */
  rc = app_writecert (ctrl->app_ctx, ctrl, certid,
                      pin_cb, ctx, certdata, certdatalen);
  xfree (certid);
  xfree (certdata);

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


cmd_restart (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);

  (void)line;

  if (ctrl->app_ctx)
    {
      release_application (ctrl->app_ctx);
      ctrl->app_ctx = NULL;
    }
  if (locked_session && ctrl->server_local == locked_session)
    {
      locked_session = NULL;
      log_info ("implicitly unlocking due to RESTART\n");
    }
  return 0;
}


hex_to_buffer (const char *string, size_t *r_length)
{
  unsigned char *buffer;
  const char *s;
  size_t n;

  buffer = xtrymalloc (strlen (string)+1);
  if (!buffer)
    return NULL;
  for (s=string, n=0; *s; s++)
    {
      if (spacep (s) || *s == ':')
        continue;
      if (hexdigitp (s) && hexdigitp (s+1))
        {
          buffer[n++] = xtoi_2 (s);
          s++;
        }
      else
        break;
    }
  *r_length = n;
  return buffer;
}


update_card_removed (int vrdr, int value)
{
  struct server_local_s *sl;

  if (vrdr == -1)
    return;

  for (sl=session_list; sl; sl = sl->next_session)
    if (sl->ctrl_backlink
        && sl->ctrl_backlink->server_local->vreader_idx == vrdr)
      {
        sl->card_removed = value;
      }
  /* Let the card application layer know about the removal.  */
  if (value)
    application_notify_card_reset (vreader_slot (vrdr));
}


do_reset (ctrl_t ctrl, int send_reset)
{
  int vrdr = ctrl->server_local->vreader_idx;
  int slot;
  int err;

  if (!(vrdr == -1 || (vrdr >= 0 && vrdr < DIM(vreader_table))))
    BUG ();

  /* If there is an active application, release it.  Tell all other
     sessions using the same application to release the
     application.  */
  if (ctrl->app_ctx)
    {
      release_application (ctrl->app_ctx);
      ctrl->app_ctx = NULL;
      if (send_reset)
        {
          struct server_local_s *sl;

          for (sl=session_list; sl; sl = sl->next_session)
            if (sl->ctrl_backlink
                && sl->ctrl_backlink->server_local->vreader_idx == vrdr)
              {
                sl->app_ctx_marked_for_release = 1;
              }
        }
    }

  /* If we want a real reset for the card, send the reset APDU and
     tell the application layer about it.  */
  slot = vreader_slot (vrdr);
  if (slot != -1 && send_reset && !IS_LOCKED (ctrl) )
    {
      application_notify_card_reset (slot);
      switch (apdu_reset (slot))
        {
        case 0:
          break;
        case SW_HOST_NO_CARD:
        case SW_HOST_CARD_INACTIVE:
          break;
        default:
	  apdu_close_reader (slot);
          vreader_table[vrdr].slot = slot = -1;
          break;
        }
    }

  /* If we hold a lock, unlock now. */
  if (locked_session && ctrl->server_local == locked_session)
    {
      locked_session = NULL;
      log_info ("implicitly unlocking due to RESET\n");
    }

  /* Reset the card removed flag for the current reader.  We need to
     take the lock here so that the ticker thread won't concurrently
     try to update the file.  Calling update_reader_status_file is
     required to get hold of the new status of the card in the vreader
     table.  */
  err = npth_mutex_lock (&status_file_update_lock);
  if (err)
    {
      log_error ("failed to acquire status_file_update lock\n");
      ctrl->server_local->vreader_idx = -1;
      return;
    }
  update_reader_status_file (0);  /* Update slot status table.  */
  update_card_removed (vrdr, 0);  /* Clear card_removed flag.  */
  err = npth_mutex_unlock (&status_file_update_lock);
  if (err)
    log_error ("failed to release status_file_update lock: %s\n",
	       strerror (err));

  /* Do this last, so that the update_card_removed above does its job.  */
  ctrl->server_local->vreader_idx = -1;
}


option_handler (assuan_context_t ctx, const char *key, const char *value)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);

  if (!strcmp (key, "event-signal"))
    {
      /* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
      if (!*value)
        return gpg_error (GPG_ERR_ASS_PARAMETER);
      ctrl->server_local->event_signal = strtoul (value, NULL, 16);
#else
      int i = *value? atoi (value) : -1;
      if (i < 0)
        return gpg_error (GPG_ERR_ASS_PARAMETER);
      ctrl->server_local->event_signal = i;
#endif
    }

 return 0;
}


cmd_setattr (assuan_context_t ctx, char *orig_line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  char *keyword;
  int keywordlen;
  size_t nbytes;
  char *line, *linebuf;

  if ( IS_LOCKED (ctrl) )
    return gpg_error (GPG_ERR_LOCKED);

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  /* We need to use a copy of LINE, because PIN_CB uses the same
     context and thus reuses the Assuan provided LINE. */
  line = linebuf = xtrystrdup (orig_line);
  if (!line)
    return out_of_core ();

  keyword = line;
  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
    ;
  if (*line)
      *line++ = 0;
  while (spacep (line))
    line++;
  nbytes = percent_plus_unescape_inplace (line, 0);

  rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx,
                    (const unsigned char*)line, nbytes);
  xfree (linebuf);

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


cmd_getinfo (assuan_context_t ctx, char *line)
{
  int rc = 0;

  if (!strcmp (line, "version"))
    {
      const char *s = VERSION;
      rc = assuan_send_data (ctx, s, strlen (s));
    }
  else if (!strcmp (line, "pid"))
    {
      char numbuf[50];

      snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
      rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
    }
  else if (!strcmp (line, "socket_name"))
    {
      const char *s = scd_get_socket_name ();

      if (s)
        rc = assuan_send_data (ctx, s, strlen (s));
      else
        rc = gpg_error (GPG_ERR_NO_DATA);
    }
  else if (!strcmp (line, "status"))
    {
      ctrl_t ctrl = assuan_get_pointer (ctx);
      int vrdr = ctrl->server_local->vreader_idx;
      char flag = 'r';

      if (!ctrl->server_local->card_removed && vrdr != -1)
	{
	  struct vreader_s *vr;

	  if (!(vrdr >= 0 && vrdr < DIM(vreader_table)))
	    BUG ();

	  vr = &vreader_table[vrdr];
	  if (vr->valid && vr->any && (vr->status & 1))
	    flag = 'u';
	}
      rc = assuan_send_data (ctx, &flag, 1);
    }
  else if (!strcmp (line, "reader_list"))
    {
#ifdef HAVE_LIBUSB
      char *s = ccid_get_reader_list ();
#else
      char *s = NULL;
#endif

      if (s)
        rc = assuan_send_data (ctx, s, strlen (s));
      else
        rc = gpg_error (GPG_ERR_NO_DATA);
      xfree (s);
    }
  else if (!strcmp (line, "deny_admin"))
    rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0;
  else if (!strcmp (line, "app_list"))
    {
      char *s = get_supported_applications ();
      if (s)
        rc = assuan_send_data (ctx, s, strlen (s));
      else
        rc = 0;
      xfree (s);
    }
  else
    rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
  return rc;
}


pin_cb (void *opaque, const char *info, char **retstr)
{
  assuan_context_t ctx = opaque;
  char *command;
  int rc;
  unsigned char *value;
  size_t valuelen;

  if (!retstr)
    {
      /* We prompt for pinpad entry.  To make sure that the popup has
         been show we use an inquire and not just a status message.
         We ignore any value returned.  */
      if (info)
        {
          log_debug ("prompting for pinpad entry '%s'\n", info);
          rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info);
          if (rc < 0)
            return gpg_error (gpg_err_code_from_errno (errno));
          rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
          xfree (command);
        }
      else
        {
          log_debug ("dismiss pinpad entry prompt\n");
          rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT",
                               &value, &valuelen, MAXLEN_PIN);
        }
      if (!rc)
        xfree (value);
      return rc;
    }

  *retstr = NULL;
  log_debug ("asking for PIN '%s'\n", info);

  rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
  if (rc < 0)
    return gpg_error (gpg_err_code_from_errno (errno));

  /* Fixme: Write an inquire function which returns the result in
     secure memory and check all further handling of the PIN. */
  rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
  xfree (command);
  if (rc)
    return rc;

  if (!valuelen || value[valuelen-1])
    {
      /* We require that the returned value is an UTF-8 string */
      xfree (value);
      return gpg_error (GPG_ERR_INV_RESPONSE);
    }
  *retstr = (char*)value;
  return 0;
}


cmd_setdata (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int append;
  int n, i, off;
  char *p;
  unsigned char *buf;

  append = (ctrl->in_data.value && has_option (line, "--append"));

  line = skip_options (line);

  if (locked_session && locked_session != ctrl->server_local)
    return gpg_error (GPG_ERR_LOCKED);

  /* Parse the hexstring. */
  for (p=line,n=0; hexdigitp (p); p++, n++)
    ;
  if (*p)
    return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
  if (!n)
    return set_error (GPG_ERR_ASS_PARAMETER, "no data given");
  if ((n&1))
    return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
  n /= 2;
  if (append)
    {
      if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA)
        return set_error (GPG_ERR_TOO_LARGE,
                          "limit on total size of data reached");
      buf = xtrymalloc (ctrl->in_data.valuelen + n);
    }
  else
    buf = xtrymalloc (n);
  if (!buf)
    return out_of_core ();

  if (append)
    {
      memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen);
      off = ctrl->in_data.valuelen;
    }
  else
    off = 0;
  for (p=line, i=0; i < n; p += 2, i++)
    buf[off+i] = xtoi_2 (p);

  xfree (ctrl->in_data.value);
  ctrl->in_data.value = buf;
  ctrl->in_data.valuelen = off+n;
  return 0;
}


initialize_module_command (void)
{
  static int initialized;
  int err;

  if (!initialized)
    {
      err = npth_mutex_init (&status_file_update_lock, NULL);
      if (!err)
        initialized = 1;
    }
}


cmd_pksign (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  unsigned char *outdata;
  size_t outdatalen;
  char *keyidstr;
  int hash_algo;

  if (has_option (line, "--hash=rmd160"))
    hash_algo = GCRY_MD_RMD160;
  else if (has_option (line, "--hash=sha1"))
    hash_algo = GCRY_MD_SHA1;
  else if (has_option (line, "--hash=sha224"))
    hash_algo = GCRY_MD_SHA224;
  else if (has_option (line, "--hash=sha256"))
    hash_algo = GCRY_MD_SHA256;
  else if (has_option (line, "--hash=sha384"))
    hash_algo = GCRY_MD_SHA384;
  else if (has_option (line, "--hash=sha512"))
    hash_algo = GCRY_MD_SHA512;
  else if (has_option (line, "--hash=md5"))
    hash_algo = GCRY_MD_MD5;
  else if (!strstr (line, "--"))
    hash_algo = GCRY_MD_SHA1;
  else
    return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");

  line = skip_options (line);

  if ( IS_LOCKED (ctrl) )
    return gpg_error (GPG_ERR_LOCKED);

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  /* We have to use a copy of the key ID because the function may use
     the pin_cb which in turn uses the assuan line buffer and thus
     overwriting the original line with the keyid */
  keyidstr = xtrystrdup (line);
  if (!keyidstr)
    return out_of_core ();

  rc = app_sign (ctrl->app_ctx,
                 keyidstr, hash_algo,
                 pin_cb, ctx,
                 ctrl->in_data.value, ctrl->in_data.valuelen,
                 &outdata, &outdatalen);

  xfree (keyidstr);
  if (rc)
    {
      log_error ("app_sign failed: %s\n", gpg_strerror (rc));
    }
  else
    {
      rc = assuan_send_data (ctx, outdata, outdatalen);
      xfree (outdata);
      if (rc)
        return rc; /* that is already an assuan error code */
    }

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


cmd_genkey (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  char *keyno;
  int force;
  const char *s;
  time_t timestamp;

  if ( IS_LOCKED (ctrl) )
    return gpg_error (GPG_ERR_LOCKED);

  force = has_option (line, "--force");

  if ((s=has_option_name (line, "--timestamp")))
    {
      if (*s != '=')
        return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
      timestamp = isotime2epoch (s+1);
      if (timestamp < 1)
        return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
    }
  else
    timestamp = 0;

  line = skip_options (line);
  if (!*line)
    return set_error (GPG_ERR_ASS_PARAMETER, "no key number given");
  keyno = line;
  while (*line && !spacep (line))
    line++;
  *line = 0;

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  if (!ctrl->app_ctx)
    return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);

  keyno = xtrystrdup (keyno);
  if (!keyno)
    return out_of_core ();
  rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0,
                   timestamp, pin_cb, ctx);
  xfree (keyno);

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


scd_command_handler (ctrl_t ctrl, int fd)
{
  int rc;
  assuan_context_t ctx = NULL;
  int stopme;

  rc = assuan_new (&ctx);
  if (rc)
    {
      log_error ("failed to allocate assuan context: %s\n",
                 gpg_strerror (rc));
      scd_exit (2);
    }

  if (fd == -1)
    {
      assuan_fd_t filedes[2];

      filedes[0] = assuan_fdopen (0);
      filedes[1] = assuan_fdopen (1);
      rc = assuan_init_pipe_server (ctx, filedes);
    }
  else
    {
      rc = assuan_init_socket_server (ctx, INT2FD(fd),
				      ASSUAN_SOCKET_SERVER_ACCEPTED);
    }
  if (rc)
    {
      log_error ("failed to initialize the server: %s\n",
                 gpg_strerror(rc));
      scd_exit (2);
    }
  rc = register_commands (ctx);
  if (rc)
    {
      log_error ("failed to register commands with Assuan: %s\n",
                 gpg_strerror(rc));
      scd_exit (2);
    }
  assuan_set_pointer (ctx, ctrl);

  /* Allocate and initialize the server object.  Put it into the list
     of active sessions. */
  ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
  ctrl->server_local->next_session = session_list;
  session_list = ctrl->server_local;
  ctrl->server_local->ctrl_backlink = ctrl;
  ctrl->server_local->assuan_ctx = ctx;
  ctrl->server_local->vreader_idx = -1;

  /* We open the reader right at startup so that the ticker is able to
     update the status file. */
  if (ctrl->server_local->vreader_idx == -1)
    {
      ctrl->server_local->vreader_idx = get_current_reader ();
    }

  /* Command processing loop. */
  for (;;)
    {
      rc = assuan_accept (ctx);
      if (rc == -1)
        {
          break;
        }
      else if (rc)
        {
          log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
          break;
        }

      rc = assuan_process (ctx);
      if (rc)
        {
          log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
          continue;
        }
    }

  /* Cleanup.  We don't send an explicit reset to the card.  */
  do_reset (ctrl, 0);

  /* Release the server object.  */
  if (session_list == ctrl->server_local)
    session_list = ctrl->server_local->next_session;
  else
    {
      struct server_local_s *sl;

      for (sl=session_list; sl->next_session; sl = sl->next_session)
        if (sl->next_session == ctrl->server_local)
          break;
      if (!sl->next_session)
          BUG ();
      sl->next_session = ctrl->server_local->next_session;
    }
  stopme = ctrl->server_local->stopme;
  xfree (ctrl->server_local);
  ctrl->server_local = NULL;

  /* Release the Assuan context.  */
  assuan_release (ctx);

  if (stopme)
    scd_exit (0);

  /* If there are no more sessions return true.  */
  return !session_list;
}


cmd_pkauth (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  unsigned char *outdata;
  size_t outdatalen;
  char *keyidstr;

  if ( IS_LOCKED (ctrl) )
    return gpg_error (GPG_ERR_LOCKED);

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  if (!ctrl->app_ctx)
    return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);

 /* We have to use a copy of the key ID because the function may use
     the pin_cb which in turn uses the assuan line buffer and thus
     overwriting the original line with the keyid */
  keyidstr = xtrystrdup (line);
  if (!keyidstr)
    return out_of_core ();

  rc = app_auth (ctrl->app_ctx,
                 keyidstr,
                 pin_cb, ctx,
                 ctrl->in_data.value, ctrl->in_data.valuelen,
                 &outdata, &outdatalen);
  xfree (keyidstr);
  if (rc)
    {
      log_error ("app_auth failed: %s\n", gpg_strerror (rc));
    }
  else
    {
      rc = assuan_send_data (ctx, outdata, outdatalen);
      xfree (outdata);
      if (rc)
        return rc; /* that is already an assuan error code */
    }

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


cmd_disconnect (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);

  (void)line;

  ctrl->server_local->disconnect_allowed = 1;
  return 0;
}


vreader_slot (int vrdr)
{
  if (vrdr == -1 || !(vrdr >= 0 && vrdr < DIM(vreader_table)))
    return -1;
  if (!vreader_table [vrdr].valid)
    return -1;
  return vreader_table[vrdr].slot;
}


cmd_getattr (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  const char *keyword;

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  keyword = line;
  for (; *line && !spacep (line); line++)
    ;
  if (*line)
      *line++ = 0;

  /* (We ignore any garbage for now.) */

  /* FIXME: Applications should not return sensitive data if the card
     is locked.  */
  rc = app_getattr (ctrl->app_ctx, ctrl, keyword);

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


cmd_serialno (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc = 0;
  char *serial;
  time_t stamp;
  int retries = 0;

  /* Clear the remove flag so that the open_card is able to reread it.  */
 retry:
  if (ctrl->server_local->card_removed)
    {
      if ( IS_LOCKED (ctrl) )
        return gpg_error (GPG_ERR_LOCKED);
      do_reset (ctrl, 1);
    }

  if ((rc = open_card (ctrl, *line? line:NULL)))
    {
      /* In case of an inactive card, retry once.  */
      if (gpg_err_code (rc) == GPG_ERR_CARD_RESET && retries++ < 1)
        goto retry;
      return rc;
    }

  rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp);
  if (rc)
    return rc;

  rc = print_assuan_status (ctx, "SERIALNO", "%s %lu",
                            serial, (unsigned long)stamp);
  xfree (serial);
  return rc;
}


send_client_notifications (void)
{
  struct {
    pid_t pid;
#ifdef HAVE_W32_SYSTEM
    HANDLE handle;
#else
    int signo;
#endif
  } killed[50];
  int killidx = 0;
  int kidx;
  struct server_local_s *sl;

  for (sl=session_list; sl; sl = sl->next_session)
    {
      if (sl->event_signal && sl->assuan_ctx)
        {
          pid_t pid = assuan_get_pid (sl->assuan_ctx);
#ifdef HAVE_W32_SYSTEM
          HANDLE handle = (void *)sl->event_signal;

          for (kidx=0; kidx < killidx; kidx++)
            if (killed[kidx].pid == pid
                && killed[kidx].handle == handle)
              break;
          if (kidx < killidx)
            log_info ("event %lx (%p) already triggered for client %d\n",
                      sl->event_signal, handle, (int)pid);
          else
            {
              log_info ("triggering event %lx (%p) for client %d\n",
                        sl->event_signal, handle, (int)pid);
              if (!SetEvent (handle))
                log_error ("SetEvent(%lx) failed: %s\n",
                           sl->event_signal, w32_strerror (-1));
              if (killidx < DIM (killed))
                {
                  killed[killidx].pid = pid;
                  killed[killidx].handle = handle;
                  killidx++;
                }
            }
#else /*!HAVE_W32_SYSTEM*/
          int signo = sl->event_signal;

          if (pid != (pid_t)(-1) && pid && signo > 0)
            {
              for (kidx=0; kidx < killidx; kidx++)
                if (killed[kidx].pid == pid
                    && killed[kidx].signo == signo)
                  break;
              if (kidx < killidx)
                log_info ("signal %d already sent to client %d\n",
                          signo, (int)pid);
              else
                {
                  log_info ("sending signal %d to client %d\n",
                            signo, (int)pid);
                  kill (pid, signo);
                  if (killidx < DIM (killed))
                    {
                      killed[killidx].pid = pid;
                      killed[killidx].signo = signo;
                      killidx++;
                    }
                }
            }
#endif /*!HAVE_W32_SYSTEM*/
        }
    }
}


skip_options (char *line)
{
  while ( *line == '-' && line[1] == '-' )
    {
      while (*line && !spacep (line))
        line++;
      while (spacep (line))
        line++;
    }
  return line;
}


cmd_readcert (assuan_context_t ctx, char *line)
{
  ctrl_t ctrl = assuan_get_pointer (ctx);
  int rc;
  unsigned char *cert;
  size_t ncert;

  if ((rc = open_card (ctrl, NULL)))
    return rc;

  line = xstrdup (line); /* Need a copy of the line. */
  rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
  if (rc)
    log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
  xfree (line);
  line = NULL;
  if (!rc)
    {
      rc = assuan_send_data (ctx, cert, ncert);
      xfree (cert);
      if (rc)
        return rc;
    }

  TEST_CARD_REMOVAL (ctrl, rc);
  return rc;
}


redrawcmd(void)
{
    int save_in_echowindow = in_echowindow;

    if (cmd_silent)
	return;

    // when 'incsearch' is set there may be no command line while redrawing
    if (ccline.cmdbuff == NULL)
    {
	windgoto(cmdline_row, 0);
	msg_clr_eos();
	return;
    }

    // Do not put this in the message window.
    in_echowindow = FALSE;

    sb_text_restart_cmdline();
    msg_start();
    redrawcmdprompt();

    // Don't use more prompt, truncate the cmdline if it doesn't fit.
    msg_no_more = TRUE;
    draw_cmdline(0, ccline.cmdlen);
    msg_clr_eos();
    msg_no_more = FALSE;

    set_cmdspos_cursor();
    if (extra_char != NUL)
	putcmdline(extra_char, extra_char_shift);

    /*
     * An emsg() before may have set msg_scroll. This is used in normal mode,
     * in cmdline mode we can reset them now.
     */
    msg_scroll = FALSE;		// next message overwrites cmdline

    // Typing ':' at the more prompt may set skip_redraw.  We don't want this
    // in cmdline mode
    skip_redraw = FALSE;

    in_echowindow = save_in_echowindow;
}

patch 9.0.0490: using freed memory with cmdwin and BufEnter autocmd

Problem:    Using freed memory with cmdwin and BufEnter autocmd.
Solution:   Make sure pointer to b_p_iminsert is still valid.


open_cmdwin(void)
{
    bufref_T		old_curbuf;
    win_T		*old_curwin = curwin;
    bufref_T		bufref;
    win_T		*wp;
    int			i;
    linenr_T		lnum;
    int			histtype;
    garray_T		winsizes;
    int			save_restart_edit = restart_edit;
    int			save_State = State;
    int			save_exmode = exmode_active;
#ifdef FEAT_RIGHTLEFT
    int			save_cmdmsg_rl = cmdmsg_rl;
#endif
#ifdef FEAT_FOLDING
    int			save_KeyTyped;
#endif

    // Can't do this when text or buffer is locked.
    // Can't do this recursively.  Can't do it when typing a password.
    if (text_or_buf_locked()
	    || cmdwin_type != 0
# if defined(FEAT_CRYPT) || defined(FEAT_EVAL)
	    || cmdline_star > 0
# endif
	    )
    {
	beep_flush();
	return K_IGNORE;
    }
    set_bufref(&old_curbuf, curbuf);

    // Save current window sizes.
    win_size_save(&winsizes);

    // When using completion in Insert mode with <C-R>=<C-F> one can open the
    // command line window, but we don't want the popup menu then.
    pum_undisplay();

    // don't use a new tab page
    cmdmod.cmod_tab = 0;
    cmdmod.cmod_flags |= CMOD_NOSWAPFILE;

    // Create a window for the command-line buffer.
    if (win_split((int)p_cwh, WSP_BOT) == FAIL)
    {
	beep_flush();
	ga_clear(&winsizes);
	return K_IGNORE;
    }
    // Don't let quitting the More prompt make this fail.
    got_int = FALSE;

    // Set "cmdwin_type" before any autocommands may mess things up.
    cmdwin_type = get_cmdline_type();

    // Create the command-line buffer empty.
    if (do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL) == FAIL)
    {
	// Some autocommand messed it up?
	win_close(curwin, TRUE);
	ga_clear(&winsizes);
	cmdwin_type = 0;
	return Ctrl_C;
    }

    apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf);
    (void)setfname(curbuf, (char_u *)_("[Command Line]"), NULL, TRUE);
    apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf);
    set_option_value_give_err((char_u *)"bt",
					    0L, (char_u *)"nofile", OPT_LOCAL);
    curbuf->b_p_ma = TRUE;
#ifdef FEAT_FOLDING
    curwin->w_p_fen = FALSE;
#endif
# ifdef FEAT_RIGHTLEFT
    curwin->w_p_rl = cmdmsg_rl;
    cmdmsg_rl = FALSE;
# endif
    RESET_BINDING(curwin);

    // Don't allow switching to another buffer.
    ++curbuf_lock;

    // Showing the prompt may have set need_wait_return, reset it.
    need_wait_return = FALSE;

    histtype = hist_char2type(cmdwin_type);
    if (histtype == HIST_CMD || histtype == HIST_DEBUG)
    {
	if (p_wc == TAB)
	{
	    add_map((char_u *)"<buffer> <Tab> <C-X><C-V>", MODE_INSERT, TRUE);
	    add_map((char_u *)"<buffer> <Tab> a<C-X><C-V>", MODE_NORMAL, TRUE);
	}
	set_option_value_give_err((char_u *)"ft",
					       0L, (char_u *)"vim", OPT_LOCAL);
    }
    --curbuf_lock;

    // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
    // sets 'textwidth' to 78).
    curbuf->b_p_tw = 0;

    // Fill the buffer with the history.
    init_history();
    if (get_hislen() > 0)
    {
	i = *get_hisidx(histtype);
	if (i >= 0)
	{
	    lnum = 0;
	    do
	    {
		if (++i == get_hislen())
		    i = 0;
		if (get_histentry(histtype)[i].hisstr != NULL)
		    ml_append(lnum++, get_histentry(histtype)[i].hisstr,
							   (colnr_T)0, FALSE);
	    }
	    while (i != *get_hisidx(histtype));
	}
    }

    // Replace the empty last line with the current command-line and put the
    // cursor there.
    ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, TRUE);
    curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
    curwin->w_cursor.col = ccline.cmdpos;
    changed_line_abv_curs();
    invalidate_botline();
    redraw_later(UPD_SOME_VALID);

    // No Ex mode here!
    exmode_active = 0;

    State = MODE_NORMAL;
    setmouse();

    // Reset here so it can be set by a CmdWinEnter autocommand.
    cmdwin_result = 0;

    // Trigger CmdwinEnter autocommands.
    trigger_cmd_autocmd(cmdwin_type, EVENT_CMDWINENTER);
    if (restart_edit != 0)	// autocmd with ":startinsert"
	stuffcharReadbuff(K_NOP);

    i = RedrawingDisabled;
    RedrawingDisabled = 0;

    /*
     * Call the main loop until <CR> or CTRL-C is typed.
     */
    main_loop(TRUE, FALSE);

    RedrawingDisabled = i;

# ifdef FEAT_FOLDING
    save_KeyTyped = KeyTyped;
# endif

    // Trigger CmdwinLeave autocommands.
    trigger_cmd_autocmd(cmdwin_type, EVENT_CMDWINLEAVE);

# ifdef FEAT_FOLDING
    // Restore KeyTyped in case it is modified by autocommands
    KeyTyped = save_KeyTyped;
# endif

    cmdwin_type = 0;
    exmode_active = save_exmode;

    // Safety check: The old window or buffer was deleted: It's a bug when
    // this happens!
    if (!win_valid(old_curwin) || !bufref_valid(&old_curbuf))
    {
	cmdwin_result = Ctrl_C;
	emsg(_(e_active_window_or_buffer_deleted));
    }
    else
    {
# if defined(FEAT_EVAL)
	// autocmds may abort script processing
	if (aborting() && cmdwin_result != K_IGNORE)
	    cmdwin_result = Ctrl_C;
# endif
	// Set the new command line from the cmdline buffer.
	vim_free(ccline.cmdbuff);
	if (cmdwin_result == K_XF1 || cmdwin_result == K_XF2) // :qa[!] typed
	{
	    char *p = (cmdwin_result == K_XF2) ? "qa" : "qa!";

	    if (histtype == HIST_CMD)
	    {
		// Execute the command directly.
		ccline.cmdbuff = vim_strsave((char_u *)p);
		cmdwin_result = CAR;
	    }
	    else
	    {
		// First need to cancel what we were doing.
		ccline.cmdbuff = NULL;
		stuffcharReadbuff(':');
		stuffReadbuff((char_u *)p);
		stuffcharReadbuff(CAR);
	    }
	}
	else if (cmdwin_result == K_XF2)	// :qa typed
	{
	    ccline.cmdbuff = vim_strsave((char_u *)"qa");
	    cmdwin_result = CAR;
	}
	else if (cmdwin_result == Ctrl_C)
	{
	    // :q or :close, don't execute any command
	    // and don't modify the cmd window.
	    ccline.cmdbuff = NULL;
	}
	else
	    ccline.cmdbuff = vim_strsave(ml_get_curline());
	if (ccline.cmdbuff == NULL)
	{
	    ccline.cmdbuff = vim_strsave((char_u *)"");
	    ccline.cmdlen = 0;
	    ccline.cmdbufflen = 1;
	    ccline.cmdpos = 0;
	    cmdwin_result = Ctrl_C;
	}
	else
	{
	    ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
	    ccline.cmdbufflen = ccline.cmdlen + 1;
	    ccline.cmdpos = curwin->w_cursor.col;
	    if (ccline.cmdpos > ccline.cmdlen)
		ccline.cmdpos = ccline.cmdlen;
	    if (cmdwin_result == K_IGNORE)
	    {
		set_cmdspos_cursor();
		redrawcmd();
	    }
	}

# ifdef FEAT_CONCEAL
	// Avoid command-line window first character being concealed.
	curwin->w_p_cole = 0;
# endif
	// First go back to the original window.
	wp = curwin;
	set_bufref(&bufref, curbuf);

	skip_win_fix_cursor = TRUE;
	win_goto(old_curwin);

	// win_goto() may trigger an autocommand that already closes the
	// cmdline window.
	if (win_valid(wp) && wp != curwin)
	    win_close(wp, TRUE);

	// win_close() may have already wiped the buffer when 'bh' is
	// set to 'wipe', autocommands may have closed other windows
	if (bufref_valid(&bufref) && bufref.br_buf != curbuf)
	    close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, FALSE, FALSE);

	// Restore window sizes.
	win_size_restore(&winsizes);
	skip_win_fix_cursor = FALSE;
    }

    ga_clear(&winsizes);
    restart_edit = save_restart_edit;
# ifdef FEAT_RIGHTLEFT
    cmdmsg_rl = save_cmdmsg_rl;
# endif

    State = save_State;
    may_trigger_modechanged();
    setmouse();

    return cmdwin_result;
}


finish_incsearch_highlighting(
	int gotesc,
	incsearch_state_T *is_state,
	int call_update_screen)
{
    if (is_state->did_incsearch)
    {
	is_state->did_incsearch = FALSE;
	if (gotesc)
	    curwin->w_cursor = is_state->save_cursor;
	else
	{
	    if (!EQUAL_POS(is_state->save_cursor, is_state->search_start))
	    {
		// put the '" mark at the original position
		curwin->w_cursor = is_state->save_cursor;
		setpcmark();
	    }
	    curwin->w_cursor = is_state->search_start;
	}
	restore_viewstate(&is_state->old_viewstate);
	highlight_match = FALSE;

	// by default search all lines
	search_first_line = 0;
	search_last_line = MAXLNUM;

	magic_overruled = is_state->magic_overruled_save;

	validate_cursor();	// needed for TAB
	redraw_all_later(UPD_SOME_VALID);
	if (call_update_screen)
	    update_screen(UPD_SOME_VALID);
    }
}


cmdline_wildchar_complete(
	int		c,
	int		escape,
	int		*did_wild_list,
	int		*wim_index_p,
	expand_T	*xp,
	int		*gotesc)
{
    int		wim_index = *wim_index_p;
    int		res;
    int		j;
    int		options = WILD_NO_BEEP;

    if (wim_flags[wim_index] & WIM_BUFLASTUSED)
	options |= WILD_BUFLASTUSED;
    if (xp->xp_numfiles > 0)   // typed p_wc at least twice
    {
	// if 'wildmode' contains "list" may still need to list
	if (xp->xp_numfiles > 1
		&& !*did_wild_list
		&& ((wim_flags[wim_index] & WIM_LIST)
		    || (p_wmnu && (wim_flags[wim_index] & WIM_FULL) != 0)))
	{
	    (void)showmatches(xp,
		    p_wmnu && ((wim_flags[wim_index] & WIM_LIST) == 0));
	    redrawcmd();
	    *did_wild_list = TRUE;
	}
	if (wim_flags[wim_index] & WIM_LONGEST)
	    res = nextwild(xp, WILD_LONGEST, options, escape);
	else if (wim_flags[wim_index] & WIM_FULL)
	    res = nextwild(xp, WILD_NEXT, options, escape);
	else
	    res = OK;	    // don't insert 'wildchar' now
    }
    else		    // typed p_wc first time
    {
	wim_index = 0;
	j = ccline.cmdpos;
	// if 'wildmode' first contains "longest", get longest
	// common part
	if (wim_flags[0] & WIM_LONGEST)
	    res = nextwild(xp, WILD_LONGEST, options, escape);
	else
	    res = nextwild(xp, WILD_EXPAND_KEEP, options, escape);

	// if interrupted while completing, behave like it failed
	if (got_int)
	{
	    (void)vpeekc();	// remove <C-C> from input stream
	    got_int = FALSE;	// don't abandon the command line
	    (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
	    xp->xp_context = EXPAND_NOTHING;
	    *wim_index_p = wim_index;
	    return CMDLINE_CHANGED;
	}

	// when more than one match, and 'wildmode' first contains
	// "list", or no change and 'wildmode' contains "longest,list",
	// list all matches
	if (res == OK && xp->xp_numfiles > 1)
	{
	    // a "longest" that didn't do anything is skipped (but not
	    // "list:longest")
	    if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j)
		wim_index = 1;
	    if ((wim_flags[wim_index] & WIM_LIST)
		    || (p_wmnu && (wim_flags[wim_index] & WIM_FULL) != 0))
	    {
		if (!(wim_flags[0] & WIM_LONGEST))
		{
		    int p_wmnu_save = p_wmnu;

		    p_wmnu = 0;

		    // remove match
		    nextwild(xp, WILD_PREV, 0, escape);
		    p_wmnu = p_wmnu_save;
		}
		(void)showmatches(xp, p_wmnu
			&& ((wim_flags[wim_index] & WIM_LIST) == 0));
		redrawcmd();
		*did_wild_list = TRUE;
		if (wim_flags[wim_index] & WIM_LONGEST)
		    nextwild(xp, WILD_LONGEST, options, escape);
		else if (wim_flags[wim_index] & WIM_FULL)
		    nextwild(xp, WILD_NEXT, options, escape);
	    }
	    else
		vim_beep(BO_WILD);
	}
	else if (xp->xp_numfiles == -1)
	    xp->xp_context = EXPAND_NOTHING;
    }
    if (wim_index < 3)
	++wim_index;
    if (c == ESC)
	*gotesc = TRUE;

    *wim_index_p = wim_index;
    return (res == OK) ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED;
}


set_cmdline_str(char_u *str, int pos)
{
    cmdline_info_T  *p = get_ccline_ptr();
    int		    len;

    if (p == NULL)
	return 1;

    len = (int)STRLEN(str);
    realloc_cmdbuff(len + 1);
    p->cmdlen = len;
    STRCPY(p->cmdbuff, str);

    p->cmdpos = pos < 0 || pos > p->cmdlen ? p->cmdlen : pos;
    new_cmdpos = p->cmdpos;

    redrawcmd();

    // Trigger CmdlineChanged autocommands.
    trigger_cmd_autocmd(get_cmdline_type(), EVENT_CMDLINECHANGED);

    return 0;
}


compute_cmdrow(void)
{
    // ignore "msg_scrolled" in update_screen(), it will be reset soon.
    if (exmode_active || (msg_scrolled != 0 && !updating_screen))
	cmdline_row = Rows - 1;
    else
	cmdline_row = W_WINROW(lastwin) + lastwin->w_height
						    + lastwin->w_status_height;
}


may_do_incsearch_highlighting(
	int		    firstc,
	long		    count,
	incsearch_state_T   *is_state)
{
    int		skiplen, patlen;
    int		found;  // do_search() result
    pos_T	end_pos;
#ifdef FEAT_RELTIME
    searchit_arg_T sia;
#endif
    int		next_char;
    int		use_last_pat;
    int		did_do_incsearch = is_state->did_incsearch;
    int		search_delim;

    // Parsing range may already set the last search pattern.
    // NOTE: must call restore_last_search_pattern() before returning!
    save_last_search_pattern();

    if (!do_incsearch_highlighting(firstc, &search_delim, is_state,
							    &skiplen, &patlen))
    {
	restore_last_search_pattern();
	finish_incsearch_highlighting(FALSE, is_state, TRUE);
	if (did_do_incsearch && vpeekc() == NUL)
	    // may have skipped a redraw, do it now
	    redrawcmd();
	return;
    }

    // If there is a character waiting, search and redraw later.
    if (char_avail())
    {
	restore_last_search_pattern();
	is_state->incsearch_postponed = TRUE;
	return;
    }
    is_state->incsearch_postponed = FALSE;

    if (search_first_line == 0)
	// start at the original cursor position
	curwin->w_cursor = is_state->search_start;
    else if (search_first_line > curbuf->b_ml.ml_line_count)
    {
	// start after the last line
	curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
	curwin->w_cursor.col = MAXCOL;
    }
    else
    {
	// start at the first line in the range
	curwin->w_cursor.lnum = search_first_line;
	curwin->w_cursor.col = 0;
    }

    // Use the previous pattern for ":s//".
    next_char = ccline.cmdbuff[skiplen + patlen];
    use_last_pat = patlen == 0 && skiplen > 0
				   && ccline.cmdbuff[skiplen - 1] == next_char;

    // If there is no pattern, don't do anything.
    if (patlen == 0 && !use_last_pat)
    {
	found = 0;
	set_no_hlsearch(TRUE); // turn off previous highlight
	redraw_all_later(UPD_SOME_VALID);
    }
    else
    {
	int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;

	cursor_off();	// so the user knows we're busy
	out_flush();
	++emsg_off;	// so it doesn't beep if bad expr
	if (!p_hls)
	    search_flags += SEARCH_KEEP;
	if (search_first_line != 0)
	    search_flags += SEARCH_START;
	ccline.cmdbuff[skiplen + patlen] = NUL;
#ifdef FEAT_RELTIME
	CLEAR_FIELD(sia);
	// Set the time limit to half a second.
	sia.sa_tm = 500;
#endif
	found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim,
				 ccline.cmdbuff + skiplen, count, search_flags,
#ifdef FEAT_RELTIME
		&sia
#else
		NULL
#endif
		);
	ccline.cmdbuff[skiplen + patlen] = next_char;
	--emsg_off;

	if (curwin->w_cursor.lnum < search_first_line
		|| curwin->w_cursor.lnum > search_last_line)
	{
	    // match outside of address range
	    found = 0;
	    curwin->w_cursor = is_state->search_start;
	}

	// if interrupted while searching, behave like it failed
	if (got_int)
	{
	    (void)vpeekc();	// remove <C-C> from input stream
	    got_int = FALSE;	// don't abandon the command line
	    found = 0;
	}
	else if (char_avail())
	    // cancelled searching because a char was typed
	    is_state->incsearch_postponed = TRUE;
    }
    if (found != 0)
	highlight_match = TRUE;		// highlight position
    else
	highlight_match = FALSE;	// remove highlight

    // First restore the old curwin values, so the screen is positioned in the
    // same way as the actual search command.
    restore_viewstate(&is_state->old_viewstate);
    changed_cline_bef_curs();
    update_topline();

    if (found != 0)
    {
	pos_T	    save_pos = curwin->w_cursor;

	is_state->match_start = curwin->w_cursor;
	set_search_match(&curwin->w_cursor);
	validate_cursor();
	end_pos = curwin->w_cursor;
	is_state->match_end = end_pos;
	curwin->w_cursor = save_pos;
    }
    else
	end_pos = curwin->w_cursor; // shutup gcc 4

    // Disable 'hlsearch' highlighting if the pattern matches everything.
    // Avoids a flash when typing "foo\|".
    if (!use_last_pat)
    {
	next_char = ccline.cmdbuff[skiplen + patlen];
	ccline.cmdbuff[skiplen + patlen] = NUL;
	if (empty_pattern(ccline.cmdbuff + skiplen, search_delim)
							       && !no_hlsearch)
	{
	    redraw_all_later(UPD_SOME_VALID);
	    set_no_hlsearch(TRUE);
	}
	ccline.cmdbuff[skiplen + patlen] = next_char;
    }

    validate_cursor();
    // May redraw the status line to show the cursor position.
    if (p_ru && curwin->w_status_height > 0)
	curwin->w_redr_status = TRUE;

    update_screen(UPD_SOME_VALID);
    highlight_match = FALSE;
    restore_last_search_pattern();

    // Leave it at the end to make CTRL-R CTRL-W work.  But not when beyond the
    // end of the pattern, e.g. for ":s/pat/".
    if (ccline.cmdbuff[skiplen + patlen] != NUL)
	curwin->w_cursor = is_state->search_start;
    else if (found != 0)
	curwin->w_cursor = end_pos;

    msg_starthere();
    redrawcmdline();
    is_state->did_incsearch = TRUE;
}


f_setcmdline(typval_T *argvars, typval_T *rettv)
{
    int pos = -1;

    if (check_for_string_arg(argvars, 0) == FAIL
	    || check_for_opt_number_arg(argvars, 1) == FAIL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	int error = FALSE;

	pos = (int)tv_get_number_chk(&argvars[1], &error) - 1;
	if (error)
	    return;
	if (pos < 0)
	{
	    emsg(_(e_argument_must_be_positive));
	    return;
	}
    }

    rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos);
}


f_getcmdscreenpos(typval_T *argvars UNUSED, typval_T *rettv)
{
    cmdline_info_T *p = get_ccline_ptr();

    rettv->vval.v_number = p != NULL ? p->cmdspos + 1 : 0;
}


may_adjust_incsearch_highlighting(
	int			firstc,
	long			count,
	incsearch_state_T	*is_state,
	int			c)
{
    int	    skiplen, patlen;
    pos_T   t;
    char_u  *pat;
    int	    search_flags = SEARCH_NOOF;
    int	    i;
    int	    save;
    int	    search_delim;

    // Parsing range may already set the last search pattern.
    // NOTE: must call restore_last_search_pattern() before returning!
    save_last_search_pattern();

    if (!do_incsearch_highlighting(firstc, &search_delim, is_state,
							    &skiplen, &patlen))
    {
	restore_last_search_pattern();
	return OK;
    }
    if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL)
    {
	restore_last_search_pattern();
	return FAIL;
    }

    if (search_delim == ccline.cmdbuff[skiplen])
    {
	pat = last_search_pattern();
	if (pat == NULL)
	{
	    restore_last_search_pattern();
	    return FAIL;
	}
	skiplen = 0;
	patlen = (int)STRLEN(pat);
    }
    else
	pat = ccline.cmdbuff + skiplen;

    cursor_off();
    out_flush();
    if (c == Ctrl_G)
    {
	t = is_state->match_end;
	if (LT_POS(is_state->match_start, is_state->match_end))
	    // Start searching at the end of the match not at the beginning of
	    // the next column.
	    (void)decl(&t);
	search_flags += SEARCH_COL;
    }
    else
	t = is_state->match_start;
    if (!p_hls)
	search_flags += SEARCH_KEEP;
    ++emsg_off;
    save = pat[patlen];
    pat[patlen] = NUL;
    i = searchit(curwin, curbuf, &t, NULL,
		 c == Ctrl_G ? FORWARD : BACKWARD,
		 pat, count, search_flags, RE_SEARCH, NULL);
    --emsg_off;
    pat[patlen] = save;
    if (i)
    {
	is_state->search_start = is_state->match_start;
	is_state->match_end = t;
	is_state->match_start = t;
	if (c == Ctrl_T && firstc != '?')
	{
	    // Move just before the current match, so that when nv_search
	    // finishes the cursor will be put back on the match.
	    is_state->search_start = t;
	    (void)decl(&is_state->search_start);
	}
	else if (c == Ctrl_G && firstc == '?')
	{
	    // Move just after the current match, so that when nv_search
	    // finishes the cursor will be put back on the match.
	    is_state->search_start = t;
	    (void)incl(&is_state->search_start);
	}
	if (LT_POS(t, is_state->search_start) && c == Ctrl_G)
	{
	    // wrap around
	    is_state->search_start = t;
	    if (firstc == '?')
		(void)incl(&is_state->search_start);
	    else
		(void)decl(&is_state->search_start);
	}

	set_search_match(&is_state->match_end);
	curwin->w_cursor = is_state->match_start;
	changed_cline_bef_curs();
	update_topline();
	validate_cursor();
	highlight_match = TRUE;
	save_viewstate(&is_state->old_viewstate);
	update_screen(UPD_NOT_VALID);
	highlight_match = FALSE;
	redrawcmdline();
	curwin->w_cursor = is_state->match_end;
    }
    else
	vim_beep(BO_ERROR);
    restore_last_search_pattern();
    return FAIL;
}


f_getcmdpos(typval_T *argvars UNUSED, typval_T *rettv)
{
    cmdline_info_T *p = get_ccline_ptr();

    rettv->vval.v_number = p != NULL ? p->cmdpos + 1 : 0;
}


getcmdline_int(
    int		firstc,
    long	count UNUSED,	// only used for incremental search
    int		indent,		// indent for inside conditionals
    int		clear_ccline)	// clear ccline first
{
    static int	depth = 0;	    // call depth
    int		c;
    int		i;
    int		j;
    int		gotesc = FALSE;		// TRUE when <ESC> just typed
    int		do_abbr;		// when TRUE check for abbr.
    char_u	*lookfor = NULL;	// string to match
    int		hiscnt;			// current history line in use
    int		histype;		// history type to be used
#ifdef FEAT_SEARCH_EXTRA
    incsearch_state_T	is_state;
#endif
    int		did_wild_list = FALSE;	// did wild_list() recently
    int		wim_index = 0;		// index in wim_flags[]
    int		res;
    int		save_msg_scroll = msg_scroll;
    int		save_State = State;	// remember State when called
    int		some_key_typed = FALSE;	// one of the keys was typed
    // mouse drag and release events are ignored, unless they are
    // preceded with a mouse down event
    int		ignore_drag_release = TRUE;
#ifdef FEAT_EVAL
    int		break_ctrl_c = FALSE;
#endif
    expand_T	xpc;
    long	*b_im_ptr = NULL;
    buf_T	*b_im_ptr_buf = NULL;	// buffer where b_im_ptr is valid
    cmdline_info_T save_ccline;
    int		did_save_ccline = FALSE;
    int		cmdline_type;
    int		wild_type;

    // one recursion level deeper
    ++depth;

    if (ccline.cmdbuff != NULL)
    {
	// Being called recursively.  Since ccline is global, we need to save
	// the current buffer and restore it when returning.
	save_cmdline(&save_ccline);
	did_save_ccline = TRUE;
    }
    if (clear_ccline)
	CLEAR_FIELD(ccline);

#ifdef FEAT_EVAL
    if (firstc == -1)
    {
	firstc = NUL;
	break_ctrl_c = TRUE;
    }
#endif
#ifdef FEAT_RIGHTLEFT
    // start without Hebrew mapping for a command line
    if (firstc == ':' || firstc == '=' || firstc == '>')
	cmd_hkmap = 0;
#endif

#ifdef FEAT_SEARCH_EXTRA
    init_incsearch_state(&is_state);
#endif

    if (init_ccline(firstc, indent) != OK)
	goto theend;	// out of memory

    if (depth == 50)
    {
	// Somehow got into a loop recursively calling getcmdline(), bail out.
	emsg(_(e_command_too_recursive));
	goto theend;
    }

    ExpandInit(&xpc);
    ccline.xpc = &xpc;

#ifdef FEAT_RIGHTLEFT
    if (curwin->w_p_rl && *curwin->w_p_rlc == 's'
					  && (firstc == '/' || firstc == '?'))
	cmdmsg_rl = TRUE;
    else
	cmdmsg_rl = FALSE;
#endif

    redir_off = TRUE;		// don't redirect the typed command
    if (!cmd_silent)
    {
	i = msg_scrolled;
	msg_scrolled = 0;		// avoid wait_return() message
	gotocmdline(TRUE);
	msg_scrolled += i;
	redrawcmdprompt();		// draw prompt or indent
	set_cmdspos();
    }
    xpc.xp_context = EXPAND_NOTHING;
    xpc.xp_backslash = XP_BS_NONE;
#ifndef BACKSLASH_IN_FILENAME
    xpc.xp_shell = FALSE;
#endif

#if defined(FEAT_EVAL)
    if (ccline.input_fn)
    {
	xpc.xp_context = ccline.xp_context;
	xpc.xp_pattern = ccline.cmdbuff;
	xpc.xp_arg = ccline.xp_arg;
    }
#endif

    /*
     * Avoid scrolling when called by a recursive do_cmdline(), e.g. when
     * doing ":@0" when register 0 doesn't contain a CR.
     */
    msg_scroll = FALSE;

    State = MODE_CMDLINE;

    if (firstc == '/' || firstc == '?' || firstc == '@')
    {
	// Use ":lmap" mappings for search pattern and input().
	if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT)
	    b_im_ptr = &curbuf->b_p_iminsert;
	else
	    b_im_ptr = &curbuf->b_p_imsearch;
	b_im_ptr_buf = curbuf;
	if (*b_im_ptr == B_IMODE_LMAP)
	    State |= MODE_LANGMAP;
#ifdef HAVE_INPUT_METHOD
	im_set_active(*b_im_ptr == B_IMODE_IM);
#endif
    }
#ifdef HAVE_INPUT_METHOD
    else if (p_imcmdline)
	im_set_active(TRUE);
#endif

    setmouse();
#ifdef CURSOR_SHAPE
    ui_cursor_shape();		// may show different cursor shape
#endif

    // When inside an autocommand for writing "exiting" may be set and
    // terminal mode set to cooked.  Need to set raw mode here then.
    settmode(TMODE_RAW);

    // Trigger CmdlineEnter autocommands.
    cmdline_type = firstc == NUL ? '-' : firstc;
    trigger_cmd_autocmd(cmdline_type, EVENT_CMDLINEENTER);
#ifdef FEAT_EVAL
    if (!debug_mode)
	may_trigger_modechanged();
#endif

    init_history();
    hiscnt = get_hislen();	// set hiscnt to impossible history value
    histype = hist_char2type(firstc);

#ifdef FEAT_DIGRAPHS
    do_digraph(-1);		// init digraph typeahead
#endif

    // If something above caused an error, reset the flags, we do want to type
    // and execute commands. Display may be messed up a bit.
    if (did_emsg)
	redrawcmd();

#ifdef FEAT_STL_OPT
    // Redraw the statusline in case it uses the current mode using the mode()
    // function.
    if (!cmd_silent && msg_scrolled == 0)
    {
	int	found_one = FALSE;
	win_T	*wp;

	FOR_ALL_WINDOWS(wp)
	    if (*p_stl != NUL || *wp->w_p_stl != NUL)
	    {
		wp->w_redr_status = TRUE;
		found_one = TRUE;
	    }

	if (*p_tal != NUL)
	{
	    redraw_tabline = TRUE;
	    found_one = TRUE;
	}

	if (found_one)
	    redraw_statuslines();
    }
#endif

    did_emsg = FALSE;
    got_int = FALSE;

    /*
     * Collect the command string, handling editing keys.
     */
    for (;;)
    {
	int trigger_cmdlinechanged = TRUE;
	int end_wildmenu;

	redir_off = TRUE;	// Don't redirect the typed command.
				// Repeated, because a ":redir" inside
				// completion may switch it on.
#ifdef USE_ON_FLY_SCROLL
	dont_scroll = FALSE;	// allow scrolling here
#endif
	quit_more = FALSE;	// reset after CTRL-D which had a more-prompt

	did_emsg = FALSE;	// There can't really be a reason why an error
				// that occurs while typing a command should
				// cause the command not to be executed.

	// Trigger SafeState if nothing is pending.
	may_trigger_safestate(xpc.xp_numfiles <= 0);

	// Get a character.  Ignore K_IGNORE and K_NOP, they should not do
	// anything, such as stop completion.
	do
	{
	    cursorcmd();		// set the cursor on the right spot
	    c = safe_vgetc();
	} while (c == K_IGNORE || c == K_NOP);

	if (c == K_COMMAND || c == K_SCRIPT_COMMAND)
	{
	    int	    clen = ccline.cmdlen;

	    if (do_cmdkey_command(c, DOCMD_NOWAIT) == OK)
	    {
		if (clen == ccline.cmdlen)
		    trigger_cmdlinechanged = FALSE;
		goto cmdline_changed;
	    }
	}

	if (KeyTyped)
	{
	    some_key_typed = TRUE;
#ifdef FEAT_RIGHTLEFT
	    if (cmd_hkmap)
		c = hkmap(c);
	    if (cmdmsg_rl && !KeyStuffed)
	    {
		// Invert horizontal movements and operations.  Only when
		// typed by the user directly, not when the result of a
		// mapping.
		switch (c)
		{
		    case K_RIGHT:   c = K_LEFT; break;
		    case K_S_RIGHT: c = K_S_LEFT; break;
		    case K_C_RIGHT: c = K_C_LEFT; break;
		    case K_LEFT:    c = K_RIGHT; break;
		    case K_S_LEFT:  c = K_S_RIGHT; break;
		    case K_C_LEFT:  c = K_C_RIGHT; break;
		}
	    }
#endif
	}

	/*
	 * Ignore got_int when CTRL-C was typed here.
	 * Don't ignore it in :global, we really need to break then, e.g., for
	 * ":g/pat/normal /pat" (without the <CR>).
	 * Don't ignore it for the input() function.
	 */
	if ((c == Ctrl_C
#ifdef UNIX
		|| c == intr_char
#endif
				)
#if defined(FEAT_EVAL) || defined(FEAT_CRYPT)
		&& firstc != '@'
#endif
#ifdef FEAT_EVAL
		// do clear got_int in Ex mode to avoid infinite Ctrl-C loop
		&& (!break_ctrl_c || exmode_active)
#endif
		&& !global_busy)
	    got_int = FALSE;

	// free old command line when finished moving around in the history
	// list
	if (lookfor != NULL
		&& c != K_S_DOWN && c != K_S_UP
		&& c != K_DOWN && c != K_UP
		&& c != K_PAGEDOWN && c != K_PAGEUP
		&& c != K_KPAGEDOWN && c != K_KPAGEUP
		&& c != K_LEFT && c != K_RIGHT
		&& (xpc.xp_numfiles > 0 || (c != Ctrl_P && c != Ctrl_N)))
	    VIM_CLEAR(lookfor);

	/*
	 * When there are matching completions to select <S-Tab> works like
	 * CTRL-P (unless 'wc' is <S-Tab>).
	 */
	if (c != p_wc && c == K_S_TAB && xpc.xp_numfiles > 0)
	    c = Ctrl_P;

	if (p_wmnu)
	    c = wildmenu_translate_key(&ccline, c, &xpc, did_wild_list);

	if (cmdline_pum_active())
	{
	    // Ctrl-Y: Accept the current selection and close the popup menu.
	    // Ctrl-E: cancel the cmdline popup menu and return the original
	    // text.
	    if (c == Ctrl_E || c == Ctrl_Y)
	    {
		wild_type = (c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY;
		if (nextwild(&xpc, wild_type, WILD_NO_BEEP,
							firstc != '@') == FAIL)
		    break;
		c = Ctrl_E;
	    }
	}

	// The wildmenu is cleared if the pressed key is not used for
	// navigating the wild menu (i.e. the key is not 'wildchar' or
	// 'wildcharm' or Ctrl-N or Ctrl-P or Ctrl-A or Ctrl-L).
	// If the popup menu is displayed, then PageDown and PageUp keys are
	// also used to navigate the menu.
	end_wildmenu = (!(c == p_wc && KeyTyped) && c != p_wcm
		&& c != Ctrl_N && c != Ctrl_P && c != Ctrl_A && c != Ctrl_L);
	end_wildmenu = end_wildmenu && (!cmdline_pum_active() ||
			    (c != K_PAGEDOWN && c != K_PAGEUP
			     && c != K_KPAGEDOWN && c != K_KPAGEUP));

	// free expanded names when finished walking through matches
	if (end_wildmenu)
	{
	    if (cmdline_pum_active())
		cmdline_pum_remove();
	    if (xpc.xp_numfiles != -1)
		(void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE);
	    did_wild_list = FALSE;
	    if (!p_wmnu || (c != K_UP && c != K_DOWN))
		xpc.xp_context = EXPAND_NOTHING;
	    wim_index = 0;
	    wildmenu_cleanup(&ccline);
	}

	if (p_wmnu)
	    c = wildmenu_process_key(&ccline, c, &xpc);

	// CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
	// mode when 'insertmode' is set, CTRL-\ e prompts for an expression.
	if (c == Ctrl_BSL)
	{
	    res = cmdline_handle_backslash_key(c, &gotesc);
	    if (res == CMDLINE_CHANGED)
		goto cmdline_changed;
	    else if (res == CMDLINE_NOT_CHANGED)
		goto cmdline_not_changed;
	    else if (res == GOTO_NORMAL_MODE)
		goto returncmd;		// back to cmd mode
	    c = Ctrl_BSL;		// backslash key not processed by
					// cmdline_handle_backslash_key()
	}

#ifdef FEAT_CMDWIN
	if (c == cedit_key || c == K_CMDWIN)
	{
	    // TODO: why is ex_normal_busy checked here?
	    if ((c == K_CMDWIN || ex_normal_busy == 0) && got_int == FALSE)
	    {
		/*
		 * Open a window to edit the command line (and history).
		 */
		c = open_cmdwin();
		some_key_typed = TRUE;
	    }
	}
# ifdef FEAT_DIGRAPHS
	else
# endif
#endif
#ifdef FEAT_DIGRAPHS
	    c = do_digraph(c);
#endif

	if (c == '\n' || c == '\r' || c == K_KENTER || (c == ESC
			&& (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL)))
	{
	    // In Ex mode a backslash escapes a newline.
	    if (exmode_active
		    && c != ESC
		    && ccline.cmdpos == ccline.cmdlen
		    && ccline.cmdpos > 0
		    && ccline.cmdbuff[ccline.cmdpos - 1] == '\\')
	    {
		if (c == K_KENTER)
		    c = '\n';
	    }
	    else
	    {
		gotesc = FALSE;	// Might have typed ESC previously, don't
				// truncate the cmdline now.
		if (ccheck_abbr(c + ABBR_OFF))
		    goto cmdline_changed;
		if (!cmd_silent)
		{
		    windgoto(msg_row, 0);
		    out_flush();
		}
		break;
	    }
	}

	// Completion for 'wildchar' or 'wildcharm' key.
	if ((c == p_wc && !gotesc && KeyTyped) || c == p_wcm)
	{
	    res = cmdline_wildchar_complete(c, firstc != '@', &did_wild_list,
		    &wim_index, &xpc, &gotesc);
	    if (res == CMDLINE_CHANGED)
		goto cmdline_changed;
	}

	gotesc = FALSE;

	// <S-Tab> goes to last match, in a clumsy way
	if (c == K_S_TAB && KeyTyped)
	{
	    if (nextwild(&xpc, WILD_EXPAND_KEEP, 0, firstc != '@') == OK)
	    {
		if (xpc.xp_numfiles > 1
		    && ((!did_wild_list && (wim_flags[wim_index] & WIM_LIST))
			    || p_wmnu))
		{
		    // Trigger the popup menu when wildoptions=pum
		    showmatches(&xpc, p_wmnu
			    && ((wim_flags[wim_index] & WIM_LIST) == 0));
		}
		if (nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK
			&& nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK)
		    goto cmdline_changed;
	    }
	}

	if (c == NUL || c == K_ZERO)	    // NUL is stored as NL
	    c = NL;

	do_abbr = TRUE;		// default: check for abbreviation

	/*
	 * Big switch for a typed command line character.
	 */
	switch (c)
	{
	case K_BS:
	case Ctrl_H:
	case K_DEL:
	case K_KDEL:
	case Ctrl_W:
	    res = cmdline_erase_chars(c, indent
#ifdef FEAT_SEARCH_EXTRA
		    , &is_state
#endif
		    );
	    if (res == CMDLINE_NOT_CHANGED)
		goto cmdline_not_changed;
	    else if (res == GOTO_NORMAL_MODE)
		goto returncmd;		// back to cmd mode
	    goto cmdline_changed;

	case K_INS:
	case K_KINS:
		ccline.overstrike = !ccline.overstrike;
#ifdef CURSOR_SHAPE
		ui_cursor_shape();	// may show different cursor shape
#endif
		goto cmdline_not_changed;

	case Ctrl_HAT:
		cmdline_toggle_langmap(
				    buf_valid(b_im_ptr_buf) ? b_im_ptr : NULL);
		goto cmdline_not_changed;

//	case '@':   only in very old vi
	case Ctrl_U:
		// delete all characters left of the cursor
		j = ccline.cmdpos;
		ccline.cmdlen -= j;
		i = ccline.cmdpos = 0;
		while (i < ccline.cmdlen)
		    ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
		// Truncate at the end, required for multi-byte chars.
		ccline.cmdbuff[ccline.cmdlen] = NUL;
#ifdef FEAT_SEARCH_EXTRA
		if (ccline.cmdlen == 0)
		    is_state.search_start = is_state.save_cursor;
#endif
		redrawcmd();
		goto cmdline_changed;

#ifdef FEAT_CLIPBOARD
	case Ctrl_Y:
		// Copy the modeless selection, if there is one.
		if (clip_star.state != SELECT_CLEARED)
		{
		    if (clip_star.state == SELECT_DONE)
			clip_copy_modeless_selection(TRUE);
		    goto cmdline_not_changed;
		}
		break;
#endif

	case ESC:	// get here if p_wc != ESC or when ESC typed twice
	case Ctrl_C:
		// In exmode it doesn't make sense to return.  Except when
		// ":normal" runs out of characters.
		if (exmode_active
			       && (ex_normal_busy == 0 || typebuf.tb_len > 0))
		    goto cmdline_not_changed;

		gotesc = TRUE;		// will free ccline.cmdbuff after
					// putting it in history
		goto returncmd;		// back to cmd mode

	case Ctrl_R:			// insert register
		res = cmdline_insert_reg(&gotesc);
		if (res == CMDLINE_NOT_CHANGED)
		    goto cmdline_not_changed;
		else if (res == GOTO_NORMAL_MODE)
		    goto returncmd;
		goto cmdline_changed;

	case Ctrl_D:
		if (showmatches(&xpc, FALSE) == EXPAND_NOTHING)
		    break;	// Use ^D as normal char instead

		redrawcmd();
		continue;	// don't do incremental search now

	case K_RIGHT:
	case K_S_RIGHT:
	case K_C_RIGHT:
		do
		{
		    if (ccline.cmdpos >= ccline.cmdlen)
			break;
		    i = cmdline_charsize(ccline.cmdpos);
		    if (KeyTyped && ccline.cmdspos + i >= Columns * Rows)
			break;
		    ccline.cmdspos += i;
		    if (has_mbyte)
			ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
							     + ccline.cmdpos);
		    else
			++ccline.cmdpos;
		}
		while ((c == K_S_RIGHT || c == K_C_RIGHT
			       || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
			&& ccline.cmdbuff[ccline.cmdpos] != ' ');
		if (has_mbyte)
		    set_cmdspos_cursor();
		goto cmdline_not_changed;

	case K_LEFT:
	case K_S_LEFT:
	case K_C_LEFT:
		if (ccline.cmdpos == 0)
		    goto cmdline_not_changed;
		do
		{
		    --ccline.cmdpos;
		    if (has_mbyte)	// move to first byte of char
			ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff,
					      ccline.cmdbuff + ccline.cmdpos);
		    ccline.cmdspos -= cmdline_charsize(ccline.cmdpos);
		}
		while (ccline.cmdpos > 0
			&& (c == K_S_LEFT || c == K_C_LEFT
			       || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
			&& ccline.cmdbuff[ccline.cmdpos - 1] != ' ');
		if (has_mbyte)
		    set_cmdspos_cursor();
		goto cmdline_not_changed;

	case K_IGNORE:
		// Ignore mouse event or open_cmdwin() result.
		goto cmdline_not_changed;

#ifdef FEAT_GUI_MSWIN
	    // On MS-Windows ignore <M-F4>, we get it when closing the window
	    // was cancelled.
	case K_F4:
	    if (mod_mask == MOD_MASK_ALT)
	    {
		redrawcmd();	    // somehow the cmdline is cleared
		goto cmdline_not_changed;
	    }
	    break;
#endif

	case K_MIDDLEDRAG:
	case K_MIDDLERELEASE:
		goto cmdline_not_changed;	// Ignore mouse

	case K_MIDDLEMOUSE:
# ifdef FEAT_GUI
		// When GUI is active, also paste when 'mouse' is empty
		if (!gui.in_use)
# endif
		    if (!mouse_has(MOUSE_COMMAND))
			goto cmdline_not_changed;   // Ignore mouse
# ifdef FEAT_CLIPBOARD
		if (clip_star.available)
		    cmdline_paste('*', TRUE, TRUE);
		else
# endif
		    cmdline_paste(0, TRUE, TRUE);
		redrawcmd();
		goto cmdline_changed;

# ifdef FEAT_DND
	case K_DROP:
		cmdline_paste('~', TRUE, FALSE);
		redrawcmd();
		goto cmdline_changed;
# endif

	case K_LEFTDRAG:
	case K_LEFTRELEASE:
	case K_RIGHTDRAG:
	case K_RIGHTRELEASE:
		// Ignore drag and release events when the button-down wasn't
		// seen before.
		if (ignore_drag_release)
		    goto cmdline_not_changed;
		// FALLTHROUGH
	case K_LEFTMOUSE:
	case K_RIGHTMOUSE:
		cmdline_left_right_mouse(c, &ignore_drag_release);
		goto cmdline_not_changed;

	// Mouse scroll wheel: ignored here
	case K_MOUSEDOWN:
	case K_MOUSEUP:
	case K_MOUSELEFT:
	case K_MOUSERIGHT:
	// Alternate buttons ignored here
	case K_X1MOUSE:
	case K_X1DRAG:
	case K_X1RELEASE:
	case K_X2MOUSE:
	case K_X2DRAG:
	case K_X2RELEASE:
	case K_MOUSEMOVE:
		goto cmdline_not_changed;

#ifdef FEAT_GUI
	case K_LEFTMOUSE_NM:	// mousefocus click, ignored
	case K_LEFTRELEASE_NM:
		goto cmdline_not_changed;

	case K_VER_SCROLLBAR:
		if (msg_scrolled == 0)
		{
		    gui_do_scroll();
		    redrawcmd();
		}
		goto cmdline_not_changed;

	case K_HOR_SCROLLBAR:
		if (msg_scrolled == 0)
		{
		    gui_do_horiz_scroll(scrollbar_value, FALSE);
		    redrawcmd();
		}
		goto cmdline_not_changed;
#endif
#ifdef FEAT_GUI_TABLINE
	case K_TABLINE:
	case K_TABMENU:
		// Don't want to change any tabs here.  Make sure the same tab
		// is still selected.
		if (gui_use_tabline())
		    gui_mch_set_curtab(tabpage_index(curtab));
		goto cmdline_not_changed;
#endif

	case K_SELECT:	    // end of Select mode mapping - ignore
		goto cmdline_not_changed;

	case Ctrl_B:	    // begin of command line
	case K_HOME:
	case K_KHOME:
	case K_S_HOME:
	case K_C_HOME:
		ccline.cmdpos = 0;
		set_cmdspos();
		goto cmdline_not_changed;

	case Ctrl_E:	    // end of command line
	case K_END:
	case K_KEND:
	case K_S_END:
	case K_C_END:
		ccline.cmdpos = ccline.cmdlen;
		set_cmdspos_cursor();
		goto cmdline_not_changed;

	case Ctrl_A:	    // all matches
		if (cmdline_pum_active())
		    // As Ctrl-A completes all the matches, close the popup
		    // menu (if present)
		    cmdline_pum_cleanup(&ccline);

		if (nextwild(&xpc, WILD_ALL, 0, firstc != '@') == FAIL)
		    break;
		xpc.xp_context = EXPAND_NOTHING;
		did_wild_list = FALSE;
		goto cmdline_changed;

	case Ctrl_L:
#ifdef FEAT_SEARCH_EXTRA
		if (may_add_char_to_search(firstc, &c, &is_state) == OK)
		    goto cmdline_not_changed;
#endif

		// completion: longest common part
		if (nextwild(&xpc, WILD_LONGEST, 0, firstc != '@') == FAIL)
		    break;
		goto cmdline_changed;

	case Ctrl_N:	    // next match
	case Ctrl_P:	    // previous match
		if (xpc.xp_numfiles > 0)
		{
		    wild_type = (c == Ctrl_P) ? WILD_PREV : WILD_NEXT;
		    if (nextwild(&xpc, wild_type, 0, firstc != '@') == FAIL)
			break;
		    goto cmdline_not_changed;
		}
		// FALLTHROUGH
	case K_UP:
	case K_DOWN:
	case K_S_UP:
	case K_S_DOWN:
	case K_PAGEUP:
	case K_KPAGEUP:
	case K_PAGEDOWN:
	case K_KPAGEDOWN:
		if (cmdline_pum_active()
			&& (c == K_PAGEUP || c == K_PAGEDOWN ||
			    c == K_KPAGEUP || c == K_KPAGEDOWN))
		{
		    // If the popup menu is displayed, then PageUp and PageDown
		    // are used to scroll the menu.
		    wild_type = WILD_PAGEUP;
		    if (c == K_PAGEDOWN || c == K_KPAGEDOWN)
			wild_type = WILD_PAGEDOWN;
		    if (nextwild(&xpc, wild_type, 0, firstc != '@') == FAIL)
			break;
		    goto cmdline_not_changed;
		}
		else
		{
		    res = cmdline_browse_history(c, firstc, &lookfor, histype,
			    &hiscnt, &xpc);
		    if (res == CMDLINE_CHANGED)
			goto cmdline_changed;
		    else if (res == GOTO_NORMAL_MODE)
			goto returncmd;
		}
		goto cmdline_not_changed;

#ifdef FEAT_SEARCH_EXTRA
	case Ctrl_G:	    // next match
	case Ctrl_T:	    // previous match
		if (may_adjust_incsearch_highlighting(
					  firstc, count, &is_state, c) == FAIL)
		    goto cmdline_not_changed;
		break;
#endif

	case Ctrl_V:
	case Ctrl_Q:
		{
		    ignore_drag_release = TRUE;
		    putcmdline('^', TRUE);

		    // Get next (two) character(s).  Do not change any
		    // modifyOtherKeys ESC sequence to a normal key for
		    // CTRL-SHIFT-V.
		    c = get_literal(mod_mask & MOD_MASK_SHIFT);

		    do_abbr = FALSE;	    // don't do abbreviation now
		    extra_char = NUL;
		    // may need to remove ^ when composing char was typed
		    if (enc_utf8 && utf_iscomposing(c) && !cmd_silent)
		    {
			draw_cmdline(ccline.cmdpos,
						ccline.cmdlen - ccline.cmdpos);
			msg_putchar(' ');
			cursorcmd();
		    }
		}

		break;

#ifdef FEAT_DIGRAPHS
	case Ctrl_K:
		ignore_drag_release = TRUE;
		putcmdline('?', TRUE);
# ifdef USE_ON_FLY_SCROLL
		dont_scroll = TRUE;	    // disallow scrolling here
# endif
		c = get_digraph(TRUE);
		extra_char = NUL;
		if (c != NUL)
		    break;

		redrawcmd();
		goto cmdline_not_changed;
#endif // FEAT_DIGRAPHS

#ifdef FEAT_RIGHTLEFT
	case Ctrl__:	    // CTRL-_: switch language mode
		if (!p_ari)
		    break;
		cmd_hkmap = !cmd_hkmap;
		goto cmdline_not_changed;
#endif

	case K_PS:
		bracketed_paste(PASTE_CMDLINE, FALSE, NULL);
		goto cmdline_changed;

	default:
#ifdef UNIX
		if (c == intr_char)
		{
		    gotesc = TRUE;	// will free ccline.cmdbuff after
					// putting it in history
		    goto returncmd;	// back to Normal mode
		}
#endif
		/*
		 * Normal character with no special meaning.  Just set mod_mask
		 * to 0x0 so that typing Shift-Space in the GUI doesn't enter
		 * the string <S-Space>.  This should only happen after ^V.
		 */
		if (!IS_SPECIAL(c))
		    mod_mask = 0x0;
		break;
	}
	/*
	 * End of switch on command line character.
	 * We come here if we have a normal character.
	 */

	if (do_abbr && (IS_SPECIAL(c) || !vim_iswordc(c))
		&& (ccheck_abbr(
			// Add ABBR_OFF for characters above 0x100, this is
			// what check_abbr() expects.
				(has_mbyte && c >= 0x100) ? (c + ABBR_OFF) : c)
		    || c == Ctrl_RSB))
	    goto cmdline_changed;

	/*
	 * put the character in the command line
	 */
	if (IS_SPECIAL(c) || mod_mask != 0)
	    put_on_cmdline(get_special_key_name(c, mod_mask), -1, TRUE);
	else
	{
	    if (has_mbyte)
	    {
		j = (*mb_char2bytes)(c, IObuff);
		IObuff[j] = NUL;	// exclude composing chars
		put_on_cmdline(IObuff, j, TRUE);
	    }
	    else
	    {
		IObuff[0] = c;
		put_on_cmdline(IObuff, 1, TRUE);
	    }
	}
	goto cmdline_changed;

/*
 * This part implements incremental searches for "/" and "?"
 * Jump to cmdline_not_changed when a character has been read but the command
 * line did not change. Then we only search and redraw if something changed in
 * the past.
 * Jump to cmdline_changed when the command line did change.
 * (Sorry for the goto's, I know it is ugly).
 */
cmdline_not_changed:
#ifdef FEAT_SEARCH_EXTRA
	if (!is_state.incsearch_postponed)
	    continue;
#endif

cmdline_changed:
#ifdef FEAT_SEARCH_EXTRA
	// If the window changed incremental search state is not valid.
	if (is_state.winid != curwin->w_id)
	    init_incsearch_state(&is_state);
#endif
	if (trigger_cmdlinechanged)
	    // Trigger CmdlineChanged autocommands.
	    trigger_cmd_autocmd(cmdline_type, EVENT_CMDLINECHANGED);

#ifdef FEAT_SEARCH_EXTRA
	if (xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL))
	    may_do_incsearch_highlighting(firstc, count, &is_state);
#endif

#ifdef FEAT_RIGHTLEFT
	if (cmdmsg_rl
# ifdef FEAT_ARABIC
		|| (p_arshape && !p_tbidi
				       && cmdline_has_arabic(0, ccline.cmdlen))
# endif
		)
	    // Always redraw the whole command line to fix shaping and
	    // right-left typing.  Not efficient, but it works.
	    // Do it only when there are no characters left to read
	    // to avoid useless intermediate redraws.
	    if (vpeekc() == NUL)
		redrawcmd();
#endif
    }

returncmd:

#ifdef FEAT_RIGHTLEFT
    cmdmsg_rl = FALSE;
#endif

    ExpandCleanup(&xpc);
    ccline.xpc = NULL;

#ifdef FEAT_SEARCH_EXTRA
    finish_incsearch_highlighting(gotesc, &is_state, FALSE);
#endif

    if (ccline.cmdbuff != NULL)
    {
	/*
	 * Put line in history buffer (":" and "=" only when it was typed).
	 */
	if (ccline.cmdlen && firstc != NUL
		&& (some_key_typed || histype == HIST_SEARCH))
	{
	    add_to_history(histype, ccline.cmdbuff, TRUE,
				       histype == HIST_SEARCH ? firstc : NUL);
	    if (firstc == ':')
	    {
		vim_free(new_last_cmdline);
		new_last_cmdline = vim_strsave(ccline.cmdbuff);
	    }
	}

	if (gotesc)
	    abandon_cmdline();
    }

    /*
     * If the screen was shifted up, redraw the whole screen (later).
     * If the line is too long, clear it, so ruler and shown command do
     * not get printed in the middle of it.
     */
    msg_check();
    msg_scroll = save_msg_scroll;
    redir_off = FALSE;

    // When the command line was typed, no need for a wait-return prompt.
    if (some_key_typed)
	need_wait_return = FALSE;

    // Trigger CmdlineLeave autocommands.
    trigger_cmd_autocmd(cmdline_type, EVENT_CMDLINELEAVE);

    State = save_State;

#ifdef FEAT_EVAL
    if (!debug_mode)
	may_trigger_modechanged();
#endif

#ifdef HAVE_INPUT_METHOD
    if (b_im_ptr != NULL && buf_valid(b_im_ptr_buf)
						  && *b_im_ptr != B_IMODE_LMAP)
	im_save_status(b_im_ptr);
    im_set_active(FALSE);
#endif
    setmouse();
#ifdef CURSOR_SHAPE
    ui_cursor_shape();		// may show different cursor shape
#endif
    sb_text_end_cmdline();

theend:
    {
	char_u *p = ccline.cmdbuff;

	--depth;
	if (did_save_ccline)
	    restore_cmdline(&save_ccline);
	else
	    ccline.cmdbuff = NULL;
	return p;
    }
}


cmdline_insert_reg(int *gotesc UNUSED)
{
    int		i;
    int		c;
#ifdef FEAT_EVAL
    int		save_new_cmdpos = new_cmdpos;
#endif

#ifdef USE_ON_FLY_SCROLL
    dont_scroll = TRUE;	// disallow scrolling here
#endif
    putcmdline('"', TRUE);
    ++no_mapping;
    ++allow_keys;
    i = c = plain_vgetc();	// CTRL-R <char>
    if (i == Ctrl_O)
	i = Ctrl_R;		// CTRL-R CTRL-O == CTRL-R CTRL-R
    if (i == Ctrl_R)
	c = plain_vgetc();	// CTRL-R CTRL-R <char>
    extra_char = NUL;
    --no_mapping;
    --allow_keys;
#ifdef FEAT_EVAL
    /*
     * Insert the result of an expression.
     */
    new_cmdpos = -1;
    if (c == '=')
    {
	if (ccline.cmdfirstc == '='  // can't do this recursively
		|| cmdline_star > 0) // or when typing a password
	{
	    beep_flush();
	    c = ESC;
	}
	else
	    c = get_expr_register();
    }
#endif
    if (c != ESC)	    // use ESC to cancel inserting register
    {
	cmdline_paste(c, i == Ctrl_R, FALSE);

#ifdef FEAT_EVAL
	// When there was a serious error abort getting the
	// command line.
	if (aborting())
	{
	    *gotesc = TRUE;  // will free ccline.cmdbuff after
	    // putting it in history
	    return GOTO_NORMAL_MODE;
	}
#endif
	KeyTyped = FALSE;	// Don't do p_wc completion.
#ifdef FEAT_EVAL
	if (new_cmdpos >= 0)
	{
	    // set_cmdline_pos() was used
	    if (new_cmdpos > ccline.cmdlen)
		ccline.cmdpos = ccline.cmdlen;
	    else
		ccline.cmdpos = new_cmdpos;
	}
#endif
    }
#ifdef FEAT_EVAL
    new_cmdpos = save_new_cmdpos;
#endif

    // remove the double quote
    redrawcmd();

    // The text has been stuffed, the command line didn't change yet.
    return CMDLINE_NOT_CHANGED;
}


	PeakAutoAdjustFilter() :
		mixerShift(0),
		masterVolume(256),
		lastPeakValue(0)
	{
	}

Fix use-after-free in PlayerGeneric destructor


mp_sint32 PlayerGeneric::setBufferSize(mp_uint32 bufferSize)
{
	mp_sint32 res = 0;

	this->bufferSize = bufferSize;

	if (mixer)
	{
		// If we're told to compensate the samples until we
		// we reached 2^n buffer sizes
		if (compensateBufferFlag)
		{
			for (mp_uint32 i = 0; i < 16; i++)
			{
				if ((unsigned)(1 << i) >= (unsigned)bufferSize)
				{
					bufferSize = 1 << i;
					break;
				}
			}
		}

		res = mixer->setBufferSize(bufferSize);
	}

	return res;
}


mp_sint32 PlayerGeneric::resumePlaying()
{
	if (player && !player->isPlaying())
		player->resumePlaying();

	if (mixer && mixer->isPaused())
		return mixer->resume();
	else if (mixer && !mixer->isPlaying())
		return mixer->start();

	return MP_OK;
}


void PlayerGeneric::setResamplerType(ResamplerTypes type)
{
	resamplerType = type;
	if (player)
		player->setResamplerType(type);
}


void PlayerGeneric::setDisableMixing(bool b)
{
	disableMixing = b;

	if (player)
		player->setDisableMixing(disableMixing);
}


mp_sint32 PlayerGeneric::getNumMaxVirChannels() const
{
#ifndef MILKYTRACKER
	if (player)
	{
		if (player->getType() == PlayerBase::PlayerType_IT)
		{
			return static_cast<PlayerIT*>(player)->getNumMaxVirChannels();
		}
	}
#endif
	return numMaxVirChannels;
}


mp_sint32 PlayerGeneric::getSongMainVolume() const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		return player->getSongMainVolume(index);
	}

	return 255;
}


void PlayerGeneric::resetOnStop(bool b)
{
	resetOnStopFlag = b;
	if (player)
		player->resetOnStop(b);
}


PlayerGeneric::PlayModes PlayerGeneric::getPlayMode() const
{
	if (player)
		return player->getPlayMode();

	return playMode;
}


void PlayerGeneric::resetAllSpeed()
{
	if (player)
		player->resetAllSpeed();
}


void PlayerGeneric::lastPattern()
{
	if (player)
		player->lastPattern();
}


void PlayerGeneric::adjustSettings()
{
	mp_uint32 bufferSize = mixer->getBufferSize();
	mp_uint32 sampleRate = mixer->getSampleRate();

	this->bufferSize = bufferSize;
	this->frequency = sampleRate;

	if (player)
	{
		player->setBufferSize(bufferSize);
		player->adjustFrequency(sampleRate);
	}
}


mp_sint32 PlayerGeneric::setPowerOfTwoCompensationFlag(bool b)
{
	if (mixer && compensateBufferFlag != b)
	{
		compensateBufferFlag = b;
		setBufferSize(bufferSize);
	}

	return MP_OK;
}


void PlayerGeneric::enable(PlayModeOptions option, bool b)
{
	ASSERT(option>=PlayModeOptionFirst && option<PlayModeOptionLast);
	options[option] = b;

	if (player)
		player->enable(option, b);
}


mp_sint32 PlayerGeneric::getSpeed() const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		return player->getSpeed(index);
	}

	return 0;
}


void PlayerGeneric::setPatternToPlay(mp_sint32 patternIndex)
{
	if (player)
		player->setPatternToPlay(patternIndex);
}


void PlayerGeneric::restart(mp_uint32 startPosition/* = 0*/, mp_uint32 startRow/* = 0*/, bool resetMixer/* = true*/, const mp_ubyte* customPanningTable/* = NULL*/, bool playOneRowOnly/* = false*/)
{
	if (player)
		player->restart(startPosition, startRow, resetMixer, customPanningTable, playOneRowOnly);
}


mp_uint32 PlayerGeneric::getSyncSampleCounter() const
{
	if (player)
		return player->getSyncSampleCounter();

	return 0;
}


void PlayerGeneric::getPosition(mp_sint32& order, mp_sint32& row) const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		player->getPosition(order, row, index);
		return;
	}

	order = row = 0;
}


void PlayerGeneric::setResamplerType(bool interpolation, bool ramping)
{
	if (interpolation)
	{
		if (ramping)
			resamplerType = MIXER_LERPING_RAMPING;
		else
			resamplerType = MIXER_LERPING;
	}
	else
	{
		if (ramping)
			resamplerType = MIXER_NORMAL_RAMPING;
		else
			resamplerType = MIXER_NORMAL;
	}

	if (player)
		player->setResamplerType(resamplerType);
}


mp_sint32 PlayerGeneric::pausePlaying()
{
	paused = true;
	if (mixer)
		return mixer->pause();

	return MP_OK;
}


mp_sint32 PlayerGeneric::getCurrentSample(mp_sint32 position, mp_sint32 channel)
{
	if (mixer)
		return mixer->getCurrentSample(position, channel);

	return 0;
}


void PlayerGeneric::resetChannels()
{
	if (player)
		player->resetChannelsFull();
}


void PlayerGeneric::setIdle(bool idle)
{
	this->idle = idle;
	if (player)
		player->setIdle(idle);
}


	virtual void mix(mp_sint32* buffer, mp_uint32 bufferSize)
	{
		const mp_sint32* buffer32 = buffer;

		for (mp_uint32 i = 0; i < bufferSize*MP_NUMCHANNELS; i++)
		{
			mp_sint32 b = *buffer32++;

			if (abs(b) > lastPeakValue)
				lastPeakValue = abs(b);
		}
	}


mp_sint32 PlayerGeneric::exportToWAV(const SYSCHAR* fileName, XModule* module,
									 mp_sint32 startOrder/* = 0*/, mp_sint32 endOrder/* = -1*/,
									 const mp_ubyte* mutingArray/* = NULL*/, mp_uint32 mutingNumChannels/* = 0*/,
									 const mp_ubyte* customPanningTable/* = NULL*/,
									 AudioDriverBase* preferredDriver/* = NULL*/,
									 mp_sint32* timingLUT/* = NULL*/)
{
	PlayerBase* player = NULL;

	AudioDriverBase* wavWriter = preferredDriver;
	bool isWAVWriterDriver = false;

	if (wavWriter == NULL)
	{
		wavWriter = new WAVWriter(fileName);
		isWAVWriterDriver = true;

		if (!static_cast<WAVWriter*>(wavWriter)->isOpen())
		{
			delete wavWriter;
			return MP_DEVICE_ERROR;
		}
	}

	MasterMixer mixer(frequency, bufferSize, 1, wavWriter);
	mixer.setSampleShift(sampleShift);
	mixer.setDisableMixing(disableMixing);

	player = getPreferredPlayer(module);

	PeakAutoAdjustFilter filter;
	if (autoAdjustPeak)
		mixer.setFilterHook(&filter);

	if (player)
	{
		player->adjustFrequency(frequency);
		player->resetOnStop(resetOnStopFlag);
		player->setBufferSize(bufferSize);
		player->setResamplerType(resamplerType);
		player->setMasterVolume(masterVolume);
		player->setPlayMode(playMode);
		player->setDisableMixing(disableMixing);
		player->setAllowFilters(allowFilters);
#ifndef MILKYTRACKER
		if (player->getType() == PlayerBase::PlayerType_IT)
		{
			static_cast<PlayerIT*>(player)->setNumMaxVirChannels(numMaxVirChannels);
		}
#endif
		mixer.addDevice(player);
	}

	if (player)
	{
		if (mutingArray && mutingNumChannels > 0 && mutingNumChannels <= module->header.channum)
		{
			for (mp_uint32 i = 0; i < mutingNumChannels; i++)
				player->muteChannel(i, mutingArray[i] == 1);
		}
		player->startPlaying(module, false, startOrder, 0, -1, customPanningTable, false, -1);

		mixer.start();
	}

	if (endOrder == -1 || endOrder < startOrder || endOrder > module->header.ordnum - 1)
		endOrder = module->header.ordnum - 1;

	mp_sint32 curOrderPos = startOrder;
	if (timingLUT)
	{
		for (mp_sint32 i = 0; i < module->header.ordnum; i++)
			timingLUT[i] = -1;

		timingLUT[curOrderPos] = 0;
	}

	while (!player->hasSongHalted() && player->getOrder(0) <= endOrder)
	{
		wavWriter->advance();

		if (player->getOrder(0) != curOrderPos)
		{
#ifdef __VERBOSE__
			printf("%f\n", (float)wavWriter->getNumPlayedSamples() / (float)getMixFrequency());
#endif
			curOrderPos = player->getOrder(0);
			if (timingLUT && curOrderPos < module->header.ordnum && timingLUT[curOrderPos] == -1)
				timingLUT[curOrderPos] = wavWriter->getNumPlayedSamples();
		}
	}

	player->stopPlaying();

	mixer.stop();
	// important step, otherwise destruction of the audio driver will cause
	// trouble if the mixer instance is removed from this function's stack
	// and trys to access the driver which is no longer existant
	mixer.closeAudioDevice();

	// Sync value
	sampleShift = mixer.getSampleShift();
	filter.mixerShift = sampleShift;
	filter.calculateMasterVolume();
	masterVolume = filter.masterVolume;

	delete player;

	mp_sint32 numWrittenSamples = wavWriter->getNumPlayedSamples();

	if (isWAVWriterDriver)
		delete wavWriter;

	return numWrittenSamples;
}


mp_sint32 PlayerGeneric::getTempo() const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		return player->getTempo(index);
	}

	return 0;
}


	MixerNotificationListener(PlayerGeneric& player) :
		player(player)
	{
	}


bool PlayerGeneric::isRepeating() const
{
	if (player)
		return player->isRepeating();

	return repeat;
}


mp_sint32 PlayerGeneric::beatPacketsToBufferSize(mp_uint32 numBeats)
{
	return ChannelMixer::beatPacketsToBufferSize(getMixFrequency(), numBeats);
}


mp_sint32 PlayerGeneric::getNumActiveChannels() const
{
	if (player)
		return player->getNumActiveChannels();

	return 0;
}


bool PlayerGeneric::isEnabled(PlayModeOptions option) const
{
	ASSERT(option>=PlayModeOptionFirst && option<PlayModeOptionLast);

	if (!player)
		return options[option];
	else
		return player->isEnabled(option);
}


mp_sint32 PlayerGeneric::getNumAllocatedChannels() const
{
	if (player)
		return player->getNumAllocatedChannels();

	return 0;
}


mp_sint32 PlayerGeneric::getOrder() const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		return player->getOrder(index);
	}

	return 0;
}


PlayerBase::PlayerTypes PlayerGeneric::getPreferredPlayerType(XModule* module)
{
	if (module == NULL)
		return PlayerBase::PlayerType_INVALID;

	switch (module->getType())
	{
		case XModule::ModuleType_669:
		case XModule::ModuleType_FAR:
#ifndef MILKYTRACKER
			return PlayerBase::PlayerType_FAR;
			break;
#endif
		case XModule::ModuleType_IT:
#ifndef MILKYTRACKER
			return PlayerBase::PlayerType_IT;
			break;
#endif
		case XModule::ModuleType_UNKNOWN: // just assume our standard player can handle this
		//case XModule::ModuleType_669:
		case XModule::ModuleType_AMF:
		case XModule::ModuleType_AMS:
		case XModule::ModuleType_CBA:
		case XModule::ModuleType_DBM:
		case XModule::ModuleType_DIGI:
		case XModule::ModuleType_DSM:
		case XModule::ModuleType_DSm:
		case XModule::ModuleType_DTM_1:
		case XModule::ModuleType_DTM_2:
		case XModule::ModuleType_GDM:
		case XModule::ModuleType_GMC:
		case XModule::ModuleType_IMF:
		case XModule::ModuleType_MDL:
		case XModule::ModuleType_MOD:
		case XModule::ModuleType_MTM:
		case XModule::ModuleType_MXM:
		case XModule::ModuleType_OKT:
		case XModule::ModuleType_PLM:
		case XModule::ModuleType_PSM:
		case XModule::ModuleType_PTM:
		case XModule::ModuleType_S3M:
		case XModule::ModuleType_STM:
		case XModule::ModuleType_SFX:
		case XModule::ModuleType_UNI:
		case XModule::ModuleType_ULT:
		case XModule::ModuleType_XM:
		case XModule::ModuleType_NONE:
			return PlayerBase::PlayerType_Generic;
			break;

		default:
			return PlayerBase::PlayerType_INVALID;
	}
}


bool PlayerGeneric::getAllowFilters() const
{
	if (player)
		return player->getAllowFilters();

	return allowFilters;
}


void PlayerGeneric::setNumMaxVirChannels(mp_sint32 max)
{
	numMaxVirChannels = max;
#ifndef MILKYTRACKER
	if (player)
	{
		if (player->getType() == PlayerBase::PlayerType_IT)
		{
			static_cast<PlayerIT*>(player)->setNumMaxVirChannels(max);
		}
	}
#endif
}


PlayerGeneric::PlayerGeneric(mp_sint32 frequency, AudioDriverInterface* audioDriver/* = NULL*/) :
	mixer(NULL),
	player(NULL),
	frequency(frequency),
	audioDriver(audioDriver),
	audioDriverName(NULL)
{
	listener = new MixerNotificationListener(*this);

	bufferSize = 0;
	sampleShift = 0;

	resamplerType = MIXER_NORMAL;

	idle = false;
	playOneRowOnly = false;
	paused = false;
	repeat = false;
	resetOnStopFlag = false;
	autoAdjustPeak = false;
	disableMixing = false;
	allowFilters = false;
#ifdef __FORCEPOWEROFTWOBUFFERSIZE__
	compensateBufferFlag = true;
#else
	compensateBufferFlag = false;
#endif
	masterVolume = panningSeparation = numMaxVirChannels = 256;
	resetMainVolumeOnStartPlayFlag = true;
	playMode = PlayMode_Auto;

	// Special playmode settings
	options[PlayModeOptionPanning8xx] = true;
	options[PlayModeOptionPanningE8x] = false;
	options[PlayModeOptionForcePTPitchLimit] = true;

	AudioDriverManager audioDriverManager;
	const char* defaultName = audioDriverManager.getPreferredAudioDriver()->getDriverID();
	if (defaultName)
	{
		audioDriverName = new char[strlen(defaultName)+1];
		strcpy(audioDriverName, defaultName);
	}
}


bool PlayerGeneric::isIdle() const
{
	if (player)
		return player->isIdle();

	return idle;
}


PlayerGeneric::~PlayerGeneric()
{

	if (player)
	{
		if (mixer && mixer->isActive() && !mixer->isDeviceRemoved(player))
			mixer->removeDevice(player);
		delete player;
	}

	if (mixer)
		delete mixer;

	delete[] audioDriverName;

	delete listener;
}


void PlayerGeneric::setPeakAutoAdjust(bool b)
{
	this->autoAdjustPeak = b;
}


mp_sint32 PlayerGeneric::getLastUnvisitedPosition() const
{
	if (player)
		return player->getLastUnvisitedPosition();

	return 0;
}


void PlayerGeneric::setAllowFilters(bool b)
{
	allowFilters = b;

	if (player)
		player->setAllowFilters(allowFilters);
}


bool PlayerGeneric::grabChannelInfo(mp_sint32 chn, TPlayerChannelInfo& channelInfo) const
{
	if (player)
		return player->grabChannelInfo(chn, channelInfo);

	return false;
}


mp_sint32 PlayerGeneric::adjustFrequency(mp_uint32 frequency)
{
	this->frequency = frequency;

	mp_sint32 res = MP_OK;

	if (mixer)
		res = mixer->setSampleRate(frequency);

	return res;
}


mp_sint32 PlayerGeneric::getPanningSeparation() const
{
	if (player)
		return player->getPanningSeparation();

	return panningSeparation;
}


void PlayerGeneric::setPanningSeparation(mp_sint32 separation)
{
	panningSeparation = separation;
	if (player)
		player->setPanningSeparation(separation);
}


void PlayerGeneric::resetMainVolumeOnStartPlay(bool b)
{
	resetMainVolumeOnStartPlayFlag = b;
	if (player)
		player->resetMainVolumeOnStartPlay(b);
}


bool PlayerGeneric::hasSongHalted() const
{
	if (player)
		return player->hasSongHalted();

	return true;
}


void PlayerGeneric::setMasterVolume(mp_sint32 vol)
{
	masterVolume = vol;
	if (player)
		player->setMasterVolume(vol);
}


ChannelMixer::ResamplerTypes PlayerGeneric::getResamplerType() const
{
	if (player)
		return player->getResamplerType();

	return resamplerType;
}


mp_sint32 PlayerGeneric::getSampleShift() const
{
	if (mixer)
		return mixer->getSampleShift();

	return sampleShift;
}


mp_sint32 PlayerGeneric::getMasterVolume() const
{
	if (player)
		return player->getMasterVolume();

	return masterVolume;
}


mp_int64 PlayerGeneric::getSampleCounter() const
{
	if (player)
		return player->getSampleCounter();

	return 0;
}


mp_sint32 PlayerGeneric::adjustBufferSize(mp_uint32 numBeats)
{
	return setBufferSize(beatPacketsToBufferSize(numBeats));
}


void PlayerGeneric::setSampleShift(mp_sint32 shift)
{
	sampleShift = shift;
	if (mixer)
		mixer->setSampleShift(shift);
}


mp_sint32 PlayerGeneric::getCurrentSamplePosition() const
{
	if (mixer && mixer->getAudioDriver())
		return mixer->getAudioDriver()->getBufferPos();

	return 0;
}


bool PlayerGeneric::isPlaying() const
{
	if (mixer)
		return mixer->isPlaying();

	return false;
}


PlayerBase*	PlayerGeneric::getPreferredPlayer(XModule* module) const
{
	switch (getPreferredPlayerType(module))
	{
#ifndef MILKYTRACKER
		case PlayerBase::PlayerType_FAR:
			return new PlayerFAR(frequency);
		case PlayerBase::PlayerType_IT:
			return new PlayerIT(frequency);
#endif
		case PlayerBase::PlayerType_Generic:
			return new PlayerSTD(frequency);

		default:
			return NULL;
	}
}


mp_sint32 PlayerGeneric::getMixFrequency() const
{
	if (player)
		return player->getMixFrequency();

	return frequency;
}


bool PlayerGeneric::setCurrentAudioDriverByName(const char* name)
{
	if (name == NULL)
		return false;

	if (mixer)
	{
		bool res = mixer->setCurrentAudioDriverByName(name);

		if (audioDriverName)
			delete[] audioDriverName;

		const char* curDrvName = getCurrentAudioDriverName();
		ASSERT(curDrvName);
		audioDriverName = new char[strlen(curDrvName)+1];
		strcpy(audioDriverName, curDrvName);
		return res;
	}

	AudioDriverManager audioDriverManager;
	if (audioDriverManager.getAudioDriverByName(name))
	{
		if (audioDriverName)
			delete[] audioDriverName;

		audioDriverName = new char[strlen(name)+1];
		strcpy(audioDriverName, name);
		return true;
	}

	return false;
}


const char*	PlayerGeneric::getCurrentAudioDriverName() const
{
	if (mixer)
		return mixer->getCurrentAudioDriverName();

	return audioDriverName;
}


mp_sint32 PlayerGeneric::getCurrentSamplePeak(mp_sint32 position, mp_sint32 channel)
{
	if (mixer)
		return mixer->getCurrentSamplePeak(position, channel);

	return 0;
}


void PlayerGeneric::setPlayMode(PlayModes mode)
{
	playMode = mode;
	if (player)
		player->setPlayMode(mode);
}


mp_sint32 PlayerGeneric::stopPlaying()
{
	if (player)
		player->stopPlaying();

	if (mixer)
		return mixer->stop();

	return MP_OK;
}


mp_int64 PlayerGeneric::getSyncCount() const
{
	if (player)
		return player->getSyncCount();

	return 0;
}


mp_sint32 PlayerGeneric::getRow() const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		return player->getRow(index);
	}

	return 0;
}


	virtual void masterMixerNotification(MasterMixer::MasterMixerNotifications notification)
	{
		player.adjustSettings();
	}


	void calculateMasterVolume()
	{
		if (lastPeakValue)
		{
			float v = 32768.0f*(1<<mixerShift) / (float)lastPeakValue;
			masterVolume = (mp_sint32)((float)masterVolume*v);
			if (masterVolume > 256)
				masterVolume = 256;
		}
	}


void PlayerGeneric::setRepeat(bool repeat)
{
	this->repeat = repeat;
	if (player)
		player->setRepeat(repeat);
}


void PlayerGeneric::reset()
{
	if (player)
		player->reset();
}


bool PlayerGeneric::isInitialized() const
{
	if (mixer)
		return mixer->isInitialized();

	return false;
}


void PlayerGeneric::setPanning(mp_ubyte chn, mp_ubyte pan)
{
	if (player)
		player->setPanning(chn, pan);
}


mp_sint32 PlayerGeneric::startPlaying(XModule* module,
									  bool repeat/* = false*/,
									  mp_uint32 startPosition/* = 0*/,
									  mp_uint32 startRow/* = 0*/,
									  mp_sint32 numChannels/* = -1*/,
									  const mp_ubyte* customPanningTable/* = NULL*/,
									  bool idle/* = false*/,
									  mp_sint32 patternIndex/* = -1*/,
									  bool playOneRowOnly/* = false*/)
{
	this->idle = idle;
	this->repeat = repeat;
	this->playOneRowOnly = playOneRowOnly;

	if (mixer == NULL)
	{
		mixer = new MasterMixer(frequency, bufferSize, 1, audioDriver);
		mixer->setMasterMixerNotificationListener(listener);
		mixer->setSampleShift(sampleShift);
		if (audioDriver == NULL)
			mixer->setCurrentAudioDriverByName(audioDriverName);
	}

	if (!player || player->getType() != getPreferredPlayerType(module))
	{
		if (player)
		{
			if (!mixer->isDeviceRemoved(player))
				mixer->removeDevice(player);
			delete player;
		}

		player = getPreferredPlayer(module);

		if (player)
		{
			// apply our own "state" to the state of the newly allocated player
			player->resetMainVolumeOnStartPlay(resetMainVolumeOnStartPlayFlag);
			player->resetOnStop(resetOnStopFlag);
			player->setBufferSize(bufferSize);
			player->setResamplerType(resamplerType);
			player->setMasterVolume(masterVolume);
			player->setPanningSeparation(panningSeparation);
			player->setPlayMode(playMode);

			for (mp_sint32 i = PlayModeOptionFirst; i < PlayModeOptionLast; i++)
				player->enable((PlayModeOptions)i, options[i]);

			player->setDisableMixing(disableMixing);
			player->setAllowFilters(allowFilters);
			//if (paused)
			//	player->pausePlaying();

			// adjust number of virtual channels if necessary
			setNumMaxVirChannels(numMaxVirChannels);
		}
	}

	if (player && mixer)
	{
		if (!mixer->isDeviceRemoved(player))
			mixer->removeDevice(player);

		player->startPlaying(module, repeat, startPosition, startRow, numChannels, customPanningTable, idle, patternIndex, playOneRowOnly);

		mixer->addDevice(player);

		if (!mixer->isPlaying())
			return mixer->start();
	}

	return MP_OK;
}


bool PlayerGeneric::isPaused() const
{
	if (mixer)
		return mixer->isPaused();

	return paused;
}


void PlayerGeneric::nextPattern()
{
	if (player)
		player->nextPattern();
}


void PlayerGeneric::resetSampleCounter()
{
	if (player)
		player->resetSampleCounter();
}


bool PlayerGeneric::getPowerOfTwoCompensationFlag() const
{
	return compensateBufferFlag;
}


mp_sint32 PlayerGeneric::getCurrentBeatIndex()
{
	if (player)
		return player->getBeatIndexFromSamplePos(getCurrentSamplePosition());

	return 0;
}


void PlayerGeneric::setPatternPos(mp_uint32 pos, mp_uint32 row/* = 0*/, bool resetChannels/* = true*/, bool resetFXMemory/* = true*/)
{
	if (player)
		player->setPatternPos(pos, row, resetChannels, resetFXMemory);
}


void PlayerGeneric::getPosition(mp_sint32& order, mp_sint32& row, mp_sint32& ticker) const
{
	if (player)
	{
		mp_uint32 index = player->getBeatIndexFromSamplePos(getCurrentSamplePosition());
		player->getPosition(order, row, ticker, index);
		return;
	}

	order = row = ticker = 0;
}