/* tcl/tk interface V0.2 July 2001 AG (some of this code was pilfered from
   Tk_octave) */

/* This file is part of the Q programming system.

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

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

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

/* system headers */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <math.h>

/* check for standard C headers */
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
#endif

#ifdef HAVE_MALLOC_H
#include <stdlib.h>
#endif

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <sys/types.h>
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#define TCL_THREADS
#include <tcl.h>
#include <tk.h>

#include <libq.h>

/* multithreading support */

#ifdef USE_THREADS
#include <pthread.h>
#endif

#ifdef DMALLOC
#include <dmalloc.h>
#endif

MODULE(tk)

#ifndef HAVE_STRDUP

static char *strdup(char *s)
{
  char *t;
  return ((t=malloc(strlen(s)+1))?strcpy(t, s):NULL);
}

#endif

/* handle X11 protocol errors */

static int XErrorProc(ClientData data, XErrorEvent *errEventPtr)
{
  fprintf(stderr, "X protocol error: ");
  fprintf(stderr, "error=%d request=%d minor=%d\n",
	  errEventPtr->error_code, errEventPtr->request_code,
	  errEventPtr->minor_code);
  return 0;
}

/* handle SIGINT and SIGTERM */

#ifdef _MSC_VER
#define SIGHANDLER_RETURN(status) return
#else
#if RETSIGTYPE == void
#define SIGHANDLER_RETURN(status) return
#else
#define SIGHANDLER_RETURN(status) return status
#endif
#endif

#ifdef MUST_REINSTALL_SIGHANDLERS
#define SIGHANDLER_RESTORE(sig,handler) syssignal(sig,handler)
#else
#define SIGHANDLER_RESTORE(sig,handler) /* nop */
#endif

typedef RETSIGTYPE (*sighandler_t)(int);

static sighandler_t syssignal(sig, handler)
     int sig;
     sighandler_t handler;
{
#ifdef HAVE_POSIX_SIGNALS
  struct sigaction new_action, old_action;
  new_action.sa_handler = handler;
  sigemptyset(&new_action.sa_mask);
  sigemptyset(&old_action.sa_mask);
  new_action.sa_flags = 0;
  sigaction(sig, &new_action, &old_action);
  return old_action.sa_handler;
#else
  return signal(sig, handler);
#endif
}

static int brkflag = 0, tcl_threads = 0;
static volatile int brkevent = 0;
static sighandler_t int_handler = NULL, term_handler = NULL,
  hup_handler = NULL;

static RETSIGTYPE break_handler(int sig)
{
  if (sig == SIGINT && int_handler) int_handler(sig);
  if (sig == SIGTERM && term_handler) term_handler(sig);
#ifdef SIGHUP
  if (sig == SIGHUP && hup_handler) hup_handler(sig);
#endif
  SIGHANDLER_RESTORE(sig, break_handler);
  brkevent = 1;
  SIGHANDLER_RETURN(0);
}

/* event source for handling SIGINT/SIGTERM in the Tcl interpreter */

static void break_setup_proc(ClientData data, int flags)
{
}

static int break_event_proc(Tcl_Event *ev, int flags)
{
  brkflag = 1;
  return 1;
}

static void break_check_proc(ClientData data, int flags)
{
  if (brkevent) {
    Tcl_Event *ev = (Tcl_Event*)Tcl_Alloc(sizeof(Tcl_Event));
    if (!ev) return;
    ev->proc = break_event_proc;
    Tcl_QueueEvent(ev, TCL_QUEUE_HEAD);
    brkevent = 0;
  }
}

/* thread-local stuff */

#ifdef USE_THREADS
#define MAXTHREAD 1024
#else
#define MAXTHREAD 1
#endif

Tcl_Interp* __interp[MAXTHREAD];
#define tld_interp __interp[this_thread()]

/* buffer for passing messages from Tcl to Q */

#define CHUNKSZ 200

static char *__buf[MAXTHREAD];
static int __bufsz[MAXTHREAD];

#define tld_buf __buf[this_thread()]
#define tld_bufsz __bufsz[this_thread()]

static void clear_buf(void)
{
  tld_buf = NULL;
  tld_bufsz = 0;
}

static void init_buf(void)
{
  if (tld_buf) free(tld_buf);
  clear_buf();
}

static int resize_buf(sz)
{
  if (sz >= tld_bufsz) {
    char *newbuf;
    sz = (sz/CHUNKSZ+1)*CHUNKSZ;
    newbuf = tld_buf?realloc(tld_buf, sz):malloc(sz);
    if (!newbuf) return 0;
    tld_bufsz = sz;
    tld_buf = newbuf;
    return 1;
  } else
    return 1;
}

static int add_buf(char *arg)
{
  int argsz = strlen(arg)+1;
  if (tld_bufsz == 0) {
    if (!resize_buf(argsz)) return 0;
    strcpy(tld_buf, arg);
    return 1;
  } else {
    int buflen = strlen(tld_buf);
    if (!resize_buf(argsz+buflen+1)) return 0;
    strcat(tld_buf, " ");
    strcat(tld_buf, arg);
    return 1;
  }
}

/* message queue */

static struct qentry {
  char *s;
  struct qentry *next;
} *__head[MAXTHREAD], *__tail[MAXTHREAD];

#define tld_head __head[this_thread()]
#define tld_tail __tail[this_thread()]

static void init_queue(void)
{
  while (tld_head) {
    struct qentry *next = tld_head->next;
    if (tld_head->s) free(tld_head->s);
    free(tld_head);
    tld_head = next;
  }
  tld_head = tld_tail = NULL;
}

static int emptyqueue(void)
{
  return tld_head == NULL;
}

static int enqueue(char *s)
{
  struct qentry *next = malloc(sizeof(struct qentry));
  if (!next) return 0;
  next->s = s;
  next->next = NULL;
  if (tld_head) {
    tld_tail->next = next;
    tld_tail = next;
  } else
    tld_head = tld_tail = next;
  return 1;
}

static char *dequeue(void)
{
  struct qentry *next;
  char *s;
  if (!tld_head) return NULL;
  s = tld_head->s;
  next = tld_head->next;
  free(tld_head);
  if (next)
    tld_head = next;
  else
    tld_head = tld_tail = NULL;
  return s;
}

static int q_send(ClientData clientData,
		  Tcl_Interp *interp,
		  int argc, char **argv)
{
  int i;
  Tcl_ResetResult(interp);
  for (i = 1; i < argc; i++) {
    if (!add_buf(argv[i])) {
      init_buf();
      Tcl_AppendResult(interp, "memory overflow", NULL);
      return TCL_ERROR;
    }
  }
  if (!enqueue(tld_buf)) {
    init_buf();
    Tcl_AppendResult(interp, "memory overflow", NULL);
    return TCL_ERROR;
  }
  clear_buf();
  return TCL_OK;
}

static char *__result[MAXTHREAD];
#define tld_result __result[this_thread()]

/* operations dealing with the Tk interpreter */

#ifdef USE_THREADS
static pthread_key_t interp_key;
#endif

static void tk_stop(void)
{
  if (tld_interp) {
    Tcl_DeleteInterp(tld_interp);
    tld_interp = NULL;
#ifdef USE_THREADS
    pthread_setspecific(interp_key, NULL);
#endif
    init_buf();
    init_queue();
  }
}

static int do_event(void)
{
  return Tcl_DoOneEvent(TCL_DONT_WAIT);
}

static void tk_do_events(void)
{
  if (!tld_interp) return;
  while (Tk_MainWindow(tld_interp) && do_event()) ;
  if (!Tk_MainWindow(tld_interp)) tk_stop();
}

static int tk_running(void)
{
  tk_do_events();
  return tld_interp != NULL;
}

static int tk_chk(void)
{
  tk_do_events();
  return !emptyqueue();
}

static char *tk_recv(void)
{
  brkflag = 0;
  tk_do_events();
  while (tld_interp && !brkflag && emptyqueue()) {
    Tcl_Sleep(1);
    tk_do_events();
  }
  brkflag = 0;
  if (emptyqueue())
    return NULL;
  else
    return dequeue();
}

static void set_result(char *s)
{
  tld_result = malloc(strlen(s)+1);
  if (tld_result) strcpy(tld_result, s);
}

static int tk_eval(char *s)
{
  int status;
  char *cmd;
  tld_result = NULL;
  if (!tld_interp) return 0;
  cmd = malloc(strlen(s)+1);
  if (!cmd) return 0;
  strcpy(cmd, s);
  status = Tcl_Eval(tld_interp, cmd);
  if (tld_interp && tld_interp->result && *tld_interp->result)
    set_result(tld_interp->result);
  else if (status == TCL_BREAK)
    set_result("invoked \"break\" outside of a loop");
  else if (status == TCL_CONTINUE)
    set_result("invoked \"continue\" outside of a loop");
  else
    set_result("");
  if (status == TCL_BREAK || status == TCL_CONTINUE)
    status = TCL_ERROR;
  tk_do_events();
  free(cmd);
  return status != TCL_ERROR;
}

static int tk_start(void)
{
  Tk_Window mainw;
  tld_result = NULL;
  if (tld_interp) return 1;
  /* Just to be safe, if Tcl has been compiled without thread support then we
     only allow a single interpreter in the main thread. */
  if (!tcl_threads && this_thread() != 0) return 0;
  /* start up a new interpreter */
  if (!(tld_interp = Tcl_CreateInterp())) return 0;
#ifdef USE_THREADS
  pthread_setspecific(interp_key, tld_interp);
#endif
  if (Tcl_Init(tld_interp) != TCL_OK) {
    if (tld_interp->result && *tld_interp->result)
      set_result(tld_interp->result);
    else
      set_result("error initializing Tcl");
    tk_stop();
    return 0;
  }
  /* intialize message queue for communication between Q and Tcl */
  init_buf();
  init_queue();
  /* create command to send messages from Tcl to Q */
  Tcl_CreateCommand(tld_interp, "q", (Tcl_CmdProc*)q_send, NULL, NULL);
  /* oddly, there are no `env' variables passed, and this one is needed */
  Tcl_SetVar2(tld_interp, "env", "DISPLAY", getenv("DISPLAY"), TCL_GLOBAL_ONLY);
  if (Tk_Init(tld_interp) != TCL_OK) {
    if (tld_interp->result && *tld_interp->result)
      set_result(tld_interp->result);
    else
      set_result("error initializing Tk");
    tk_stop();
    return 0;
  }
  /* set up an X error handler */
  mainw = Tk_MainWindow(tld_interp);
  Tk_CreateErrorHandler(Tk_Display(mainw), -1, -1, -1,
			XErrorProc, (ClientData)mainw);
  return 1;
}

FUNCTION(tk,tk,argc,argv)
{
  char *s;
  if (argc == 1 && isstr(argv[0], &s)) {
    if (tk_start()) {
      int res;
      release_lock();
      res = tk_eval(s);
      acquire_lock();
      if (!tld_result)
	return __ERROR;
      else if (res)
	if (*tld_result)
	  return mkstr(tld_result);
	else {
	  free(tld_result);
	  return mkvoid;
	}
      else
	return mkapp(mksym(sym(tk_error)), mkstr(tld_result));
    } else if (tld_result)
      return mkapp(mksym(sym(tk_error)), mkstr(tld_result));
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_set,argc,argv)
{
  char *s, *t;
  if (argc == 2 && isstr(argv[0], &s) && isstr(argv[1], &t)) {
    if (tk_start()) {
      const char *res;
      release_lock();
      res = Tcl_SetVar(tld_interp, s, t, TCL_GLOBAL_ONLY);
      acquire_lock();
      if (res)
	return mkvoid;
      else
	return __FAIL;
    } else if (tld_result)
      return mkapp(mksym(sym(tk_error)), mkstr(tld_result));
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_unset,argc,argv)
{
  char *s;
  if (argc == 1 && isstr(argv[0], &s)) {
    if (tk_start()) {
      int res;
      release_lock();
      res = Tcl_UnsetVar(tld_interp, s, TCL_GLOBAL_ONLY);
      acquire_lock();
      if (res == TCL_OK)
	return mkvoid;
      else
	return __FAIL;
    } else if (tld_result)
      return mkapp(mksym(sym(tk_error)), mkstr(tld_result));
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_get,argc,argv)
{
  char *s;
  if (argc == 1 && isstr(argv[0], &s)) {
    if (tk_start()) {
      const char *res;
      release_lock();
      res = Tcl_GetVar(tld_interp, s, TCL_GLOBAL_ONLY);
      acquire_lock();
      if (res)
	return mkstr(strdup(res));
      else
	return __FAIL;
    } else if (tld_result)
      return mkapp(mksym(sym(tk_error)), mkstr(tld_result));
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_quit,argc,argv)
{
  if (argc == 0) {
    tk_stop();
    return mkvoid;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_ready,argc,argv)
{
  if (argc == 0) {
    int res;
    release_lock();
    res = tk_running();
    acquire_lock();
    if (res)
      return mktrue;
    else
      return mkfalse;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_check,argc,argv)
{
  if (argc == 0) {
    int res;
    release_lock();
    res = tk_chk();
    acquire_lock();
    if (res)
      return mktrue;
    else
      return mkfalse;
  } else
    return __FAIL;
}

FUNCTION(tk,tk_reads,argc,argv)
{
  if (argc == 0) {
    char *s;
    release_lock();
    s = tk_recv();
    acquire_lock();
    if (s)
      return mkstr(s);
    else
      return __FAIL;
  } else
    return __FAIL;
}

#ifdef USE_THREADS
static void destruct(void *p)
{
  tk_stop();
}
#endif

#ifndef X_DISPLAY_MISSING

/* hotfix: get rid of spurious X error messages */

#include <X11/Xlib.h>

static int (*last_handler)(Display *, XErrorEvent *);

static int dummy_handler(Display *dpy, XErrorEvent *ev)
{
  return 0;
}

#endif

INIT(tk)
{
  Tcl_Mutex mut = NULL;
  /* work around a bug in recent Tcl/Tk versions */
  Tcl_FindExecutable(NULL);
  /* check whether Tcl actually supports threads */
  Tcl_MutexLock(&mut);
  if (mut) {
    Tcl_MutexUnlock(&mut);
    Tcl_MutexFinalize(&mut);
    tcl_threads = 1;
  } else
    tcl_threads = 0;
  /* register signal handlers and event sources */
  int_handler = syssignal(SIGINT, break_handler);
  term_handler = syssignal(SIGTERM, break_handler);
#ifdef SIGHUP
  hup_handler = syssignal(SIGHUP, break_handler);
#endif
  Tcl_CreateEventSource(break_setup_proc, break_check_proc, NULL);
#ifdef USE_THREADS
  /* set up a destructor which kills a running interpreter in a thread which
     is being terminated */
  pthread_key_create(&interp_key, destruct);
#endif
#ifndef X_DISPLAY_MISSING
  /* KLUDGE ALERT: some interactions between Tk and GGI produce spurious X
     error messages; ignore these for now */
  last_handler = XSetErrorHandler(dummy_handler);
#endif
}

FINI(tk)
{
  tk_stop();
  Tcl_Finalize();
#ifndef X_DISPLAY_MISSING
  XSetErrorHandler(last_handler);
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1