/*
 * libbrlapi - A library providing access to braille terminals for applications.
 *
 * Copyright (C) 2002-2024 by
 *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
 *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
 *
 * libbrlapi comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

/* api_client.c handles connection with BrlApi */

#ifdef __ANDROID__
#define __ANDROID_API__ 21
#endif /* __ANDROID__ */

#define WIN_ERRNO_STORAGE_CLASS static
#include "prologue.h"

#include <stdio.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <locale.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>

#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif /* HAVE_ALLOCA_H */

#ifndef __MINGW32__
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif /* HAVE_LANGINFO_H */
#endif /* __MINGW32__ */

#ifdef __MINGW32__
#include <ws2tcpip.h>
#include "win_pthread.h"

#include "win_errno.h"

#define syslog(level,fmt,...) fprintf(stderr,#level ": " fmt, ## __VA_ARGS__)

#else /* __MINGW32__ */
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <pthread.h>
#include <syslog.h>

#if defined(__APPLE__)
#include <mach/mach.h>
#include <mach/semaphore.h>

#else /* semaphore support */
#include <semaphore.h>
#endif /* semaphore support */

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */
#ifdef HAVE_SYS_POLL_H
#include <poll.h>
#endif /* HAVE_SYS_POLL_H */
#endif /* __MINGW32__ */

#include <sys/time.h>
#include "gettime.h"

#ifdef linux
#include <sys/sysmacros.h>
#include <linux/major.h>
#include <linux/tty.h>
#include <linux/vt.h>
#define MAXIMUM_VIRTUAL_CONSOLE MAX_NR_CONSOLES
#endif /* linux */

#ifdef __OpenBSD__
#define MAXIMUM_VIRTUAL_CONSOLE 16
#endif /* __OpenBSD__ */

#ifndef MAXIMUM_VIRTUAL_CONSOLE
#define MAXIMUM_VIRTUAL_CONSOLE 1
#endif /* MAXIMUM_VIRTUAL_CONSOLE */

#ifdef HAVE_SD_SESSION_GET_VT
#include <systemd/sd-login.h>
#endif /* HAVE_SD_SESSION_GET_VT */

#define BRLAPI_NO_DEPRECATED
#include "brlapi.h"
#include "brlapi_protocol.h"

#define BRLAPI(fun) brlapi_ ## fun
#include "brlapi_common.h"

#ifndef MIN
#define MIN(a, b) (((a) < (b))? (a): (b))
#endif /* MIN */

#ifndef MAX
#define MAX(a, b) (((a) > (b))? (a): (b))
#endif /* MAX */

/* API states */
#define STCONNECTED 1
#define STRAW 2
#define STSUSPEND 4
#define STCONTROLLINGTTY 8

#ifdef WINDOWS
#ifdef __MINGW32__
static WSADATA wsadata;
#endif /* __MINGW32__ */

static void* GetProc(const char *library, const char *fun) {
  HMODULE module;
  void *ret;
  if (!(module = LoadLibrary(library))) return (void*)(-1);
  if ((ret = GetProcAddress(module, fun))) return ret;
  FreeLibrary(module);
  return (void*)(-1);
}

#define CHECKPROC(library, name) \
  (name##Proc && name##Proc != (void*)(-1))
#define CHECKGETPROC(library, name) \
  (name##Proc != (void*)(-1) && (name##Proc || (name##Proc = GetProc(library,#name)) != (void*)(-1)))
#define WIN_PROC_STUB(name) typeof(name) (*name##Proc);


static WIN_PROC_STUB(GetConsoleWindow);

#ifdef __MINGW32__
static WIN_PROC_STUB(wcslen);

static WIN_PROC_STUB(getaddrinfo);
#define getaddrinfo(host,port,hints,res) getaddrinfoProc(host,port,hints,res)
static WIN_PROC_STUB(freeaddrinfo);
#define freeaddrinfo(res) freeaddrinfoProc(res)
#endif /* __MINGW32__ */
#endif /* WINDOWS */

/* We need to declare these as weak external references to determine
 * at runtime whether libpthread is used or not.
 * We also can't rely on the functions prototypes.
 */
#if defined(WINDOWS)

#elif defined(__APPLE__)
#pragma weak pthread_key_create
#pragma weak pthread_once
#pragma weak pthread_getspecific
#pragma weak pthread_setspecific

typedef semaphore_t sem_t;

static int
sem_init (sem_t *sem, int shared, int value) {
  return semaphore_create(mach_task_self(), sem, SYNC_POLICY_FIFO, value);
}

static int
sem_post (sem_t *sem) {
  return semaphore_signal(*sem);
}

static int
sem_wait (sem_t *sem) {
  return semaphore_wait(*sem);
}

static int
sem_timedwait (sem_t *sem, const struct timespec *abs) {
  struct timeval now;
  getRealTime(&now);

  mach_timespec_t rel = {
    .tv_sec = abs->tv_sec - now.tv_sec,
    .tv_nsec = abs->tv_nsec - (now.tv_usec * 1000)
  };

  if (rel.tv_nsec < 0) rel.tv_sec -= 1, rel.tv_nsec += 1000000000;
  if (rel.tv_sec < 0) rel.tv_sec = 0, rel.tv_nsec = 0;

  return semaphore_timedwait(*sem, rel);
}

static int
sem_trywait (sem_t *sem) {
  mach_timespec_t rel = { .tv_sec=0, .tv_nsec=0 };
  return semaphore_timedwait(*sem, rel);
}

static int
sem_destroy (sem_t *sem) {
  return semaphore_destroy(mach_task_self(), *sem);
}

#elif defined(__GNUC__) || defined(__sun__)
#pragma weak pthread_key_create
#pragma weak pthread_once
#pragma weak pthread_getspecific
#pragma weak pthread_setspecific
#pragma weak sem_init
#pragma weak sem_post
#pragma weak sem_wait
#pragma weak sem_destroy
#endif /* weak external references */

/** key presses buffer size
 *
 * key presses won't be lost provided no more than BRL_KEYBUF_SIZE key presses
 * are done between two calls to brlapi_read* if a call to another function is
 * done in the meanwhile (which needs somewhere to put them before being able
 * to get responses from the server)
*/
#define BRL_KEYBUF_SIZE 256

struct brlapi_parameterCallback_t {
  brlapi_param_t parameter;
  brlapi_param_subparam_t subparam;
  brlapi_param_flags_t flags;
  brlapi_paramCallback_t func;
  void *priv;
  struct brlapi_parameterCallback_t *prev, *next;
};

struct brlapi_handle_t { /* Connection-specific information */
  uint32_t serverVersion;
  unsigned int brlx;
  unsigned int brly;
  brlapi_fileDescriptor fileDescriptor; /* Descriptor of the socket connected to BrlApi */
  int addrfamily; /* Address family of the socket */
  /* to protect concurrent fd write operations */
  pthread_mutex_t fileDescriptor_mutex;
  /* to protect concurrent fd requests */
  pthread_mutex_t req_mutex;
  /* to protect concurrent key reading */
  pthread_mutex_t key_mutex;
  /* to protect concurrent fd key events and request answers, also protects the
   * key buffer */
  /* Only two threads might want to take it: one that already got
  * brlapi_req_mutex, or one that got key_mutex */
  pthread_mutex_t read_mutex;
  Packet packet;
  /* when someone is already reading (reading==1), put expected type,
   * address/size of buffer, and address of return value, then wait on semaphore,
   * the buffer gets filled, altRes too */
  int reading;
  brlapi_packetType_t altExpectedPacketType;
  unsigned char *altPacket;
  size_t altSize;
  ssize_t *altRes;
  sem_t *altSem;
  int state;
  pthread_mutex_t state_mutex;

#ifdef LC_GLOBAL_LOCALE
  locale_t default_locale;
#endif /* LC_GLOBAL_LOCALE */

  /* key presses buffer, for when key presses are received instead of
   * acknowledgements for instance
   *
   * every function must hence be able to read at least sizeof(brlapi_keyCode_t) */
  brlapi_keyCode_t keybuf[BRL_KEYBUF_SIZE];
  unsigned keybuf_next;
  unsigned keybuf_nb;
  union {
    brlapi_exceptionHandler_t withoutHandle;
    brlapi__exceptionHandler_t withHandle;
  } exceptionHandler;
  int exception_sync;
  int exception_error;
  pthread_mutex_t exceptionHandler_mutex;

  struct brlapi_parameterCallback_t *parameterCallbacks;
  /* This protects the callback list, but also callback calling conventions in
   * general:
   * callbacks are called in the update order, and not after unregistration */
  pthread_mutex_t callbacks_mutex;
  /* The callback might want to unregister itself, while we are processing the
   * list of callbacks. This records where we are in the list, to skip the
   * deleted item. */
  struct brlapi_parameterCallback_t *nextCallback;

  void *clientData; /* Private client data */
};

/* Function brlapi_getLibraryVersion */
void brlapi_getLibraryVersion(int *major, int *minor, int *revision)
{
  *major = BRLAPI_MAJOR;
  *minor = BRLAPI_MINOR;
  *revision = BRLAPI_REVISION;
}

/* Function brlapi_getHandleSize */
size_t BRLAPI_STDCALL brlapi_getHandleSize(void)
{
  return sizeof(brlapi_handle_t);
}

static brlapi_handle_t defaultHandle;

/* brlapi_initializeHandle */
/* Initialize a BrlAPI handle */
static void brlapi_initializeHandle(brlapi_handle_t *handle)
{
  handle->brlx = 0;
  handle->brly = 0;
  handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
  handle->addrfamily = 0;
  pthread_mutex_init(&handle->fileDescriptor_mutex, NULL);
  pthread_mutex_init(&handle->req_mutex, NULL);
  pthread_mutex_init(&handle->key_mutex, NULL);
  pthread_mutex_init(&handle->read_mutex, NULL);
  brlapi_initializePacket(&handle->packet);
  handle->reading = 0;
  handle->altExpectedPacketType = 0;
  handle->altPacket = NULL;
  handle->altSize = 0;
  handle->altRes = NULL;
  handle->altSem = NULL;
  handle->state = 0;
  pthread_mutex_init(&handle->state_mutex, NULL);

#ifdef LC_GLOBAL_LOCALE
  handle->default_locale = LC_GLOBAL_LOCALE;
#endif /* LC_GLOBAL_LOCALE */

  memset(handle->keybuf, 0, sizeof(handle->keybuf));
  handle->keybuf_next = 0;
  handle->keybuf_nb = 0;
  if (handle == &defaultHandle)
    handle->exceptionHandler.withoutHandle = brlapi_defaultExceptionHandler;
  else
    handle->exceptionHandler.withHandle = brlapi__defaultExceptionHandler;
  handle->exception_sync = 0;
  handle->exception_error = BRLAPI_ERROR_SUCCESS;
  pthread_mutex_init(&handle->exceptionHandler_mutex, NULL);
  handle->parameterCallbacks = NULL;
  {
    pthread_mutexattr_t mattr;
    pthread_mutexattr_init(&mattr);
    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&handle->callbacks_mutex, &mattr);
  }
  handle->nextCallback = NULL;
  handle->clientData = NULL;
}

/* brlapi_doWaitForPacket */
/* Waits for the specified type of packet: must be called with brlapi_req_mutex locked */
/* deadline can be used to stop waiting after a given date, or wait forever (NULL) */
/* If the right packet type arrives, returns its size */
/* Returns -1 if a non-fatal error is encountered */
/* Returns -2 on end of file */
/* Returns -3 if the available packet was not for us */
/* Returns -4 on timeout (if deadline is not NULL) */
/* Calls the exception handler if an exception is encountered */
static ssize_t brlapi__doWaitForPacket(brlapi_handle_t *handle, brlapi_packetType_t expectedPacketType, void *packet, size_t packetSize, struct timeval *deadline)
{
  uint32_t *uint32Packet = handle->packet.content;
  const brlapi_packet_t *localPacket = (brlapi_packet_t *) uint32Packet;
  const brlapi_errorPacket_t *errorPacket = &localPacket->error;

  uint32_t size;
  brlapi_packetType_t type;
  int ret = 0;

  struct timeval now;
  long delay = 0;
  int polled = 0; /* Whether we have polled at least once */

  do {
    if (deadline) {
      getRealTime(&now);
      delay = (deadline->tv_sec  - now.tv_sec ) * 1000 +
	      (deadline->tv_usec - now.tv_usec) / 1000;
      if (delay < 0) {
	if (polled) {
	  /* The deadline has expired, don't wait more */
	  return -4;
	}
	/* Poll at least once */
	delay = 0;
      }
    }
    polled = 1;
#ifdef __MINGW32__
    DWORD dw;
    dw = WaitForSingleObject(handle->packet.overl.hEvent, deadline ? delay : INFINITE);
    if (dw == WAIT_FAILED) {
      setSystemErrno();
      LibcError("waiting for packet");
      return -2;
    }

    if (dw == WAIT_OBJECT_0)
#else /* __MINGW32__ */
#ifdef HAVE_POLL
    struct pollfd pollfd;

    pollfd.fd = handle->fileDescriptor;
    pollfd.events = POLLIN;
    pollfd.revents = 0;

    if (poll(&pollfd, 1, deadline ? delay : -1) < 0) {
      LibcError("waiting for packet");
      return -2;
    }

    if (pollfd.revents & POLLIN)
#else /* HAVE_POLL */
    fd_set sockset;
    struct timeval timeout, *ptimeout = NULL;
    if (deadline) {
      timeout.tv_sec = delay / 1000;
      timeout.tv_usec = (delay % 1000) * 1000;
      ptimeout = &timeout;
    }

    FD_ZERO(&sockset);
    FD_SET(handle->fileDescriptor, &sockset);
    if (select(handle->fileDescriptor+1, &sockset, NULL, NULL, ptimeout) < 0) {
      LibcError("waiting for packet");
      return -2;
    }

    if (FD_ISSET(handle->fileDescriptor, &sockset))
#endif /* !HAVE_POLL */
#endif /* __MINGW32__ */
    {
      /* Some data is here, read it */
      ret = brlapi__readPacket(&handle->packet, handle->fileDescriptor);
      if (ret == -1) {
	LibcError("reading packet");
      }
      if (ret < 0) {
	/* error or end of file */
	return -2;
      }
    }
  } while (ret == 0);

  /* Got a packet, process it.  */
  size = handle->packet.header.size;
  type = handle->packet.header.type;

  if (type==expectedPacketType)
  {
    /* For us, just copy */
    memcpy(packet, handle->packet.content, MIN(packetSize, size));
    return size;
  }

  /* Not for us. For alternate reader? */
  pthread_mutex_lock(&handle->read_mutex);
  if (handle->altSem && type==handle->altExpectedPacketType) {
    /* Yes, put packet content there */
    memcpy(handle->altPacket, handle->packet.content, MIN(handle->altSize, size));
    *handle->altRes = size;
#ifndef WINDOWS
    if (sem_post)
#endif /* WINDOWS */
      sem_post(handle->altSem);
    handle->altSem = NULL;
    pthread_mutex_unlock(&handle->read_mutex);
    return -3;
  }
  /* No alternate reader, read it locally... */
  if ((type==BRLAPI_PACKET_KEY) && (handle->state & STCONTROLLINGTTY) && (size==sizeof(brlapi_keyCode_t))) {
    /* keypress, buffer it */
    if (handle->keybuf_nb>=BRL_KEYBUF_SIZE) {
      syslog(LOG_WARNING,"lost key: 0X%8lx%8lx\n",(unsigned long)ntohl(uint32Packet[0]),(unsigned long)ntohl(uint32Packet[1]));
    } else {
      handle->keybuf[(handle->keybuf_next+handle->keybuf_nb++)%BRL_KEYBUF_SIZE]
          = brlapi_packetToKeyCode(uint32Packet);
    }
    pthread_mutex_unlock(&handle->read_mutex);
    return -3;
  }
  if (type==BRLAPI_PACKET_PARAM_UPDATE) {
    /* Parameter update, find handler */
    brlapi_paramValuePacket_t *value = (void*) handle->packet.content;
    brlapi_param_flags_t flags = ntohl(value->flags);
    brlapi_param_t param = ntohl(value->param);
    brlapi_param_subparam_t subparam = ((brlapi_param_subparam_t)ntohl(value->subparam_hi) << 32) | ntohl(value->subparam_lo);
    size_t rlen = size - sizeof(flags) - sizeof(param) - sizeof(subparam);
    _brlapi_ntohParameter(param, value, rlen);
    pthread_mutex_lock(&handle->callbacks_mutex);
    for(handle->nextCallback = handle->parameterCallbacks;
	handle->nextCallback; ) {
      struct brlapi_parameterCallback_t *callback = handle->nextCallback;
      handle->nextCallback = handle->nextCallback->next;
      if (callback->parameter == param &&
	  callback->subparam == subparam &&
	  (callback->flags & BRLAPI_PARAMF_GLOBAL) == (flags & BRLAPI_PARAMF_GLOBAL)) {
	callback->func(param, subparam, callback->flags, callback->priv, value->data, rlen);
	/* Note: the callback might have removed this entry */
      }
    }
    handle->nextCallback = NULL;
    pthread_mutex_unlock(&handle->callbacks_mutex);
  }
  pthread_mutex_unlock(&handle->read_mutex);

  /* otherwise this is an error */

  if (type==BRLAPI_PACKET_ERROR) {
    brlapi_errno = ntohl(errorPacket->code);
    return -1;
  }

  if (type==BRLAPI_PACKET_EXCEPTION) {
    size_t esize;
    int hdrSize = sizeof(errorPacket->code)+sizeof(errorPacket->type);
    int err = ntohl(errorPacket->code);

    if (size<hdrSize)
      esize = 0;
    else
      esize = size-hdrSize;

    pthread_mutex_lock(&handle->exceptionHandler_mutex);
    if (handle->exception_sync) {
      handle->exception_error = err;
      pthread_mutex_unlock(&handle->exceptionHandler_mutex);
      return -3;
    }
    if (handle==&defaultHandle)
      defaultHandle.exceptionHandler.withoutHandle(err, ntohl(errorPacket->type), &errorPacket->packet, esize);
    else
      handle->exceptionHandler.withHandle(handle, err, ntohl(errorPacket->type), &errorPacket->packet, esize);
    pthread_mutex_unlock(&handle->exceptionHandler_mutex);

    pthread_mutex_lock(&handle->fileDescriptor_mutex);
    closeFileDescriptor(handle->fileDescriptor);
    handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
    pthread_mutex_unlock(&handle->fileDescriptor_mutex);

    return -2;
  }

  syslog(LOG_ERR,"(brlapi_waitForPacket) Received unexpected packet of type %s and size %ld\n",brlapi_getPacketTypeName(type),(long)size);
  return -3;
}

#define TRY_WAIT_FOR_EXPECTED_PACKET 0
#define WAIT_FOR_EXPECTED_PACKET 1

#define POLL 0
#define WAIT_FOREVER (-1)

/* brlapi_waitForPacket */
/* same as brlapi_doWaitForPacket, but sleeps instead of reading if another
 * thread is already reading, and timeout_ms expressed as relative ms delay
 * instead of absolute deadline.
 * timeout_ms set to WAIT_FOREVER means no deadline.
 * Never returns -2. If loop is WAIT_FOR_EXPECTED_PACKET, never returns -3.
 */
static ssize_t brlapi__waitForPacket(brlapi_handle_t *handle, brlapi_packetType_t expectedPacketType, void *packet, size_t size, int loop, int timeout_ms) {
  int doread;
  ssize_t res;
  sem_t sem;
  struct timeval deadline, *pdeadline = NULL;
  if (timeout_ms >= 0) {
    getRealTime(&deadline);
    deadline.tv_sec += timeout_ms / 1000;
    deadline.tv_usec += (timeout_ms % 1000) * 1000;
    if (deadline.tv_usec >= 1000000) {
      deadline.tv_sec++;
      deadline.tv_usec -= 1000000;
    }
    pdeadline = &deadline;
  }
again:
  doread = 0;
  pthread_mutex_lock(&handle->read_mutex);
  if (!handle->reading) {
    doread = handle->reading = 1;
  } else {
    if (
#ifndef WINDOWS
      !sem_init || !sem_post || !sem_wait || !sem_destroy ||
#endif /* WINDOWS */
	handle->altSem) {
      /* This can't happen without threads */
      pthread_mutex_unlock(&handle->read_mutex);
      syslog(LOG_ERR,"third call to brlapi_waitForPacket !");
      brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
      return -1;
    }

    handle->altExpectedPacketType = expectedPacketType;
    handle->altPacket = packet;
    handle->altSize = size;
    handle->altRes = &res;
    sem_init(&sem, 0, 0);
    handle->altSem = &sem;
  }
  pthread_mutex_unlock(&handle->read_mutex);
  if (doread) {
    do {
      res = brlapi__doWaitForPacket(handle, expectedPacketType, packet, size, pdeadline);
    } while (loop && (res == -3 || (res == -1 && brlapi_errno == BRLAPI_ERROR_LIBCERR && (
	  brlapi_libcerrno == EINTR ||
#ifdef EWOULDBLOCK
	  brlapi_libcerrno == EWOULDBLOCK ||
#endif /* EWOULDBLOCK */
	  brlapi_libcerrno == EAGAIN))));
    pthread_mutex_lock(&handle->read_mutex);
    if (handle->altSem) {
      *handle->altRes = -3; /* no packet for him */
#ifndef WINDOWS
      if (sem_post)
#endif /* WINDOWS */
        sem_post(handle->altSem);
      handle->altSem = NULL;
    }
    handle->reading = 0;
    pthread_mutex_unlock(&handle->read_mutex);
  } else {
    int ret;
    if (timeout_ms == 0) {
      ret = sem_trywait(&sem);
    } else if (timeout_ms > 0) {
      struct timespec abs_timeout;
      abs_timeout.tv_sec = deadline.tv_sec;
      abs_timeout.tv_nsec = deadline.tv_usec * 1000;
      ret = sem_timedwait(&sem, &abs_timeout);
    } else {
      do {
	ret = sem_wait(&sem);
      } while (ret && errno == EINTR);
    }
    if (ret) {
      /* Timeout, drop our semaphore */
      pthread_mutex_lock(&handle->read_mutex);
      handle->altSem = NULL;
      pthread_mutex_unlock(&handle->read_mutex);
      res = -4;
    }
    sem_destroy(&sem);
    if (res == -1 /* He got an error, we want to read it too */
	|| (res == -3 && loop)) /* reader hadn't any packet for us */
      goto again;
  }
  if (res==-2) {
    res = -1;
    brlapi_errno = BRLAPI_ERROR_EOF;
  }
  return res;
}

/* brlapi_waitForAck */
/* Wait for an acknowledgement, must be called with brlapi_req_mutex locked */
static int brlapi__waitForAck(brlapi_handle_t *handle)
{
  return brlapi__waitForPacket(handle, BRLAPI_PACKET_ACK, NULL, 0, WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER);
}

/* brlapi_writePacketWaitForAck */
/* write a packet and wait for an acknowledgement */
static int brlapi__writePacketWaitForAck(brlapi_handle_t *handle, brlapi_packetType_t type, const void *buf, size_t size)
{
  ssize_t res;
  pthread_mutex_lock(&handle->req_mutex);
  if ((res=brlapi_writePacket(handle->fileDescriptor, type,buf,size))<0) {
    pthread_mutex_unlock(&handle->req_mutex);
    return res;
  }
  res=brlapi__waitForAck(handle);
  pthread_mutex_unlock(&handle->req_mutex);
  return res;
}

/* brlapi__pause */
/* Wait for an event to be received */
int BRLAPI_STDCALL brlapi__pause(brlapi_handle_t *handle, int timeout_ms) {
  ssize_t res = brlapi__waitForPacket(handle, 0, NULL, 0, TRY_WAIT_FOR_EXPECTED_PACKET, timeout_ms);
  if (res == -3) {
    brlapi_libcerrno = EINTR;
    brlapi_errno = BRLAPI_ERROR_LIBCERR;
    brlapi_errfun = "waitForPacket";
    res = -1;
  }
  if (res == -4) {
    /* Timeout */
    res = 0;
  }
  return res;
}

/* brlapi_pause */
int BRLAPI_STDCALL brlapi_pause(int timeout_ms) {
  return brlapi__pause(&defaultHandle, timeout_ms);
}


/* brlapi__sync */
int BRLAPI_STDCALL brlapi__sync(brlapi_handle_t *handle) {
  int res, error;

  pthread_mutex_lock(&handle->exceptionHandler_mutex);
  handle->exception_sync++;
  error = handle->exception_error;
  handle->exception_error = BRLAPI_ERROR_SUCCESS;
  pthread_mutex_unlock(&handle->exceptionHandler_mutex);

  if (error != BRLAPI_ERROR_SUCCESS) {
    pthread_mutex_lock(&handle->exceptionHandler_mutex);
    handle->exception_sync--;
    pthread_mutex_unlock(&handle->exceptionHandler_mutex);
    brlapi_errno = error;
    return -1;
  }

  res = brlapi__writePacketWaitForAck(handle,BRLAPI_PACKET_SYNCHRONIZE,NULL,0);

  if (res == -1) {
    pthread_mutex_lock(&handle->exceptionHandler_mutex);
    handle->exception_sync--;
    pthread_mutex_unlock(&handle->exceptionHandler_mutex);
    return -1;
  }

  pthread_mutex_lock(&handle->exceptionHandler_mutex);
  handle->exception_sync--;
  error = handle->exception_error;
  handle->exception_error = BRLAPI_ERROR_SUCCESS;
  pthread_mutex_unlock(&handle->exceptionHandler_mutex);

  if (error != BRLAPI_ERROR_SUCCESS) {
    brlapi_errno = error;
    return -1;
  }
  return 0;
}

/* brlapi_sync */
int BRLAPI_STDCALL brlapi_sync(void) {
  return brlapi__sync(&defaultHandle);
}

/* Function: tryHost */
/* Tries to connect to the given host. */
static int tryHost(brlapi_handle_t *handle, const char *hostAndPort) {
  char *host = NULL;
  char *port;
  SocketDescriptor sockfd = -1;

#ifdef __MINGW32__
  if (WSAStartup(
#ifdef HAVE_GETADDRINFO
	  MAKEWORD(2,0),
#else /* HAVE_GETADDRINFO */
	  MAKEWORD(1,1),
#endif /* HAVE_GETADDRINFO */
	  &wsadata))
    return -1;
#endif /* __MINGW32__ */

  handle->addrfamily = brlapi_expandHost(hostAndPort,&host,&port);

#if defined(PF_LOCAL)
  if (handle->addrfamily == PF_LOCAL) {
    int lpath = strlen(BRLAPI_SOCKETPATH),lport;
    lport = strlen(port);

#ifdef __MINGW32__
    {
      HANDLE pipefd;
      char path[lpath+lport+1];

      memcpy(path, BRLAPI_SOCKETPATH, lpath);
      memcpy(path+lpath, port, lport+1);

      while ((pipefd = CreateFile(path,GENERIC_READ|GENERIC_WRITE,
	      FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,SECURITY_IMPERSONATION|FILE_FLAG_OVERLAPPED,NULL))
	  == INVALID_HANDLE_VALUE) {
	if (GetLastError() != ERROR_PIPE_BUSY) {
	  brlapi_errfun = "CreateFile";
	  goto outlibc;
	}

	WaitNamedPipe(path, NMPWAIT_WAIT_FOREVER);
      }

      sockfd = (SocketDescriptor)pipefd;
    }
#else /* __MINGW32__ */
    {
      struct sockaddr_un sa;

      if (lpath+1+lport+1 > sizeof(sa.sun_path)) {
	brlapi_libcerrno=ENAMETOOLONG;
	brlapi_errfun="path";
	brlapi_errno = BRLAPI_ERROR_LIBCERR;
	goto out;
      }

      if ((sockfd = socket(PF_LOCAL, SOCK_STREAM, 0))<0) {
        brlapi_errfun="socket";
        setSocketErrno();
        goto outlibc;
      }

#if !defined(HAVE_POLL)
      if (sockfd >= FD_SETSIZE) {
	/* Will not be able to call select() on this */
	closeFileDescriptor(sockfd);
	brlapi_errfun="socket";
	setErrno(EMFILE);
	goto outlibc;
      }
#endif /* !HAVE_POLL */

      sa.sun_family = AF_LOCAL;
      memcpy(sa.sun_path,BRLAPI_SOCKETPATH "/",lpath+1);
      memcpy(sa.sun_path+lpath+1,port,lport+1);

      if (connect(sockfd, (struct sockaddr *) &sa, sizeof(sa))<0) {
        brlapi_errfun="connect";
        setSocketErrno();
        goto outlibc;
      }
    }
#endif /* __MINGW32__ */

    handle->fileDescriptor = (FileDescriptor) sockfd;
  } else {
#else /* PF_LOCAL */
  if (0) {} else {
#endif /* PF_LOCAL */

#ifdef __MINGW32__
    if (CHECKGETPROC("ws2_32.dll",getaddrinfo)
	&& CHECKGETPROC("ws2_32.dll",freeaddrinfo)) {
#endif /* __MINGW32__ */
#if defined(HAVE_GETADDRINFO) || defined(__MINGW32__)

    struct addrinfo *res,*cur;
    struct addrinfo hints;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    brlapi_gaierrno = getaddrinfo(host, port, &hints, &res);
    if (brlapi_gaierrno) {
      brlapi_errno=BRLAPI_ERROR_GAIERR;
      brlapi_libcerrno=errno;
      goto out;
    }
    for(cur = res; cur; cur = cur->ai_next) {
      sockfd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
      if (sockfd<0) continue;

#if !defined(__MINGW32__) && !defined(HAVE_POLL)
      if (sockfd >= FD_SETSIZE) {
	/* Will not be able to call select() on this */
	closeFileDescriptor(sockfd);
	brlapi_errfun="socket";
	setErrno(EMFILE);
	goto outlibc;
      }
#endif /* !__MINGW32__ && !HAVE_POLL */

      if (connect(sockfd, cur->ai_addr, cur->ai_addrlen)<0) {
        closeSocketDescriptor(sockfd);
        continue;
      }
      break;
    }
    freeaddrinfo(res);
    if (!cur) {
      brlapi_errno=BRLAPI_ERROR_CONNREFUSED;
      goto out;
    }

#endif /* HAVE_GETADDRINFO */
#ifdef __MINGW32__
    } else {
#endif /* __MINGW32__ */
#if !defined(HAVE_GETADDRINFO) || defined(__MINGW32__)

    struct sockaddr_in addr;
    struct hostent *he;

    memset(&addr,0,sizeof(addr));
    addr.sin_family = AF_INET;
    if (!port)
      addr.sin_port = htons(BRLAPI_SOCKETPORTNUM);
    else {
      char *c;
      addr.sin_port = htons(strtol(port, &c, 0));
      if (*c) {
	struct servent *se;

	if (!(se = getservbyname(port,"tcp"))) {
	  brlapi_gaierrno = h_errno;
          brlapi_errno=BRLAPI_ERROR_GAIERR;
	  goto out;
	}
	addr.sin_port = se->s_port;
      }
    }

    if (!host) {
      addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    } else if ((addr.sin_addr.s_addr = inet_addr(host)) == htonl(INADDR_NONE)) {
      if (!(he = gethostbyname(host))) {
	brlapi_gaierrno = h_errno;
	brlapi_errno = BRLAPI_ERROR_GAIERR;
	goto out;
      }
      if (he->h_addrtype != AF_INET) {
#ifdef EAFNOSUPPORT
        errno = EAFNOSUPPORT;
#else /* EAFNOSUPPORT */
        errno = EINVAL;
#endif /* EAFNOSUPPORT */
	brlapi_errfun = "gethostbyname";
	goto outlibc;
      }
      if (he->h_length > sizeof(addr.sin_addr)) {
        errno = EINVAL;
	brlapi_errfun = "gethostbyname";
	goto outlibc;
      }
      memcpy(&addr.sin_addr,he->h_addr,he->h_length);
    }

    sockfd = socket(addr.sin_family, SOCK_STREAM, 0);
    if (sockfd<0) {
      brlapi_errfun = "socket";
      setSocketErrno();
      goto outlibc;
    }

#if !defined(__MINGW32__) && !defined(HAVE_POLL)
    if (sockfd >= FD_SETSIZE) {
      /* Will not be able to call select() on this */
      closeFileDescriptor(sockfd);
      brlapi_errfun="socket";
      setErrno(EMFILE);
      goto outlibc;
    }
#endif /* !__MINGW32__ && !HAVE_POLL */

    if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr))<0) {
      brlapi_errfun = "connect";
      setSocketErrno();
      goto outlibc;
    }

#endif /* !HAVE_GETADDRINFO */
#ifdef __MINGW32__
    }
#endif /* __MINGW32__ */

#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
    {
      int yes=1;
      setsockopt(sockfd,IPPROTO_TCP,TCP_NODELAY,(void*)&yes,sizeof(yes));
    }
#endif /* defined(IPPROTO_TCP) && defined(TCP_NODELAY) */
    handle->fileDescriptor = (FileDescriptor) sockfd;
  }
  free(host);
  free(port);
  return 0;

outlibc:
  brlapi_errno = BRLAPI_ERROR_LIBCERR;
  brlapi_libcerrno = errno;
  if (sockfd>=0)
    closeSocketDescriptor(sockfd);
out:
  free(host);
  free(port);
  return -1;
}

/* Function : updateSettings */
/* Updates the content of a brlapi_connectionSettings_t structure according to */
/* another structure of the same type */
static void updateSettings(brlapi_connectionSettings_t *s1, const brlapi_connectionSettings_t *s2)
{
  if (s2==NULL) return;
  if ((s2->auth) && (*s2->auth))
    s1->auth = s2->auth;
  if ((s2->host) && (*s2->host))
    s1->host = s2->host;
}

/* Function: brlapi_openConnection
 * Creates a socket to connect to BrlApi */
brlapi_fileDescriptor BRLAPI_STDCALL brlapi__openConnection(brlapi_handle_t *handle, const brlapi_connectionSettings_t *clientSettings, brlapi_connectionSettings_t *usedSettings)
{
  brlapi_packet_t packet;
  brlapi_packet_t serverPacket;
  brlapi_authClientPacket_t *auth = &packet.authClient;
  brlapi_authServerPacket_t *authServer = &serverPacket.authServer;
  brlapi_versionPacket_t *version = &serverPacket.version;
  uint32_t *type;
  int len;

  brlapi_connectionSettings_t settings = { BRLAPI_DEFAUTH, ":0" };
  brlapi_connectionSettings_t envsettings = { getenv("BRLAPI_AUTH"), getenv("BRLAPI_HOST") };

  /* Here update settings with the parameters from misc sources (files, env...) */
  updateSettings(&settings, &envsettings);
  updateSettings(&settings, clientSettings);
  if (usedSettings!=NULL) updateSettings(usedSettings, &settings);
  brlapi_initializeHandle(handle);

  if (tryHost(handle, settings.host)<0) {
    const char *port = strrchr(settings.host, ':');
    if (port != settings.host) goto out;
    if (!isPortNumber(port+1, NULL)) goto out;

    brlapi_error_t originalError = brlapi_error;
    size_t portLength = strlen(port);

    static const char *const localhostAddressTable[] = {
      LOCALHOST_ADDRESS_IPV4,

      #ifdef AF_INET6
      LOCALHOST_ADDRESS_IPV6,
      #endif /* AF_INET6 */
    };

    const char *const *lha = localhostAddressTable;
    const char *const *end = lha + ARRAY_COUNT(localhostAddressTable);

    while (lha < end) {
      size_t hostLength = strlen(*lha);
      char host[hostLength + portLength + 1];
      snprintf(host, sizeof(host), "%s%s", *lha, port);

      if (tryHost(handle, host) != -1) {
        if (usedSettings) usedSettings->host = strdup(host);
        break;
      }

      lha += 1;
    }

    if (lha == end) {
      brlapi_error = originalError;
      goto out;
    }
  }

  if ((len = brlapi__waitForPacket(handle, BRLAPI_PACKET_VERSION, &serverPacket, sizeof(serverPacket), WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER)) < 0)
    goto outfd;

  handle->serverVersion = ntohl(version->protocolVersion);
  if (handle->serverVersion < 8) {
    /* We only provide compatibility with version 8 and later. */
    brlapi_errno = BRLAPI_ERROR_PROTOCOL_VERSION;
    goto outfd;
  }

  version->protocolVersion = htonl(BRLAPI_PROTOCOL_VERSION);
  if (brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_VERSION, version, sizeof(*version)) < 0)
    goto outfd;

  if ((len = brlapi__waitForPacket(handle, BRLAPI_PACKET_AUTH, &serverPacket, sizeof(serverPacket), WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER)) < 0)
    return BRLAPI_INVALID_FILE_DESCRIPTOR;

  for (type = &authServer->type[0]; type < &authServer->type[len / sizeof(authServer->type[0])]; type++) {
    auth->type = *type;
    switch (ntohl(*type)) {
      case BRLAPI_AUTH_NONE:
	if (usedSettings) usedSettings->auth = "none";
	goto done;
      case BRLAPI_AUTH_KEY: {
        size_t authKeyLength;
	int res;
	char *keyfile = brlapi_getKeyFile(settings.auth);
	if (!keyfile)
	  continue;
	res = brlapi_loadAuthKey(keyfile, &authKeyLength, (void *) &auth->key);
	free(keyfile);
        if (res < 0)
	  continue;
        res = brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_AUTH, auth,
	  sizeof(auth->type)+authKeyLength);
	memset(&auth->key, 0, authKeyLength);
	if (res < 0)
	  goto outfd;
	if (usedSettings) usedSettings->auth = settings.auth;
	break;
      }
      default:
        /* unsupported authorization type */
	continue;
    }
    if ((brlapi__waitForAck(handle)) == 0) goto done;
  }

  /* no suitable authorization method */
  brlapi_errno = BRLAPI_ERROR_AUTHENTICATION;
outfd:
  closeFileDescriptor(handle->fileDescriptor);
  handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
out:
  return BRLAPI_INVALID_FILE_DESCRIPTOR;

done:
  pthread_mutex_lock(&handle->state_mutex);
  handle->state = STCONNECTED;
  pthread_mutex_unlock(&handle->state_mutex);
  return handle->fileDescriptor;
}

brlapi_fileDescriptor BRLAPI_STDCALL brlapi_openConnection(const brlapi_connectionSettings_t *clientSettings, brlapi_connectionSettings_t *usedSettings)
{
  return brlapi__openConnection(&defaultHandle, clientSettings, usedSettings);
}

brlapi_fileDescriptor BRLAPI_STDCALL brlapi__getFileDescriptor(brlapi_handle_t *handle)
{
  return handle->fileDescriptor;
}

brlapi_fileDescriptor BRLAPI_STDCALL brlapi_getFileDescriptor(void)
{
  return brlapi__getFileDescriptor(&defaultHandle);
}

/* brlapi_closeConnection */
/* Cleanly close the socket */
void BRLAPI_STDCALL brlapi__closeConnection(brlapi_handle_t *handle)
{
  pthread_mutex_lock(&handle->state_mutex);
  handle->state = 0;
  pthread_mutex_unlock(&handle->state_mutex);
  pthread_mutex_lock(&handle->fileDescriptor_mutex);
  closeFileDescriptor(handle->fileDescriptor);
  handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
  pthread_mutex_unlock(&handle->fileDescriptor_mutex);

#ifdef LC_GLOBAL_LOCALE
  if (handle->default_locale != LC_GLOBAL_LOCALE) {
    freelocale(handle->default_locale);
  }
#endif /* LC_GLOBAL_LOCALE */

#ifdef __MINGW32__
  WSACleanup();
#endif /* __MINGW32__ */
}

void BRLAPI_STDCALL brlapi_closeConnection(void)
{
  brlapi__closeConnection(&defaultHandle);
}

/* brlapi__setClientData */
void BRLAPI_STDCALL brlapi__setClientData(brlapi_handle_t *handle, void *data)
{
  handle->clientData = data;
}

void BRLAPI_STDCALL brlapi_setClientData(void *data)
{
  brlapi__setClientData(&defaultHandle, data);
}

/* brlapi__getClientData */
void * BRLAPI_STDCALL brlapi__getClientData(brlapi_handle_t *handle)
{
  return handle->clientData;
}

void * BRLAPI_STDCALL brlapi_getClientData(void)
{
  return brlapi__getClientData(&defaultHandle);
}

/* brlapi_getDriverSpecific */
/* Switch to device specific mode */
static int brlapi__getDriverSpecific(brlapi_handle_t *handle, const char *driver, brlapi_packetType_t type, int st)
{
  int res;
  brlapi_packet_t packet;
  brlapi_getDriverSpecificModePacket_t *driverPacket = &packet.getDriverSpecificMode;
  unsigned int n = strlen(driver);
  if (n>BRLAPI_MAXNAMELENGTH) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }
  pthread_mutex_lock(&defaultHandle.state_mutex);
  if ((defaultHandle.state & st)) {
    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
    res = -1;
    goto out;
  }
  driverPacket->magic = htonl(BRLAPI_DEVICE_MAGIC);
  driverPacket->nameLength = n;
  memcpy(&driverPacket->name, driver, n);
  res = brlapi__writePacketWaitForAck(handle, type, &packet, sizeof(uint32_t)+1+n);
  if (res!=-1)
    handle->state |= st;
out:
  pthread_mutex_unlock(&handle->state_mutex);
  return res;
}

/* brlapi_leaveDriverSpecific */
/* Leave device specific mode */
static int brlapi__leaveDriverSpecific(brlapi_handle_t *handle, brlapi_packetType_t type, int st)
{
  int res;
  pthread_mutex_lock(&handle->state_mutex);
  if (!(handle->state & st)) {
    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
    res = -1;
    goto out;
  }
  res = brlapi__writePacketWaitForAck(handle, type, NULL, 0);
  if (!res)
    handle->state &= ~st;
out:
  pthread_mutex_unlock(&handle->state_mutex);
  return res;
}

/* brlapi_enterRawMode */
/* Switch to Raw mode */
int BRLAPI_STDCALL brlapi__enterRawMode(brlapi_handle_t *handle, const char *driver)
{
  return brlapi__getDriverSpecific(handle, driver, BRLAPI_PACKET_ENTERRAWMODE, STRAW);
}

int BRLAPI_STDCALL brlapi_enterRawMode(const char *driver)
{
  return brlapi__enterRawMode(&defaultHandle, driver);
}

/* brlapi_leaveRawMode */
/* Leave Raw mode */
int BRLAPI_STDCALL brlapi__leaveRawMode(brlapi_handle_t *handle)
{
  return brlapi__leaveDriverSpecific(handle, BRLAPI_PACKET_LEAVERAWMODE, STRAW);
}

int BRLAPI_STDCALL brlapi_leaveRawMode(void)
{
  return brlapi__leaveRawMode(&defaultHandle);
}

/* brlapi_sendRaw */
/* Send a Raw Packet */
ssize_t BRLAPI_STDCALL brlapi__sendRaw(brlapi_handle_t *handle, const void *buf, size_t size)
{
  ssize_t res;
  pthread_mutex_lock(&handle->fileDescriptor_mutex);
  res=brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_PACKET, buf, size);
  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
  return res;
}

ssize_t BRLAPI_STDCALL brlapi_sendRaw(const void *buf, size_t size)
{
  return brlapi__sendRaw(&defaultHandle, buf, size);
}

/* brlapi_recvRaw */
/* Get a Raw packet */
ssize_t BRLAPI_STDCALL brlapi__recvRaw(brlapi_handle_t *handle, void *buf, size_t size)
{
  ssize_t res;
  if (!(handle->state & STRAW)) {
    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
    return -1;
  }
  res = brlapi__waitForPacket(handle, BRLAPI_PACKET_PACKET, buf, size, TRY_WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER);
  if (res == -3) {
    brlapi_libcerrno = EINTR;
    brlapi_errno = BRLAPI_ERROR_LIBCERR;
    brlapi_errfun = "waitForPacket";
    res = -1;
  }
  return res;
}

ssize_t BRLAPI_STDCALL brlapi_recvRaw(void *buf, size_t size)
{
  return brlapi__recvRaw(&defaultHandle, buf, size);
}

/* brlapi_suspendDriver */
int BRLAPI_STDCALL brlapi__suspendDriver(brlapi_handle_t *handle, const char *driver)
{
  return brlapi__getDriverSpecific(handle, driver, BRLAPI_PACKET_SUSPENDDRIVER, STSUSPEND);
}

int BRLAPI_STDCALL brlapi_suspendDriver(const char *driver)
{
  return brlapi__suspendDriver(&defaultHandle, driver);
}

/* brlapi_resumeDriver */
int BRLAPI_STDCALL brlapi__resumeDriver(brlapi_handle_t *handle)
{
  return brlapi__leaveDriverSpecific(handle, BRLAPI_PACKET_RESUMEDRIVER, STSUSPEND);
}

int BRLAPI_STDCALL brlapi_resumeDriver(void)
{
  return brlapi__resumeDriver(&defaultHandle);
}

/* Function brlapi_request */
/* Sends a request to the API and waits for the answer */
/* The answer is put in the given packet */
static ssize_t brlapi__request(brlapi_handle_t *handle, brlapi_packetType_t request, void *packet, size_t size)
{
  ssize_t res;
  pthread_mutex_lock(&handle->req_mutex);
  res = brlapi_writePacket(handle->fileDescriptor, request, NULL, 0);
  if (res==-1) {
    pthread_mutex_unlock(&handle->req_mutex);
    return -1;
  }
  res = brlapi__waitForPacket(handle, request, packet, size, WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER);
  pthread_mutex_unlock(&handle->req_mutex);
  return res;
}

/* Function : brlapi_getDriverName */
/* Name of the driver used by brltty */
int BRLAPI_STDCALL brlapi__getDriverName(brlapi_handle_t *handle, char *name, size_t n)
{
  ssize_t res = brlapi__request(handle, BRLAPI_PACKET_GETDRIVERNAME, name, n);
  if ((res>0) && (res<=n)) name[res-1] = '\0';
  return res;
}

int BRLAPI_STDCALL brlapi_getDriverName(char *name, size_t n)
{
  return brlapi__getDriverName(&defaultHandle, name, n);
}

/* Function : brlapi_getModelIdentifier */
/* An identifier for the model of the device used by brltty */
int BRLAPI_STDCALL brlapi__getModelIdentifier(brlapi_handle_t *handle, char *identifier, size_t n)
{
  ssize_t res = brlapi__request(handle, BRLAPI_PACKET_GETMODELID, identifier, n);
  if ((res>0) && (res<=n)) identifier[res-1] = '\0';
  return res;
}

int BRLAPI_STDCALL brlapi_getModelIdentifier(char *identifier, size_t n)
{
  return brlapi__getModelIdentifier(&defaultHandle, identifier, n);
}

/* Function : brlapi_getDisplaySize */
/* Returns the size of the braille display */
int BRLAPI_STDCALL brlapi__getDisplaySize(brlapi_handle_t *handle, unsigned int *x, unsigned int *y)
{
  uint32_t displaySize[2];
  ssize_t res;

  if (handle->brlx&&handle->brly) { *x = handle->brlx; *y = handle->brly; return 0; }
  res = brlapi__request(handle, BRLAPI_PACKET_GETDISPLAYSIZE, displaySize, sizeof(displaySize));
  if (res==-1) { return -1; }
  handle->brlx = ntohl(displaySize[0]);
  handle->brly = ntohl(displaySize[1]);
  *x = handle->brlx; *y = handle->brly;
  return 0;
}

int BRLAPI_STDCALL brlapi_getDisplaySize(unsigned int *x, unsigned int *y)
{
  return brlapi__getDisplaySize(&defaultHandle, x, y);
}

/* Function: brlapi_getParameter */

/* Internal version, returns the reply value packet and the length of the value */
static ssize_t _brlapi__getParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramValuePacket_t *reply)
{
  brlapi_paramRequestPacket_t request;
  int res;
  ssize_t rlen;

  request.flags = htonl(flags);
  request.param = htonl(parameter);
  request.subparam_hi = htonl(subparam >> 32);
  request.subparam_lo = htonl(subparam & 0xfffffffful);

  pthread_mutex_lock(&handle->req_mutex);
  res = brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_PARAM_REQUEST, &request, sizeof(request));
  if (res < 0) {
    pthread_mutex_unlock(&handle->req_mutex);
    return -1;
  }
  if (flags & BRLAPI_PARAMF_GET) {
    rlen = brlapi__waitForPacket(handle, BRLAPI_PACKET_PARAM_VALUE, reply, sizeof(*reply), 1, -1);
  } else {
    rlen = brlapi__waitForAck(handle);
  }
  pthread_mutex_unlock(&handle->req_mutex);

  if (rlen < 0) {
    return -1;
  }

  if (flags & BRLAPI_PARAMF_GET) {
    rlen -= sizeof(brlapi_param_flags_t) + sizeof(brlapi_param_t) + sizeof(brlapi_param_subparam_t);
    if (rlen < 0) {
      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
      return -1;
    }
  }

  return rlen;
}

ssize_t BRLAPI_STDCALL brlapi__getParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len)
{
  brlapi_paramValuePacket_t reply;
  ssize_t rlen;

  if (flags & ~BRLAPI_PARAMF_GLOBAL) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }

  rlen = _brlapi__getParameter(handle, parameter, subparam, flags | BRLAPI_PARAMF_GET, &reply);
  if (rlen < 0)
    return -1;

  if (rlen < len) {
    len = rlen;
  }
  _brlapi_ntohParameter(parameter, &reply, rlen);
  memcpy(data, &reply.data, len);

  return rlen;
}

ssize_t BRLAPI_STDCALL brlapi_getParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len)
{
  return brlapi__getParameter(&defaultHandle, parameter, subparam, flags, data, len);
}

void* BRLAPI_STDCALL brlapi__getParameterAlloc(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len)
{
  brlapi_paramValuePacket_t reply;
  ssize_t rlen;
  void *data;

  if (flags & ~BRLAPI_PARAMF_GLOBAL) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return NULL;
  }

  rlen = _brlapi__getParameter(handle, parameter, subparam, flags | BRLAPI_PARAMF_GET, &reply);
  if (rlen < 0)
    return NULL;

  data = malloc(rlen + 1);
  if (!data)
    return NULL;

  _brlapi_ntohParameter(parameter, &reply, rlen);
  memcpy(data, &reply.data, rlen);
  ((char*)data)[rlen] = 0;
  if (len)
    *len = rlen;
  return data;
}

void* BRLAPI_STDCALL brlapi_getParameterAlloc(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len)
{
  return brlapi__getParameterAlloc(&defaultHandle, parameter, subparam, flags, len);
}

/* Function: brlapi_setParameter */
int BRLAPI_STDCALL brlapi__setParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len)
{
  brlapi_paramValuePacket_t packet;
  int res;

  if (flags & ~BRLAPI_PARAMF_GLOBAL) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }

  if (len > sizeof(packet.data)) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }

  packet.flags = htonl(flags);
  packet.param = htonl(parameter);
  packet.subparam_hi = htonl(subparam >> 32);
  packet.subparam_lo = htonl(subparam & 0xfffffffful);
  memcpy(packet.data, data, len);
  _brlapi_htonParameter(parameter, &packet, len);

  res = brlapi__writePacketWaitForAck(handle, BRLAPI_PACKET_PARAM_VALUE, &packet, sizeof(packet.flags) + sizeof(parameter) + sizeof(subparam) + len);
  return res;
}

int BRLAPI_STDCALL brlapi_setParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len)
{
  return brlapi__setParameter(&defaultHandle, parameter, subparam, flags, data, len);
}

/* Function: brlapi_watchParameter */
brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi__watchParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len)
{
  brlapi_paramValuePacket_t reply;
  ssize_t rlen;
  struct brlapi_parameterCallback_t *callback;

  if (flags & ~(BRLAPI_PARAMF_GLOBAL|BRLAPI_PARAMF_SELF)) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return NULL;
  }

  pthread_mutex_lock(&handle->callbacks_mutex);
  rlen = _brlapi__getParameter(handle, parameter, subparam, flags | BRLAPI_PARAMF_GET | BRLAPI_PARAMF_SUBSCRIBE, &reply);
  if (rlen < 0) {
    pthread_mutex_unlock(&handle->callbacks_mutex);
    return NULL;
  }

  callback = malloc(sizeof(*callback));
  if (callback == NULL) {
    brlapi_errno = BRLAPI_ERROR_NOMEM;
    return NULL;
  }
  callback->parameter = parameter;
  callback->subparam = subparam;
  callback->flags = flags;
  callback->func = func;
  callback->priv = priv;

  callback->next = handle->parameterCallbacks;
  if (callback->next)
    callback->next->prev = callback;
  callback->prev = NULL;
  handle->parameterCallbacks = callback;

  _brlapi_ntohParameter(parameter, &reply, rlen);
  if (data) {
    if (rlen < len) {
      len = rlen;
    }
    memcpy(data, &reply.data, len);
  } else {
    func(parameter, subparam, flags, priv, &reply.data, rlen);
  }
  pthread_mutex_unlock(&handle->callbacks_mutex);

  return callback;
}

brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi_watchParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len)
{
  return brlapi__watchParameter(&defaultHandle, parameter, subparam, flags, func, priv, data, len);
}

/* Function: brlapi_unwatchParameter */
int BRLAPI_STDCALL brlapi__unwatchParameter(brlapi_handle_t *handle, brlapi_paramCallbackDescriptor_t descriptor)
{
  brlapi_paramValuePacket_t reply;
  ssize_t rlen;
  struct brlapi_parameterCallback_t *callback = descriptor;

  pthread_mutex_lock(&handle->callbacks_mutex);
  rlen = _brlapi__getParameter(handle, callback->parameter, callback->subparam, callback->flags | BRLAPI_PARAMF_UNSUBSCRIBE, &reply);
  if (rlen < 0) {
    pthread_mutex_unlock(&handle->callbacks_mutex);
    return -1;
  }

  if (handle->nextCallback == callback) {
    /* We might be removing an entry which brlapi__doWaitForPacket was planning
     * to have a look at next, make it skip it. */
    handle->nextCallback = handle->nextCallback->next;
  }

  if (callback->next) {
    callback->next->prev = callback->prev;
  }
  if (callback->prev) {
    callback->prev->next = callback->next;
  } else {
    handle->parameterCallbacks = callback->next;
  }
  pthread_mutex_unlock(&handle->callbacks_mutex);
  free(callback);
  return 0;
}

int BRLAPI_STDCALL brlapi_unwatchParameter(brlapi_paramCallbackDescriptor_t descriptor)
{
  return brlapi__unwatchParameter(&defaultHandle, descriptor);
}


/* Function : getControllingTty */
/* Returns the number of the caller's controlling terminal */
/* -1 if error or unknown */
static int getControllingTty(void)
{
  int tty;
  const char *env;

  /*if ((env = getenv("WINDOW")) && sscanf(env, "%u", &tty) == 1) return tty;*/
  if ((env = getenv("WINDOWID")) && sscanf(env, "%u", &tty) == 1) return tty;
  if ((env = getenv("CONTROLVT")) && sscanf(env, "%u", &tty) == 1) return tty;
  if (!getenv("DISPLAY") && !getenv("WAYLAND_DISPLAY"))
    if ((env = getenv("XDG_VTNR")) && sscanf(env, "%u", &tty) == 1) return tty;

#ifdef WINDOWS
  if (CHECKGETPROC("kernel32.dll", GetConsoleWindow))
    /* really good guess */
    if ((tty = (LONG_PTR) GetConsoleWindowProc())) return tty;
  if ((tty = (LONG_PTR) GetActiveWindow()) || (tty = (LONG_PTR) GetFocus())) {
    /* good guess, but need to get back up to parent window */
    HWND root = GetDesktopWindow();
    HWND tmp = (HWND) (LONG_PTR) tty;
    while (1) {
      tmp = GetParent(tmp);
      if (!tmp || tmp == root) return tty;
      tty = (LONG_PTR) tmp;
    }
  }
  /* poor guess: assumes that focus is here */
  if ((tty = (LONG_PTR) GetForegroundWindow())) return tty;
#endif /* WINDOWS */

#ifdef linux
  do {
    int fd = open("/proc/self/stat", O_RDONLY, 0);
    if (fd < 0) {
      break;
    }

    char buf[256];
    ssize_t read_bytes = read(fd, buf, sizeof(buf) - 1);
    close(fd);
    if (read_bytes < 0) {
      break;
    }
    /* Append null at the end of buffer to ensure that sscanf doesn't
     * overflows.*/
    buf[read_bytes] = '\0';

    /* The line format is "$pid ($comm) $state ...
     * where comm may contain space and `)` as part of its name, ex:
     * 12345 (some proc (foo)) R ...
     * So to process valid identified after comm, we should skip comm by
     * getting the 1st `)` from end.
     */
    char *ptr = buf + read_bytes - 1;
    while (ptr > buf && *ptr != ')') {
      ptr -= 1;
    }
    if (*ptr != ')') {
      break;
    }
    /* Point to space after ($comm). */
    ptr += 1;

    int vt;
    int ok = sscanf(ptr, " %*c %*d %*d %*d %d", &tty) == 1;

    if (ok && (major(tty) == TTY_MAJOR)) {
      vt = minor(tty);
      if ((vt >= 1) && (vt <= MAXIMUM_VIRTUAL_CONSOLE)) return vt;
    }
  } while (0);
#endif /* linux */

  return -1;
}

/* Function : brlapi_enterTtyMode */
/* Takes control of a tty */
int BRLAPI_STDCALL brlapi__enterTtyMode(brlapi_handle_t *handle, int tty, const char *driverName)
{
  /* Determine which tty to take control of */
  if (tty<0) tty = getControllingTty();
  /* 0 can be a valid screen WINDOW
  0xffffffff can not be a valid WINDOWID (top 3 bits guaranteed to be zero) */
  if (tty<0) { brlapi_errno=BRLAPI_ERROR_UNKNOWNTTY; return -1; }

  if (brlapi__enterTtyModeWithPath(handle, &tty, 1, driverName)) return -1;

  return tty;
}

int BRLAPI_STDCALL brlapi_enterTtyMode(int tty, const char *how)
{
  return brlapi__enterTtyMode(&defaultHandle, tty, how);
}

/* Function : brlapi_enterTtyModeWithPath */
/* Takes control of a tty path */
int BRLAPI_STDCALL brlapi__enterTtyModeWithPath(brlapi_handle_t *handle, const int *ttys, int nttys, const char *driverName)
{
  int res;
  void *packet;
  unsigned char *p;
  uint32_t *nbTtys, *t;
  char *ttytreepath,*cur,*next;
  int ttypath;
  unsigned int n, nttytreepath;
  size_t size;

  pthread_mutex_lock(&handle->state_mutex);
  if ((handle->state & STCONTROLLINGTTY)) {
    pthread_mutex_unlock(&handle->state_mutex);
    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
    return -1;
  }

  if (brlapi__getDisplaySize(handle, &handle->brlx, &handle->brly)<0){
    pthread_mutex_unlock(&handle->state_mutex);
    return -1;
  }

  /* Clear key buffer before taking the tty, just in case... */
  pthread_mutex_lock(&handle->read_mutex);
  handle->keybuf_next = handle->keybuf_nb = 0;
  pthread_mutex_unlock(&handle->read_mutex);

  /* OK, Now we know where we are, so get the effective control of the terminal! */
  ttytreepath = getenv("WINDOWPATH");
  if (!ttytreepath && (getenv("DISPLAY") || getenv("WAYLAND_DISPLAY"))) {
    /* Cope with some DMs which are not setting WINDOWPATH (e.g. gdm 3.12) */
    ttytreepath = getenv("XDG_VTNR");

#ifdef HAVE_SD_SESSION_GET_VT
    if (!ttytreepath) {
      /* Fallback to asking logind */
      char *id;
      unsigned vtnr = 0;
      int ret;

      ret = sd_pid_get_session(0, &id);
      if (ret == 0 && id != NULL) {
	sd_session_get_vt(id, &vtnr);
	free(id);
      } else {
	char **sessions;
	/* Not even logind knows :/ we are probably logged in from gdm */
	int nsessions = sd_uid_get_sessions(getuid(), 0, &sessions);

	if (nsessions > 0) {
	  int i, chosen = -1;

	  for (i = 0; i < nsessions; i++) {
	    char *type;

	    ret = sd_session_get_type(sessions[i], &type);
	    if (ret == 0) {
	      if (strcmp(type, "tty") != 0 &&
	          strcmp(type, "unspecified") != 0) {
		/* x11, wayland or mir, so graphical.
		 * User normally has only one of them */
		if (chosen >= 0) {
		  /* Oops, several sessions? That is not supposed to happen, better
		   * choose none of them. */
		  chosen = -1;
		  break;
		}
		chosen = i;
	      }
	    }
	  }

	  if (chosen >= 0) sd_session_get_vt(sessions[chosen], &vtnr);

	  for (i = 0; i < nsessions; i++) free(sessions[i]);
	  free(sessions);
	}
      }

      if (vtnr) {
	size_t size = sizeof(vtnr)*3 + 1;
	ttytreepath=alloca(size);
	snprintf(ttytreepath, size, "%u", vtnr);
      }
    }
#endif /* HAVE_SD_SESSION_GET_VT */
  }

  nttytreepath = 0;
  if (ttytreepath) {
    /* Count ttys from path */
    for(cur = ttytreepath; *cur; nttytreepath++, cur = next+1) {
      strtol(cur,&next,0);
      if (next==cur) {
        syslog(LOG_WARNING, "Erroneous window path %s", ttytreepath);
        pthread_mutex_unlock(&handle->state_mutex);
        brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
        return -1;
      }
      if (*next==0) { nttytreepath++; break; }
    }
  }

  size = (1 + nttytreepath + nttys) * sizeof(uint32_t);
  size += 1 + (driverName ? strlen(driverName) : 0);
  packet = alloca(size);
  nbTtys = packet;
  t = nbTtys+1;
  *nbTtys = htonl(nttytreepath + nttys);

  if (ttytreepath) {
    /* First add ttys from environment path */
    for(cur=ttytreepath; *cur; nttytreepath++, cur=next+1) {
      ttypath=strtol(cur,&next,0);
      *t++ = htonl(ttypath);
      if (*next==0) break;
    }
  }

  /* Then add ttys from caller path */
  for (n=0; n<nttys; n++) *t++ = htonl(ttys[n]);

  p = (unsigned char *) t;
  if (driverName==NULL) n = 0; else n = strlen(driverName);
  if (n>BRLAPI_MAXNAMELENGTH) {
    pthread_mutex_unlock(&handle->state_mutex);
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }
  *p = n;
  p++;
  if (n) p = mempcpy(p, driverName, n);

  assert(p-(unsigned char *)packet == size);
  if ((res=brlapi__writePacketWaitForAck(handle,BRLAPI_PACKET_ENTERTTYMODE,packet,size)) == 0) {
    handle->state |= STCONTROLLINGTTY;
  }

  pthread_mutex_unlock(&handle->state_mutex);

  /* Determine default charset if application did not call setlocale.  */
#ifdef LC_GLOBAL_LOCALE
  const char *locale = setlocale(LC_CTYPE, NULL);

  if (!locale || !strcmp(locale, "C")) {
    /* Application did not call setlocale, try to load the current locale.  */
    locale_t default_locale = newlocale(LC_CTYPE_MASK, "", 0);
    if (default_locale) handle->default_locale = default_locale;
  }
#endif /* LC_GLOBAL_LOCALE */

  return res;
}

int BRLAPI_STDCALL brlapi_enterTtyModeWithPath(const int *ttys, int nttys, const char *how)
{
  return brlapi__enterTtyModeWithPath(&defaultHandle, ttys, nttys, how);
}

/* Function : brlapi_leaveTtyMode */
/* Gives back control of our tty to brltty */
int BRLAPI_STDCALL brlapi__leaveTtyMode(brlapi_handle_t *handle)
{
  int res;
  pthread_mutex_lock(&handle->state_mutex);
  if (!(handle->state & STCONTROLLINGTTY)) {
    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
    res = -1;
    goto out;
  }
  handle->brlx = 0; handle->brly = 0;
  res = brlapi__writePacketWaitForAck(handle,BRLAPI_PACKET_LEAVETTYMODE,NULL,0);
  handle->state &= ~STCONTROLLINGTTY;
out:
  pthread_mutex_unlock(&handle->state_mutex);
  return res;
}

int BRLAPI_STDCALL brlapi_leaveTtyMode(void)
{
  return brlapi__leaveTtyMode(&defaultHandle);
}

/* Function : brlapi_setFocus */
/* sends the current focus to brltty */
int BRLAPI_STDCALL brlapi__setFocus(brlapi_handle_t *handle, int tty)
{
  uint32_t utty;
  int res;
  utty = htonl(tty);
  pthread_mutex_lock(&handle->fileDescriptor_mutex);
  res = brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_SETFOCUS, &utty, sizeof(utty));
  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
  return res;
}

int BRLAPI_STDCALL brlapi_setFocus(int tty)
{
  return brlapi__setFocus(&defaultHandle, tty);
}

static size_t getCharset(brlapi_handle_t *handle, void *buffer, int wide) {
  char *p = buffer;
  const char *start = p;
  const char *locale = setlocale(LC_CTYPE, NULL);

  if (wide
#ifdef __MINGW32__
      && CHECKPROC("ntdll.dll", wcslen)
#endif /* __MINGW32__ */
  ) {
    size_t length = strlen(WCHAR_CHARSET);
    *p++ = length;
    p = mempcpy(p, WCHAR_CHARSET, length);
  } else if ((locale && strcmp(locale, "C"))
#ifdef LC_GLOBAL_LOCALE
             || (handle->default_locale != LC_GLOBAL_LOCALE)
#endif /* LC_GLOBAL_LOCALE */
            ) {
    /* not default locale, tell charset to server */
#ifdef WINDOWS
    UINT CP = GetACP();
    if (!CP) CP = GetOEMCP();

    if (CP) {
      size_t length = sprintf(p+1, "CP%u", CP);
      *p++ = length;
      p += length;
    }
#elif defined(HAVE_NL_LANGINFO)
    char *language = nl_langinfo(CODESET);
    size_t length = strlen(language);

    *p++ = length;
    p = mempcpy(p, language, length);
#endif /* get non-wide character set */
  }

  return p-start;
}

/* Function : brlapi_writeText */
/* Writes a string to the braille display */
static int brlapi___writeText(brlapi_handle_t *handle, int cursor, const void *str, int wide)
{
  int dispSize = handle->brlx * handle->brly;
  brlapi_packet_t packet;
  brlapi_writeArgumentsPacket_t *wa = &packet.writeArguments;
  unsigned char *p = &wa->data;
  int res;
  size_t len;

#ifdef LC_GLOBAL_LOCALE
  locale_t old_locale = 0;

  if (handle->default_locale != LC_GLOBAL_LOCALE) {
    /* Temporarily load the default locale.  */
    old_locale = uselocale(handle->default_locale);
  }

#else /* LC_GLOBAL_LOCALE */
  setlocale(LC_CTYPE,NULL);
#endif /* LC_GLOBAL_LOCALE */

  wa->flags = BRLAPI_WF_REGION;
  *((uint32_t *) p) = htonl(1); p += sizeof(uint32_t);
  *((uint32_t *) p) = htonl(-dispSize); p += sizeof(uint32_t);

  if (str) {
    wa->flags |= BRLAPI_WF_TEXT;
#if defined(__MINGW32__)
    if (CHECKGETPROC("ntdll.dll", wcslen) && wide)
      len = sizeof(wchar_t) * wcslenProc(str);
#else /* __MINGW32__ */
    if (wide)
      len = sizeof(wchar_t) * wcslen(str);
#endif /* __MINGW32__ */
    else
      len = strlen(str);
    *((uint32_t *) p) = htonl(len); p += sizeof(uint32_t);
    memcpy(p, str, len);
    p += len;
  }
  if (cursor!=BRLAPI_CURSOR_LEAVE) {
    wa->flags |= BRLAPI_WF_CURSOR;
    *((uint32_t *) p) = htonl(cursor);
    p += sizeof(uint32_t);
  }

  if ((len = getCharset(handle, p , wide))) {
    wa->flags |= BRLAPI_WF_CHARSET;
    p += len;
  }

  wa->flags = htonl(wa->flags);
  pthread_mutex_lock(&handle->fileDescriptor_mutex);
  res = brlapi_writePacket(handle->fileDescriptor,BRLAPI_PACKET_WRITE,&packet,sizeof(wa->flags)+(p-&wa->data));
  pthread_mutex_unlock(&handle->fileDescriptor_mutex);

#ifdef LC_GLOBAL_LOCALE
  if (handle->default_locale != LC_GLOBAL_LOCALE) {
    /* Restore application locale */
    uselocale(old_locale);
  }
#endif /* LC_GLOBAL_LOCALE */

  return res;
}

#ifdef WINDOWS
int BRLAPI_STDCALL brlapi__writeTextWin(brlapi_handle_t *handle, int cursor, const void *str, int wide)
{
  return brlapi___writeText(handle, cursor, str, wide);
}

int BRLAPI_STDCALL brlapi_writeTextWin(int cursor, const void *str, int wide)
{
  return brlapi___writeText(&defaultHandle, cursor, str, wide);
}
#else /* WINDOWS */
int BRLAPI_STDCALL brlapi__writeText(brlapi_handle_t *handle, int cursor, const char *str)
{
  return brlapi___writeText(handle, cursor, str, 0);
}

int BRLAPI_STDCALL brlapi__writeWText(brlapi_handle_t *handle, int cursor, const wchar_t *str)
{
  return brlapi___writeText(handle, cursor, str, 1);
}

int BRLAPI_STDCALL brlapi_writeText(int cursor, const char *str)
{
  return brlapi__writeText(&defaultHandle, cursor, str);
}

int BRLAPI_STDCALL brlapi_writeWText(int cursor, const wchar_t *str)
{
  return brlapi__writeWText(&defaultHandle, cursor, str);
}
#endif /* WINDOWS */

/* Function : brlapi_writeDots */
/* Writes dot-matrix to the braille display */
int BRLAPI_STDCALL brlapi__writeDots(brlapi_handle_t *handle, const unsigned char *dots)
{
  brlapi_writeArguments_t wa = BRLAPI_WRITEARGUMENTS_INITIALIZER;
  unsigned int size = handle->brlx * handle->brly;

  if (size == 0) {
    brlapi_errno=BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }

  unsigned char andMask[size];
  memset(andMask, 0, size);
  wa.andMask = andMask;

  unsigned char orMask[size];
  memcpy(orMask, dots, size);
  wa.orMask = orMask;

  char text[(size * 3) + 1];
  wa.text = text;
  wa.charset = "utf-8";

  {
    /* Pass a UTF8-encoded string of the braille characters as the text.
     *
     * The Unicode row for the braille pattern characters is U+2800.
     * Each of the eight dots is represented by a bit in the low-order byte:
     * Dot1 by 0X01, Dot2 by 0X02, ..., Dot7 by 0X40, and Dot8 by 0X80.
     *
     * The UTF-8 template for the Unicode braille row is: 0XE2, 0XA0, 0X80.
     * Dots 1-6 are the low-order six bits of the last (0X80) byte.
     * Dots 7-8 are the low-order two bits of the middle (0XA0) byte.
     */

    char *byte = text;
    const unsigned char *cell = dots;
    const unsigned char *end = cell + size;

    while (cell < end) {
      *byte++ = 0XE2;
      *byte++ = 0XA0 | ((*cell >> 6) & 0X3); // dots 7-8
      *byte++ = 0X80 | (*cell & 0X3F); // dots 1-6
      cell += 1;
    }

    *byte = 0;
    wa.textSize = byte - text;
  }

  wa.regionBegin = 1;
  wa.regionSize = -size;
  wa.cursor = BRLAPI_CURSOR_OFF;

  return brlapi__write(handle, &wa);
}

int BRLAPI_STDCALL brlapi_writeDots(const unsigned char *dots)
{
  return brlapi__writeDots(&defaultHandle, dots);
}

/* Function : brlapi_write */
/* Extended writes on braille displays */
#ifdef WINDOWS
int BRLAPI_STDCALL brlapi__writeWin(brlapi_handle_t *handle, const brlapi_writeArguments_t *s, int wide)
#else /* WINDOWS */
int brlapi__write(brlapi_handle_t *handle, const brlapi_writeArguments_t *s)
#endif /* WINDOWS */
{
  int dispSize = handle->brlx * handle->brly;
  unsigned int rbeg, strLen;
  int rsiz;
  brlapi_packet_t packet;
  brlapi_writeArgumentsPacket_t *wa = &packet.writeArguments;
  unsigned char *p = &wa->data;
  unsigned char *end = (unsigned char*) &packet.data[sizeof(packet)];
  int res;
#ifndef WINDOWS
  int wide = 0;
#endif /* WINDOWS */
  wa->flags = 0;
  if (s==NULL) goto send;
  rbeg = s->regionBegin;
  rsiz = s->regionSize;
  if (rbeg || rsiz) {
    if (rsiz == 0) return 0;
    wa->flags |= BRLAPI_WF_REGION;
    *((uint32_t *) p) = htonl(rbeg); p += sizeof(uint32_t);
    *((uint32_t *) p) = htonl((int32_t) rsiz); p += sizeof(uint32_t);
  } else {
    /* DEPRECATED */
    rsiz = -dispSize;
  }
  if (rsiz < 0)
    rsiz = -rsiz;
  if (s->text) {
    if (s->textSize != -1)
      strLen = s->textSize;
    else
#if defined(__CYGWIN__)
      if (wide)
	strLen = sizeof(wchar_t) * wcslen((wchar_t *) s->text);
      else
#elif defined(__MINGW32__)
      if (CHECKGETPROC("ntdll.dll", wcslen) && wide)
	strLen = sizeof(wchar_t) * wcslenProc((wchar_t *) s->text);
      else
#endif /* windows wide string length */
	strLen = strlen(s->text);
    *((uint32_t *) p) = htonl(strLen); p += sizeof(uint32_t);
    wa->flags |= BRLAPI_WF_TEXT;
    if (p + strLen > end) {
      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
      return -1;
    }
    memcpy(p, s->text, strLen);
    p += strLen;
  }
  if (s->andMask) {
    wa->flags |= BRLAPI_WF_ATTR_AND;
    if (p + rsiz > end) {
      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
      return -1;
    }
    memcpy(p, s->andMask, rsiz);
    p += rsiz;
  }
  if (s->orMask) {
    wa->flags |= BRLAPI_WF_ATTR_OR;
    if (p + rsiz > end) {
      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
      return -1;
    }
    memcpy(p, s->orMask, rsiz);
    p += rsiz;
  }
  if ((s->cursor>=0) && (s->cursor<=dispSize)) {
    wa->flags |= BRLAPI_WF_CURSOR;
    if (p + sizeof(uint32_t) > end) {
      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
      return -1;
    }
    *((uint32_t *) p) = htonl(s->cursor);
    p += sizeof(uint32_t);
  } else if (s->cursor != BRLAPI_CURSOR_LEAVE) {
    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    return -1;
  }

  if (s->charset) {
    if (!*s->charset) {
#ifdef LC_GLOBAL_LOCALE
      locale_t old_locale = 0;

      if (handle->default_locale != LC_GLOBAL_LOCALE) {
	/* Temporarily load the default locale.  */
	old_locale = uselocale(handle->default_locale);
      }
#endif /* LC_GLOBAL_LOCALE */

      if ((strLen = getCharset(handle, p, wide))) {
	wa->flags |= BRLAPI_WF_CHARSET;
	p += strLen;
      }

#ifdef LC_GLOBAL_LOCALE
      if (handle->default_locale != LC_GLOBAL_LOCALE) {
	/* Restore application locale */
	uselocale(old_locale);
      }
#endif /* LC_GLOBAL_LOCALE */
    } else {
      strLen = strlen(s->charset);
      *p++ = strLen;
      wa->flags |= BRLAPI_WF_CHARSET;
      if (p + strLen > end) {
	brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
	return -1;
      }

      memcpy(p, s->charset, strLen);
      p += strLen;
    }
  }

send:
  wa->flags = htonl(wa->flags);
  pthread_mutex_lock(&handle->fileDescriptor_mutex);
  res = brlapi_writePacket(handle->fileDescriptor,BRLAPI_PACKET_WRITE,&packet,sizeof(wa->flags)+(p-&wa->data));
  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
  return res;
}

#ifdef WINDOWS
int BRLAPI_STDCALL brlapi_writeWin(const brlapi_writeArguments_t *s, int wide)
{
  return brlapi__writeWin(&defaultHandle, s, wide);
}
#else /* WINDOWS */
int brlapi_write(const brlapi_writeArguments_t *s)
{
  return brlapi__write(&defaultHandle, s);
}
#endif /* WINDOWS */

/* Function : brlapi_readKey */
/* Reads a key from the braille keyboard */
int BRLAPI_STDCALL brlapi__readKeyWithTimeout(brlapi_handle_t *handle, int timeout_ms, brlapi_keyCode_t *code)
{
  ssize_t res;
  uint32_t buf[2];

  pthread_mutex_lock(&handle->state_mutex);
  if (!(handle->state & STCONTROLLINGTTY)) {
    pthread_mutex_unlock(&handle->state_mutex);
    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
    return -1;
  }
  pthread_mutex_unlock(&handle->state_mutex);

  pthread_mutex_lock(&handle->read_mutex);
  if (handle->keybuf_nb>0) {
    *code=handle->keybuf[handle->keybuf_next];
    handle->keybuf_next=(handle->keybuf_next+1)%BRL_KEYBUF_SIZE;
    handle->keybuf_nb--;
    pthread_mutex_unlock(&handle->read_mutex);
    return 1;
  }
  pthread_mutex_unlock(&handle->read_mutex);

  pthread_mutex_lock(&handle->key_mutex);
  res = brlapi__waitForPacket(handle,BRLAPI_PACKET_KEY, buf, sizeof(buf), TRY_WAIT_FOR_EXPECTED_PACKET, timeout_ms);
  pthread_mutex_unlock(&handle->key_mutex);
  if (res == -3) {
    if (timeout_ms == 0) return 0;
    brlapi_libcerrno = EINTR;
    brlapi_errno = BRLAPI_ERROR_LIBCERR;
    brlapi_errfun = "waitForPacket";
    return -1;
  }
  if (res == -4) {
    /* Timeout */
    return 0;
  }
  if (res < 0) return -1;
  *code = brlapi_packetToKeyCode(buf);
  return 1;
}

int BRLAPI_STDCALL brlapi_readKeyWithTimeout(int timeout_ms, brlapi_keyCode_t *code)
{
  return brlapi__readKeyWithTimeout(&defaultHandle, timeout_ms, code);
}

int BRLAPI_STDCALL brlapi__readKey(brlapi_handle_t *handle, int block, brlapi_keyCode_t *code)
{
  return brlapi__readKeyWithTimeout(handle, block ? -1 : 0, code);
}

int BRLAPI_STDCALL brlapi_readKey(int block, brlapi_keyCode_t *code)
{
  return brlapi__readKeyWithTimeout(&defaultHandle, block ? -1 : 0, code);
}

typedef struct {
  brlapi_keyCode_t code;
  const char *name;
} brlapi_keyEntry_t;

static const brlapi_keyEntry_t brlapi_keyTable[] = {
#include "brlapi_keytab.auto.h"

  {.code=0X0000U, .name="LATIN1"},
  {.code=0X0100U, .name="LATIN2"},
  {.code=0X0200U, .name="LATIN3"},
  {.code=0X0300U, .name="LATIN4"},
  {.code=0X0400U, .name="KATAKANA"},
  {.code=0X0500U, .name="ARABIC"},
  {.code=0X0600U, .name="CYRILLIC"},
  {.code=0X0700U, .name="GREEK"},
  {.code=0X0800U, .name="TECHNICAL"},
  {.code=0X0900U, .name="SPECIAL"},
  {.code=0X0A00U, .name="PUBLISHING"},
  {.code=0X0B00U, .name="APL"},
  {.code=0X0C00U, .name="HEBREW"},
  {.code=0X0D00U, .name="THAI"},
  {.code=0X0E00U, .name="KOREAN"},
  {.code=0X1200U, .name="LATIN8"},
  {.code=0X1300U, .name="LATIN9"},
  {.code=0X1400U, .name="ARMENIAN"},
  {.code=0X1500U, .name="GEORGIAN"},
  {.code=0X1600U, .name="CAUCASUS"},
  {.code=0X1E00U, .name="VIETNAMESE"},
  {.code=0X2000U, .name="CURRENCY"},
  {.code=0XFD00U, .name="3270"},
  {.code=0XFE00U, .name="XKB"},
  {.code=0XFF00U, .name="MISCELLANY"},
  {.code=0X01000000U, .name="UNICODE"},

  {.name=NULL}
};

int BRLAPI_STDCALL
brlapi_expandKeyCode (brlapi_keyCode_t keyCode, brlapi_expandedKeyCode_t *ekc) {
  int argumentWidth = brlapi_getArgumentWidth(keyCode);

  if (argumentWidth != -1) {
    unsigned int argumentMask = (1 << argumentWidth) - 1;
    brlapi_keyCode_t type = keyCode & BRLAPI_KEY_TYPE_MASK;
    brlapi_keyCode_t code = keyCode & BRLAPI_KEY_CODE_MASK;

    ekc->type = type;
    ekc->command = (code & ~argumentMask);
    ekc->argument = code & argumentMask;
    ekc->flags = (keyCode & BRLAPI_KEY_FLAGS_MASK) >> BRLAPI_KEY_FLAGS_SHIFT;

    return 0;
  }

  return -1;
}

int BRLAPI_STDCALL
brlapi_describeKeyCode (brlapi_keyCode_t keyCode, brlapi_describedKeyCode_t *dkc) {
  brlapi_expandedKeyCode_t ekc;
  int result = brlapi_expandKeyCode(keyCode, &ekc);

  if (result != -1) {
    unsigned int argument = ekc.argument;
    unsigned int codeWithoutArgument = ekc.type | ekc.command;
    unsigned int codeWithArgument = codeWithoutArgument | argument;
    const brlapi_keyEntry_t *keyWithoutArgument = NULL;
    const brlapi_keyEntry_t *key = brlapi_keyTable;

    while (key->name) {
      if (codeWithArgument == key->code) {
        argument = 0;
        goto found;
      }

      if (codeWithoutArgument == key->code)
        if (!keyWithoutArgument)
          keyWithoutArgument = key;

      ++key;
    }

    if (keyWithoutArgument) {
      key = keyWithoutArgument;
      goto found;
    }

    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
    result = -1;
    goto done;

  found:
    dkc->command = key->name;
    dkc->argument = argument;
    dkc->values = ekc;

    switch (ekc.type) {
      case BRLAPI_KEY_TYPE_SYM: dkc->type = "SYM";     break;
      case BRLAPI_KEY_TYPE_CMD: dkc->type = "CMD";     break;
      default:                  dkc->type = "UNKNOWN"; break;
    }

#define FLAG(name) if (keyCode & BRLAPI_KEY_FLG_##name) dkc->flag[dkc->flags++] = #name
    dkc->flags = 0;

    FLAG(SHIFT);
    FLAG(UPPER);
    FLAG(CONTROL);
    FLAG(META);
    FLAG(ALTGR);
    FLAG(GUI);

    switch (ekc.type) {
      case BRLAPI_KEY_TYPE_CMD:
        switch (ekc.command & BRLAPI_KEY_CMD_BLK_MASK) {
          case BRLAPI_KEY_CMD_PASSDOTS:
            break;

          case BRLAPI_KEY_CMD_PASSXT:
          case BRLAPI_KEY_CMD_PASSAT:
          case BRLAPI_KEY_CMD_PASSPS2:
            FLAG(KBD_RELEASE);
            FLAG(KBD_EMUL0);
            FLAG(KBD_EMUL1);
            break;

          default:
            FLAG(TOGGLE_ON);
            FLAG(TOGGLE_OFF);
            FLAG(MOTION_ROUTE);
            FLAG(MOTION_SCALED);
            FLAG(MOTION_TOLEFT);
            break;
        }
        break;
    }
#undef FLAG
  }

done:
  return result;
}

/* Function : ignore_accept_key_range */
/* Common tasks for ignoring and unignoring key ranges */
/* what = 0 for ignoring !0 for unignoring */
static int ignore_accept_key_ranges(brlapi_handle_t *handle, int what, const brlapi_range_t ranges[], unsigned int n)
{
  uint32_t ints[n][4];
  unsigned int i;

  for (i=0; i<n; i++) {
    ints[i][0] = htonl(ranges[i].first >> 32);
    ints[i][1] = htonl(ranges[i].first & 0xffffffff);
    ints[i][2] = htonl(ranges[i].last >> 32);
    ints[i][3] = htonl(ranges[i].last & 0xffffffff);
  };

  if (brlapi__writePacketWaitForAck(handle,(what ? BRLAPI_PACKET_ACCEPTKEYRANGES : BRLAPI_PACKET_IGNOREKEYRANGES),ints,n*2*sizeof(brlapi_keyCode_t)))
    return -1;
  return 0;
}

/* Function : ignore_accept_keys */
/* Common tasks for ignoring and unignoring keys */
/* what = 0 for ignoring !0 for unignoring */
static int ignore_accept_keys(brlapi_handle_t *handle, int what, brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
{
  if (!n) {
    if (r != brlapi_rangeType_all) {
      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
      return -1;
    }
    brlapi_range_t range = { .first = 0, .last = BRLAPI_KEY_MAX };
    return ignore_accept_key_ranges(handle, what, &range, 1);
  } else {
    brlapi_range_t ranges[n];
    unsigned int i;
    brlapi_keyCode_t mask;

    for (i=0; i<n; i++) {
      if (brlapi_getKeyrangeMask(r, code[i], &mask))
	return -1;
      if (code[i] & mask) {
	brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
	return -1;
      }
      ranges[i].first = code[i];
      ranges[i].last = code[i] | mask;
    }
    return ignore_accept_key_ranges(handle, what, ranges, n);
  }
}

/* Function : brlapi_acceptKeyRanges */
int BRLAPI_STDCALL brlapi__acceptKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int n)
{
  return ignore_accept_key_ranges(handle, !0, ranges, n);
}

int BRLAPI_STDCALL brlapi_acceptKeyRanges(const brlapi_range_t ranges[], unsigned int n)
{
  return brlapi__acceptKeyRanges(&defaultHandle, ranges, n);
}

/* Function : brlapi_acceptKeys */
int BRLAPI_STDCALL brlapi__acceptKeys(brlapi_handle_t *handle, brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
{
  return ignore_accept_keys(handle, !0, r, code, n);
}

int BRLAPI_STDCALL brlapi_acceptKeys(brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
{
  return brlapi__acceptKeys(&defaultHandle, r, code, n);
}

/* Function : brlapi_ignoreKeyRanges */
int BRLAPI_STDCALL brlapi__ignoreKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int n)
{
  return ignore_accept_key_ranges(handle, 0, ranges, n);
}

int BRLAPI_STDCALL brlapi_ignoreKeyRanges(const brlapi_range_t ranges[], unsigned int n)
{
  return brlapi__ignoreKeyRanges(&defaultHandle, ranges, n);
}

/* Function : brlapi_ignoreKeys */
int BRLAPI_STDCALL brlapi__ignoreKeys(brlapi_handle_t *handle, brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
{
  return ignore_accept_keys(handle, 0, r, code, n);
}

int BRLAPI_STDCALL brlapi_ignoreKeys(brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
{
  return brlapi__ignoreKeys(&defaultHandle, r, code, n);
}

/* Error code handling */

/* brlapi_errlist: error messages */
const char *brlapi_errlist[] = {
  [0]                                = "Success",
  [BRLAPI_ERROR_NOMEM]               = "Insufficient memory",
  [BRLAPI_ERROR_TTYBUSY]             = "Tty is busy",
  [BRLAPI_ERROR_DEVICEBUSY]          = "Device is busy",
  [BRLAPI_ERROR_UNKNOWN_INSTRUCTION] = "Unknown instruction",
  [BRLAPI_ERROR_ILLEGAL_INSTRUCTION] = "Illegal instruction",
  [BRLAPI_ERROR_INVALID_PARAMETER]   = "Invalid parameter",
  [BRLAPI_ERROR_INVALID_PACKET]      = "Invalid packet",
  [BRLAPI_ERROR_CONNREFUSED]         = "Connection refused",
  [BRLAPI_ERROR_OPNOTSUPP]           = "Operation not supported",
  [BRLAPI_ERROR_GAIERR]              = "getaddrinfo() error",
  [BRLAPI_ERROR_LIBCERR]             = "libc error",
  [BRLAPI_ERROR_UNKNOWNTTY]          = "Can't determine tty number",
  [BRLAPI_ERROR_PROTOCOL_VERSION]    = "Bad protocol version",
  [BRLAPI_ERROR_EOF]                 = "Unexpected end of file",
  [BRLAPI_ERROR_EMPTYKEY]            = "Key file is empty",
  [BRLAPI_ERROR_DRIVERERROR]         = "Driver error",
  [BRLAPI_ERROR_AUTHENTICATION]      = "Authentication failed",
  [BRLAPI_ERROR_READONLY_PARAMETER]  = "Parameter can not be changed",
};

/* brlapi_nerr: number of error codes */
const int brlapi_nerr = (sizeof(brlapi_errlist)/sizeof(char*));

/* _brlapi_strerror_cpy: store constant-string error message */
static size_t _brlapi_strerror_cpy(char *buf, size_t buflen, const char *msg)
{
  size_t totsize = strlen(msg);

  if (buflen == 0)
    return totsize;

  if (buflen > totsize+1) {
    buflen = totsize+1;
  }
  memcpy(buf, msg, buflen-1);
  buf[buflen-1] = '\0';

  return totsize;
}

/* _brlapi_strerror_snprintf: store formatted error message */
static size_t _brlapi_strerror_snprintf(char *buf, size_t buflen, const char *fmt, ...)
{
  va_list va;
  int totsize;

  va_start(va, fmt);
  totsize = vsnprintf(buf, buflen, fmt, va);
  va_end(va);

  return totsize;
}

/* brlapi_strerror_r: store error message */
size_t BRLAPI_STDCALL brlapi_strerror_r(const brlapi_error_t *error, char *buf, size_t buflen)
{
  if (error->brlerrno>=brlapi_nerr)
    return _brlapi_strerror_cpy(buf, buflen, "Unknown error");
  else if (error->brlerrno==BRLAPI_ERROR_GAIERR) {
#if defined(EAI_SYSTEM)
    if (error->gaierrno == EAI_SYSTEM)
      return _brlapi_strerror_snprintf(buf,buflen,"resolve: %s", strerror(error->libcerrno));
    else
#endif /* EAI_SYSTEM */
      return _brlapi_strerror_snprintf(buf,buflen,"resolve: "
#if defined(HAVE_GETADDRINFO)
#if defined(HAVE_GAI_STRERROR)
	"%s", gai_strerror(error->gaierrno)
#else
	"%d", error->gaierrno
#endif
#elif defined(HAVE_HSTRERROR) && !defined(__MINGW32__)
	"%s", hstrerror(error->gaierrno)
#else
	"%x", error->gaierrno
#endif
	);
  }
  else if (error->brlerrno==BRLAPI_ERROR_LIBCERR) {
    return _brlapi_strerror_snprintf(buf,buflen,"%s: %s", error->errfun?error->errfun:"(null)", strerror(error->libcerrno));
  } else
    return _brlapi_strerror_cpy(buf, buflen, brlapi_errlist[error->brlerrno]);
}

/* brlapi_strerror: return error message */
const char * BRLAPI_STDCALL brlapi_strerror(const brlapi_error_t *error)
{
  static char errmsg[128];
  brlapi_strerror_r(error, errmsg, sizeof(errmsg));
  return errmsg;
}

/* brlapi_perror: error message printing */
void BRLAPI_STDCALL brlapi_perror(const char *s)
{
  fprintf(stderr,"%s: %s\n",s,brlapi_strerror(&brlapi_error));
}

/* XXX functions mustn't use brlapi_errno after this since it #undefs it XXX */

#ifdef brlapi_error
#undef brlapi_error
#endif /* brlapi_error */

brlapi_error_t brlapi_error;
static int pthread_error_ok;

/* we need a per-thread errno variable, thanks to pthread_keys */
static pthread_key_t error_key;

/* the key must be created at most once */
static pthread_once_t error_key_once = PTHREAD_ONCE_INIT;

static void error_key_free(void *key)
{
  free(key);
}

static void error_key_alloc(void)
{
  pthread_error_ok=!pthread_key_create(&error_key, error_key_free);
}

/* how to get per-thread errno variable. This will be called by the macro
 * brlapi_errno */
brlapi_error_t * BRLAPI_STDCALL brlapi_error_location(void)
{
  brlapi_error_t *errorp;
#ifndef WINDOWS
  if (pthread_once && pthread_key_create) {
#endif /* WINDOWS */
    pthread_once(&error_key_once, error_key_alloc);
    if (pthread_error_ok) {
      if ((errorp=(brlapi_error_t *) pthread_getspecific(error_key)))
        /* normal case */
        return errorp;
      else
        /* on the first time, must allocate it */
        if ((errorp=malloc(sizeof(*errorp))) && !pthread_setspecific(error_key,errorp)) {
	  memset(errorp,0,sizeof(*errorp));
          return errorp;
	}
    }
#ifndef WINDOWS
  }
#endif /* WINDOWS */
  /* fall-back: shared errno :/ */
  return &brlapi_error;
}

brlapi__exceptionHandler_t BRLAPI_STDCALL brlapi__setExceptionHandler(brlapi_handle_t *handle, brlapi__exceptionHandler_t new)
{
  brlapi__exceptionHandler_t tmp;
  pthread_mutex_lock(&handle->exceptionHandler_mutex);
  tmp = handle->exceptionHandler.withHandle;
  if (new!=NULL) handle->exceptionHandler.withHandle = new;
  pthread_mutex_unlock(&handle->exceptionHandler_mutex);
  return tmp;
}

brlapi_exceptionHandler_t BRLAPI_STDCALL brlapi_setExceptionHandler(brlapi_exceptionHandler_t new)
{
  brlapi_exceptionHandler_t tmp;
  pthread_mutex_lock(&defaultHandle.exceptionHandler_mutex);
  tmp = defaultHandle.exceptionHandler.withoutHandle;
  if (new!=NULL) defaultHandle.exceptionHandler.withoutHandle = new;
  pthread_mutex_unlock(&defaultHandle.exceptionHandler_mutex);
  return tmp;
}

int BRLAPI_STDCALL brlapi__strexception(brlapi_handle_t *handle, char *buf, size_t n, int err, brlapi_packetType_t type, const void *packet, size_t size)
{
  int chars = 128; /* Number of bytes to dump */
  char hexString[3*chars+1];
  int i, nbChars = MIN(chars, size);
  char *p = hexString;
  brlapi_error_t error = { .brlerrno = err };
  for (i=0; i<nbChars; i++)
    p += sprintf(p, "%02x ", ((unsigned char *) packet)[i]);
  p--; /* Don't keep last space */
  *p = '\0';
  return snprintf(buf, n, "%s on %s request of size %d (%s)",
    brlapi_strerror(&error), brlapi_getPacketTypeName(type), (int)size, hexString);
}

int BRLAPI_STDCALL brlapi_strexception(char *buf, size_t n, int err, brlapi_packetType_t type, const void *packet, size_t size)
{
  return brlapi__strexception(&defaultHandle, buf, n, err, type, packet, size);
}

void BRLAPI_STDCALL brlapi__defaultExceptionHandler(brlapi_handle_t *handle, int err, brlapi_packetType_t type, const void *packet, size_t size)
{
  char str[0X100];
  brlapi_strexception(str,0X100, err, type, packet, size);
  fprintf(stderr, "BrlAPI exception: %s\nYou may wish to add the -ldebug option to the brltty command line in order to get additional information in the system log\n", str);
  fprintf(stderr, "Crashing the client now. You may want to use brlapi_setExceptionHandler to define your own exception handling.\n");
  abort();
}

void BRLAPI_STDCALL brlapi_defaultExceptionHandler(int err, brlapi_packetType_t type, const void *packet, size_t size)
{
  brlapi__defaultExceptionHandler(&defaultHandle, err, type, packet, size);
}
