/*----------------------------------------------------------------------*
 * File:	rxvtperl.xs
 *----------------------------------------------------------------------*
 *
 * All portions of code are copyright by their respective author/s.
 * Copyright (c) 2005-2014 Marc Lehmann <schmorp@schmorp.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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.
 *----------------------------------------------------------------------*/

#define line_t perl_line_t
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>
#undef line_t
#undef bool // perl defines it's own bool type, except with g++... what a trap

#include "../config.h"

#include <stddef.h>
#include <stdarg.h>

#include "unistd.h"

#include "ev_cpp.h"
#include "rxvt.h"
#include "keyboard.h"
#include "rxvtutil.h"
#include "rxvtperl.h"

#include "perlxsi.c"

#define GRAB_CURSOR THIS->scrollBar.leftptr_cursor

#undef LINENO
#define LINENO(n) MOD (THIS->term_start + int(n), THIS->total_rows)
#undef ROW
#define ROW(n) THIS->row_buf [LINENO (n)]

/////////////////////////////////////////////////////////////////////////////

typedef char *		octet_string;
typedef char *		utf8_string;

typedef int		render_repeat_mode;

#if HAVE_PIXBUF
typedef GdkPixbuf *	urxvt__pixbuf;
#endif
#if HAVE_IMG
typedef rxvt_img *	urxvt__img;
typedef rxvt_img::nv	rxvt_img__nv;

/////////////////////////////////////////////////////////////////////////////

static rgba
parse_rgba (SV *sv, rxvt_screen *s = 0)
{
  rgba c;

  if (SvROK (sv))
    {
      AV *av = (AV *)SvRV (sv);

      if (SvTYPE ((SV *)av) != SVt_PVAV)
        croak ("colour must be either a colour string, or an array,");

      int len = av_len (av) + 1;

      if (len != 1 && len != 3 && len != 4)
        croak ("component colour array must have 1, 3 or 4 components,");

      c.a = rgba::MAX_CC;

      c.r = c.g = c.b = float_to_component (SvNV (*av_fetch (av, 0, 0)));

      if (len >= 3)
        {
          c.g = float_to_component (SvNV (*av_fetch (av, 1, 0)));
          c.b = float_to_component (SvNV (*av_fetch (av, 2, 0)));

          if (len >= 4)
            c.a = float_to_component (SvNV (*av_fetch (av, 3, 0)));
        }
    }
  else if (s)
    {
      rxvt_color rc;
      rc.set (s, SvPVbyte_nolen (sv));
      rc.get (c);
    }
  else
    croak ("unable to parse colour,");

  return c;
}

/////////////////////////////////////////////////////////////////////////////
#endif

static wchar_t *
sv2wcs (SV *sv)
{
  STRLEN len;
  char *str = SvPVutf8 (sv, len);
  return rxvt_utf8towcs (str, len);
}

static SV *
wcs2sv (wchar_t *wstr, int len = -1)
{
  char *str = rxvt_wcstoutf8 (wstr, len);

  SV *sv = newSVpv (str, 0);
  SvUTF8_on (sv);
  free (str);

  return sv;
}

static SV *
newSVptr (void *ptr, const char *klass)
{
  HV *hv = newHV ();
  sv_magic ((SV *)hv, 0, PERL_MAGIC_ext, (char *)ptr, 0);
  return sv_bless (newRV_noinc ((SV *)hv), gv_stashpv (klass, 1));
}

static void
clearSVptr (SV *sv)
{
  if (SvROK (sv))
    sv = SvRV (sv);

  hv_clear ((HV *)sv);
  sv_unmagic (sv, PERL_MAGIC_ext);
}

static long
SvPTR (SV *sv, const char *klass)
{
  if (!sv_derived_from (sv, klass))
    croak ("object of type %s expected", klass);

  MAGIC *mg = mg_find (SvRV (sv), PERL_MAGIC_ext);

  if (!mg)
    croak ("perl code used %s object, but C++ object is already destroyed, caught", klass);

  return (long)mg->mg_ptr;
}

#define newSVterm(term) SvREFCNT_inc ((SV *)(term)->perl.self)
#define SvTERM(sv) (rxvt_term *)SvPTR ((sv), "urxvt::term")

/////////////////////////////////////////////////////////////////////////////

#define SvOVERLAY(sv) (overlay *)SvPTR (sv, "urxvt::overlay")

class overlay : overlay_base
{
  rxvt_term *THIS;
  AV *overlay_av;
  int border;

public:
  HV *self;

  overlay (rxvt_term *THIS, int x_, int y_, int w_, int h_, rend_t rstyle, int border);
  ~overlay ();

  void show ();
  void hide ();

  void swap ();

  void set (int x, int y, SV *str, SV *rend);
};

overlay::overlay (rxvt_term *THIS, int x_, int y_, int w_, int h_, rend_t rstyle, int border)
: THIS(THIS), border(border == 2), overlay_av (0)
{
  x = x_;
  y = y_;
  w = w_;
  h = h_;

  if (w < 0) w = 0;
  if (h < 0) h = 0;

  if (border == 2)
    {
      w += 2;
      h += 2;
    }

  text = new text_t *[h];
  rend = new rend_t *[h];

  for (int y = 0; y < h; y++)
    {
      text_t *tp = text[y] = new text_t[w];
      rend_t *rp = rend[y] = new rend_t[w];

      text_t t0, t1, t2;
      rend_t r = rstyle;

      if (border == 2)
        {
          if (y == 0)
            t0 = 0x2554, t1 = 0x2550, t2 = 0x2557;
          else if (y < h - 1)
            t0 = 0x2551, t1 = 0x0020, t2 = 0x2551;
          else
            t0 = 0x255a, t1 = 0x2550, t2 = 0x255d;

          *tp++ = t0;
          *rp++ = r;

          for (int x = w - 2; x-- > 0; )
            {
              *tp++ = t1;
              *rp++ = r;
            }

          *tp = t2;
          *rp = r;
        }
      else
        for (int x = w; x-- > 0; )
          {
            *tp++ = 0x0020;
            *rp++ = r;
          }
    }

  show ();
}

overlay::~overlay ()
{
  hide ();

  for (int y = h; y--; )
    {
      delete [] text[y];
      delete [] rend[y];
    }

  delete [] text;
  delete [] rend;
}

void
overlay::show ()
{
  if (overlay_av)
    return;

  overlay_av = (AV *)SvREFCNT_inc (SvRV (
        *hv_fetch ((HV *)SvRV ((SV *)THIS->perl.self), "_overlay", 8, 0)
     ));
  av_push (overlay_av, newSViv ((long)this));

  THIS->want_refresh = 1;
  THIS->refresh_check ();
}

void
overlay::hide ()
{
  if (!overlay_av)
    return;

  int i;

  for (i = AvFILL (overlay_av); i >= 0; i--)
    if (SvIV (*av_fetch (overlay_av, i, 1)) == (long)this)
      break;

  for (; i < AvFILL (overlay_av); i++)
    av_store (overlay_av, i, SvREFCNT_inc (*av_fetch (overlay_av, i + 1, 0)));

  SvREFCNT_dec (av_pop (overlay_av));

  SvREFCNT_dec (overlay_av);
  overlay_av = 0;

  THIS->want_refresh = 1;
  THIS->refresh_check ();
}

void overlay::swap ()
{
  int ov_x = max (0, min (MOD (x, THIS->ncol), THIS->ncol - w));
  int ov_y = max (0, min (MOD (y, THIS->nrow), THIS->nrow - h));

  int ov_w = min (w, THIS->ncol - ov_x);
  int ov_h = min (h, THIS->nrow - ov_y);

  // hide cursor if it is within the overlay area
  if (IN_RANGE_EXC (THIS->screen.cur.col - ov_x, 0, ov_w)
      && IN_RANGE_EXC (THIS->screen.cur.row - ov_y, 0, ov_h))
    THIS->screen.flags &= ~Screen_VisibleCursor;

  for (int y = ov_h; y--; )
    {
      text_t *t1 = text [y];
      rend_t *r1 = rend [y];

      text_t *t2 = ROW(y + ov_y + THIS->view_start).t + ov_x;
      rend_t *r2 = ROW(y + ov_y + THIS->view_start).r + ov_x;

      for (int x = ov_w; x--; )
        {
          text_t t = *t1; *t1++ = *t2; *t2++ = t;
          rend_t r = *r1; *r1++ = *r2; *r2++ = SET_FONT (r, THIS->fontset [GET_STYLE (r)]->find_font (t));
        }
    }

}

void overlay::set (int x, int y, SV *text, SV *rend)
{
  x += border;
  y += border;

  if (!IN_RANGE_EXC (y, 0, h - border))
    return;

  wchar_t *wtext = sv2wcs (text);

  for (int col = min (wcslen (wtext), w - x - border); col--; )
    this->text [y][x + col] = wtext [col];

  free (wtext);

  if (rend)
    {
      if (!SvROK (rend) || SvTYPE (SvRV (rend)) != SVt_PVAV)
        croak ("rend must be arrayref");

      AV *av = (AV *)SvRV (rend);

      for (int col = min (AvFILL (av) + 1, w - x - border); col--; )
        this->rend [y][x + col] = SvIV (*av_fetch (av, col, 1));
    }

  THIS->want_refresh = 1;
  THIS->refresh_check ();
}

/////////////////////////////////////////////////////////////////////////////

#include "iom_perl.h"

/////////////////////////////////////////////////////////////////////////////

struct rxvt_perl_interp rxvt_perl;

static PerlInterpreter *perl;

#if 0 /* we are not a library anymore, so doing this is just not worth it */
/*THINK/TODO: this has the side effect of, of course, not calling destructors. */
/* but therse are not guaranteed anyway... */
rxvt_perl_interp::~rxvt_perl_interp ()
{
  if (perl)
    {
      localise_env set_environ (perl_environ);
      perl_destruct (perl);
      perl_free (perl);
      PERL_SYS_TERM ();
    }
}
#endif

void
rxvt_perl_interp::init ()
{
  if (!perl)
    {
      rxvt_push_locale (""); // perl init destroys current locale

      {
        perl_environ = rxvt_environ;
        localise_env set_environ (perl_environ);

        const char *args[] = {
          "",
          "-e"
          "BEGIN {"
          "   urxvt->bootstrap;"
          "   unshift @INC, '" LIBDIR "';"
          "}"
          ""
          "use urxvt;",
          0
        };
        int argc = ecb_array_length (args) - 1;
        char **argv = (char **)args;

        PERL_SYS_INIT3 (&argc, &argv, &environ);
        perl = perl_alloc ();
        perl_construct (perl);

        if (perl_parse (perl, xs_init, argc, argv, (char **)NULL)
            || perl_run (perl))
          {
            rxvt_warn ("unable to initialize perl-interpreter, continuing without.\n");

            perl_destruct (perl);
            perl_free (perl);
            perl = 0;
          }
      }

      rxvt_pop_locale ();
    }
}

void
rxvt_perl_interp::init (rxvt_term *term)
{
  init ();

  if (perl && !term->perl.self)
    {
      // runs outside of perls ENV
      term->perl.self = (void *)newSVptr ((void *)term, "urxvt::term");
      hv_store ((HV *)SvRV ((SV *)term->perl.self), "_overlay", 8, newRV_noinc ((SV *)newAV ()), 0);
      hv_store ((HV *)SvRV ((SV *)term->perl.self), "_selection", 10, newRV_noinc ((SV *)newAV ()), 0);
    }
}

void
rxvt_perl_interp::eval (const char *str)
{
  eval_pv (str, 1);
}

void
rxvt_perl_interp::usage (rxvt_term *term, int type)
{
  localise_env set_environ (perl_environ);

  ENTER;
  SAVETMPS;

  dSP;
  PUSHMARK (SP);
  EXTEND (SP, 2);
  PUSHs (sv_2mortal (newSVterm (term)));
  PUSHs (sv_2mortal (newSViv (type)));
  PUTBACK;
  call_pv ("urxvt::usage", G_VOID | G_DISCARD);

  FREETMPS;
  LEAVE;
}

uint8_t
rxvt_perl_interp::parse_resource (rxvt_term *term, const char *name, bool arg, bool longopt, bool flag, const char *value)
{
  localise_env set_environ (perl_environ);

  ENTER;
  SAVETMPS;

  dSP;
  PUSHMARK (SP);
  EXTEND (SP, 6);
  PUSHs (sv_2mortal (newSVterm (term)));
  PUSHs (sv_2mortal (newSVpv (name, 0)));
  PUSHs (arg     ? &PL_sv_yes : &PL_sv_no);
  PUSHs (longopt ? &PL_sv_yes : &PL_sv_no);
  PUSHs (flag    ? &PL_sv_yes : &PL_sv_no);
  PUSHs (value ? sv_2mortal (newSVpv (value, 0)) : &PL_sv_undef);
  PUTBACK;
  call_pv ("urxvt::parse_resource", G_SCALAR);
  SPAGAIN;

  uint8_t ret = POPi;

  FREETMPS;
  LEAVE;

  return ret;
}

static void
_keysym_resource_push (rxvt_term *term, const char *k, const char *v)
{
  unsigned int state;

  if (term->parse_keysym (k, state) == -1)
    return;

  dSP;
  XPUSHs (sv_2mortal (newSVpv (v, 0)));
  PUTBACK;
}

static void
_keysym_resources (rxvt_term *term)
{
  term->enumerate_keysym_resources (_keysym_resource_push);
}

static void
ungrab (rxvt_term *THIS)
{
  if (THIS->perl.grabtime)
    {
      XUngrabKeyboard (THIS->dpy, THIS->perl.grabtime);
      XUngrabPointer  (THIS->dpy, THIS->perl.grabtime);
      THIS->perl.grabtime = 0;
    }
}

bool
rxvt_perl_interp::invoke (rxvt_term *term, int htype, ...)
{
  if (!perl || !term->perl.self)
    return false;

  localise_env set_environ (perl_environ);

  // pre-handling of some events
  if (htype == HOOK_REFRESH_END)
    {
      AV *av = (AV *)SvRV (*hv_fetch ((HV *)SvRV ((SV *)term->perl.self), "_overlay", 8, 0));

      for (int i = 0; i <= AvFILL (av); i++)
        ((overlay *)SvIV (*av_fetch (av, i, 0)))->swap ();
    }
  else if (htype == HOOK_DESTROY)
    {
      AV *av = (AV *)SvRV (*hv_fetch ((HV *)SvRV ((SV *)term->perl.self), "_selection", 10, 0));

      for (int i = AvFILL (av); i >= 0; i--)
        {
          rxvt_selection *req = (rxvt_selection *)SvIV (*av_fetch (av, i, 0));
          delete req;
        }
    }

  bool event_consumed;

  if (term->perl.should_invoke [htype])
    {
      dSP;
      va_list ap;

      va_start (ap, htype);

      ENTER;
      SAVETMPS;

      PUSHMARK (SP);

      EXTEND (SP, 2);
      PUSHs (sv_2mortal (newSVterm (term)));
      PUSHs (sv_2mortal (newSViv (htype)));

      for (;;) {
        data_type dt = (data_type)va_arg (ap, int);

        switch (dt)
          {
            case DT_INT:
              XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
              break;

            case DT_LONG:
              XPUSHs (sv_2mortal (newSViv (va_arg (ap, long))));
              break;

            case DT_STR:
              XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
              break;

            case DT_STR_LEN:
              {
                char *str = va_arg (ap, char *);
                int len   = va_arg (ap, int);

                XPUSHs (sv_2mortal (newSVpvn (str, len)));
              }
              break;

            case DT_WCS_LEN:
              {
                wchar_t *wstr = va_arg (ap, wchar_t *);
                int wlen      = va_arg (ap, int);

                XPUSHs (sv_2mortal (wcs2sv (wstr, wlen)));
              }
             break;

            case DT_LCS_LEN:
              {
                long *lstr = va_arg (ap, long *);
                int llen   = va_arg (ap, int);

                XPUSHs (sv_2mortal (newSVpvn ((char *)lstr, llen * sizeof (long))));
              }
             break;

            case DT_XEVENT:
              {
                XEvent *xe = va_arg (ap, XEvent *);
                HV *hv = newHV ();

#               define set(name, sv) hv_store (hv, # name,  sizeof (# name) - 1, sv, 0)
#               define setiv(name, val) hv_store (hv, # name,  sizeof (# name) - 1, newSViv (val), 0)
#               define setuv(name, val) hv_store (hv, # name,  sizeof (# name) - 1, newSVuv (val), 0)
#               undef set

                setiv (type,       xe->type);
                setiv (send_event, xe->xany.send_event);
                setiv (serial,     xe->xany.serial);

                switch (xe->type)
                  {
                    case KeyPress:
                    case KeyRelease:
                    case ButtonPress:
                    case ButtonRelease:
                    case MotionNotify:
                      setuv (window,    xe->xmotion.window);
                      setuv (root,      xe->xmotion.root);
                      setuv (subwindow, xe->xmotion.subwindow);
                      setuv (time,      xe->xmotion.time);
                      setiv (x,         xe->xmotion.x);
                      setiv (y,         xe->xmotion.y);
                      setiv (row,       xe->xmotion.y / term->fheight + term->view_start);
                      setiv (col,       xe->xmotion.x / term->fwidth);
                      setiv (x_root,    xe->xmotion.x_root);
                      setiv (y_root,    xe->xmotion.y_root);
                      setuv (state,     xe->xmotion.state);

                      switch (xe->type)
                        {
                          case KeyPress:
                          case KeyRelease:
                            setuv (keycode, xe->xkey.keycode);
                            break;

                          case ButtonPress:
                          case ButtonRelease:
                            setuv (button,  xe->xbutton.button);
                            break;

                          case MotionNotify:
                            setiv (is_hint, xe->xmotion.is_hint);
                            break;
                        }

                      break;

                    case MapNotify:
                    case UnmapNotify:
                    case ConfigureNotify:
                      setuv (event,  xe->xconfigure.event);
                      setuv (window, xe->xconfigure.window);

                      switch (xe->type)
                        {
                          case ConfigureNotify:
                            setiv (x,      xe->xconfigure.x);
                            setiv (y,      xe->xconfigure.y);
                            setiv (width,  xe->xconfigure.width);
                            setiv (height, xe->xconfigure.height);
                            setuv (above,  xe->xconfigure.above);
                            break;
                        }

                      break;

                    case PropertyNotify:
                      setuv (window,       xe->xproperty.window);
                      setuv (atom,         xe->xproperty.atom);
                      setuv (time,         xe->xproperty.time);
                      setiv (state,        xe->xproperty.state);
                      break;

                    case ClientMessage:
                      setuv (window,       xe->xclient.window);
                      setuv (message_type, xe->xclient.message_type);
                      setuv (format,       xe->xclient.format);
                      setuv (l0,           xe->xclient.data.l[0]);
                      setuv (l1,           xe->xclient.data.l[1]);
                      setuv (l2,           xe->xclient.data.l[2]);
                      setuv (l3,           xe->xclient.data.l[3]);
                      setuv (l4,           xe->xclient.data.l[4]);
                      break;
                  }

                XPUSHs (sv_2mortal (newRV_noinc ((SV *)hv)));
              }
              break;

            case DT_END:
              goto call;

            default:
              rxvt_fatal ("FATAL: unable to pass data type %d\n", dt);
          }
      }

    call:
      va_end (ap);

      PUTBACK;
      int count = call_pv ("urxvt::invoke", G_ARRAY | G_EVAL);
      SPAGAIN;

      if (count)
        {
          SV *status = POPs;
          count = SvTRUE (status);
        }

      PUTBACK;
      FREETMPS;
      LEAVE;

      if (SvTRUE (ERRSV))
        {
          rxvt_warn ("perl hook %d evaluation error: %s", htype, SvPVbyte_nolen (ERRSV));
          ungrab (term); // better lose the grab than the session
        }

      event_consumed = !!count;
    }
  else
    event_consumed = false;

  // post-handling of some events
  if (htype == HOOK_REFRESH_BEGIN)
    {
      AV *av = (AV *)SvRV (*hv_fetch ((HV *)SvRV ((SV *)term->perl.self), "_overlay", 8, 0));

      for (int i = AvFILL (av); i >= 0; i--)
        ((overlay *)SvIV (*av_fetch (av, i, 0)))->swap ();
    }
  else if (htype == HOOK_DESTROY)
    {
      clearSVptr ((SV *)term->perl.self);
      SvREFCNT_dec ((SV *)term->perl.self);

      // don't allow further calls
      term->perl.self = 0;
    }

  return event_consumed;
}

void
rxvt_perl_interp::selection_finish (rxvt_selection *sel, char *data, unsigned int len)
{
  localise_env set_environ (perl_environ);

  ENTER;
  SAVETMPS;

  dSP;
  XPUSHs (sv_2mortal (newSVpvn (data, len)));
  PUTBACK;
  call_sv ((SV *)sel->cb_sv, G_VOID | G_DISCARD | G_EVAL);

  if (SvTRUE (ERRSV))
    rxvt_warn ("perl selection callback evaluation error: %s", SvPVbyte_nolen (ERRSV));

  FREETMPS;
  LEAVE;
}

/////////////////////////////////////////////////////////////////////////////

MODULE = urxvt             PACKAGE = urxvt

PROTOTYPES: ENABLE

BOOT:
{
  sv_setsv (get_sv ("urxvt::LIBDIR",   1), newSVpvn (LIBDIR,   sizeof (LIBDIR)   - 1));
  sv_setsv (get_sv ("urxvt::RESNAME",  1), newSVpvn (RESNAME,  sizeof (RESNAME)  - 1));
  sv_setsv (get_sv ("urxvt::RESCLASS", 1), newSVpvn (RESCLASS, sizeof (RESCLASS) - 1));
  sv_setsv (get_sv ("urxvt::RXVTNAME", 1), newSVpvn (RXVTNAME, sizeof (RXVTNAME) - 1));

  AV *hookname = get_av ("urxvt::HOOKNAME", 1);
# define def(sym) av_store (hookname, HOOK_ ## sym, newSVpv (# sym, 0));
# include "hookinc.h"
# undef def

  HV *option = get_hv ("urxvt::OPTION", 1);
# define def(name) hv_store (option, # name, sizeof (# name) - 1, newSVuv (Opt_ ## name), 0);
# define nodef(name)
# include "optinc.h"
# undef nodef
# undef def

  HV *stash = gv_stashpv ("urxvt", 1);
  static const struct {
    const char *name;
    IV iv;
  } *civ, const_iv[] = {
#   define const_iv(name) { # name, (IV)name }
    const_iv (HOOK_INIT),
    const_iv (HOOK_DESTROY),
    const_iv (HOOK_ACTION),
    const_iv (HOOK_OSC_SEQ),
    const_iv (HOOK_OSC_SEQ_PERL),

    const_iv (NUM_RESOURCES),
    const_iv (DEFAULT_RSTYLE),
    const_iv (OVERLAY_RSTYLE),
    const_iv (Color_Bits),
    const_iv (RS_bgShift), const_iv (RS_bgMask),
    const_iv (RS_fgShift), const_iv (RS_fgMask),
    const_iv (RS_Careful),
    const_iv (RS_fontCount),
    const_iv (RS_fontShift),
    const_iv (RS_fontMask),
    const_iv (RS_baseattrMask),
    const_iv (RS_attrMask),
    const_iv (RS_redraw),
    const_iv (RS_Sel),
    const_iv (RS_Bold),
    const_iv (RS_Italic),
    const_iv (RS_Blink),
    const_iv (RS_RVid),
    const_iv (RS_Uline),

    // TODO: should support all colour constants, create colorinc.h &c
    const_iv (Color_fg),
    const_iv (Color_bg),
#if OFF_FOCUS_FADING
    const_iv (Color_fade),
#endif
    const_iv (Color_pointer_fg),
    const_iv (Color_pointer_bg),
    const_iv (Color_border),
    const_iv (NRS_COLORS),
    const_iv (TOTAL_COLORS),

    const_iv (CurrentTime),
    const_iv (ShiftMask),
    const_iv (LockMask),
    const_iv (ControlMask),
    const_iv (Mod1Mask),
    const_iv (Mod2Mask),
    const_iv (Mod3Mask),
    const_iv (Mod4Mask),
    const_iv (Mod5Mask),
    const_iv (Button1Mask),
    const_iv (Button2Mask),
    const_iv (Button3Mask),
    const_iv (Button4Mask),
    const_iv (Button5Mask),
    const_iv (AnyModifier),

    const_iv (NoSymbol),
    const_iv (GrabModeSync),
    const_iv (GrabModeAsync),

    const_iv (NoEventMask),
    const_iv (KeyPressMask),
    const_iv (KeyReleaseMask),
    const_iv (ButtonPressMask),
    const_iv (ButtonReleaseMask),
    const_iv (EnterWindowMask),
    const_iv (LeaveWindowMask),
    const_iv (PointerMotionMask),
    const_iv (PointerMotionHintMask),
    const_iv (Button1MotionMask),
    const_iv (Button2MotionMask),
    const_iv (Button3MotionMask),
    const_iv (Button4MotionMask),
    const_iv (Button5MotionMask),
    const_iv (ButtonMotionMask),
    const_iv (KeymapStateMask),
    const_iv (ExposureMask),
    const_iv (VisibilityChangeMask),
    const_iv (StructureNotifyMask),
    const_iv (ResizeRedirectMask),
    const_iv (SubstructureNotifyMask),
    const_iv (SubstructureRedirectMask),
    const_iv (FocusChangeMask),
    const_iv (PropertyChangeMask),
    const_iv (ColormapChangeMask),
    const_iv (OwnerGrabButtonMask),

    const_iv (KeyPress),
    const_iv (KeyRelease),
    const_iv (ButtonPress),
    const_iv (ButtonRelease),
    const_iv (MotionNotify),
    const_iv (EnterNotify),
    const_iv (LeaveNotify),
    const_iv (FocusIn),
    const_iv (FocusOut),
    const_iv (KeymapNotify),
    const_iv (Expose),
    const_iv (GraphicsExpose),
    const_iv (NoExpose),
    const_iv (VisibilityNotify),
    const_iv (CreateNotify),
    const_iv (DestroyNotify),
    const_iv (UnmapNotify),
    const_iv (MapNotify),
    const_iv (MapRequest),
    const_iv (ReparentNotify),
    const_iv (ConfigureNotify),
    const_iv (ConfigureRequest),
    const_iv (GravityNotify),
    const_iv (ResizeRequest),
    const_iv (CirculateNotify),
    const_iv (CirculateRequest),
    const_iv (PropertyNotify),
    const_iv (SelectionClear),
    const_iv (SelectionRequest),
    const_iv (SelectionNotify),
    const_iv (ColormapNotify),
    const_iv (ClientMessage),
    const_iv (MappingNotify),
#   if ENABLE_XIM_ONTHESPOT
    const_iv (XIMReverse),
    const_iv (XIMUnderline),
    const_iv (XIMHighlight),
    const_iv (XIMPrimary),
    const_iv (XIMSecondary),
    const_iv (XIMTertiary),
    const_iv (XIMVisibleToForward),
    const_iv (XIMVisibleToBackword),
    const_iv (XIMVisibleToCenter),
#if XRENDER
    const_iv (PictStandardARGB32),
    const_iv (PictStandardRGB24),
    const_iv (PictStandardA8),
    const_iv (PictStandardA4),
    const_iv (PictStandardA1),
    const_iv (RepeatNone),
    const_iv (RepeatNormal),
    const_iv (RepeatPad),
    const_iv (RepeatReflect),
    // all versions
    const_iv (PictOpClear),
    const_iv (PictOpSrc),
    const_iv (PictOpDst),
    const_iv (PictOpOver),
    const_iv (PictOpOverReverse),
    const_iv (PictOpIn),
    const_iv (PictOpInReverse),
    const_iv (PictOpOut),
    const_iv (PictOpOutReverse),
    const_iv (PictOpAtop),
    const_iv (PictOpAtopReverse),
    const_iv (PictOpXor),
    const_iv (PictOpAdd),
    const_iv (PictOpSaturate),
    // 0.2+
    const_iv (PictOpDisjointClear),
    const_iv (PictOpDisjointSrc),
    const_iv (PictOpDisjointDst),
    const_iv (PictOpDisjointOver),
    const_iv (PictOpDisjointOverReverse),
    const_iv (PictOpDisjointIn),
    const_iv (PictOpDisjointInReverse),
    const_iv (PictOpDisjointOut),
    const_iv (PictOpDisjointOutReverse),
    const_iv (PictOpDisjointAtop),
    const_iv (PictOpDisjointAtopReverse),
    const_iv (PictOpDisjointXor),
    const_iv (PictOpConjointClear),
    const_iv (PictOpConjointSrc),
    const_iv (PictOpConjointDst),
    const_iv (PictOpConjointOver),
    const_iv (PictOpConjointOverReverse),
    const_iv (PictOpConjointIn),
    const_iv (PictOpConjointInReverse),
    const_iv (PictOpConjointOut),
    const_iv (PictOpConjointOutReverse),
    const_iv (PictOpConjointAtop),
    const_iv (PictOpConjointAtopReverse),
    const_iv (PictOpConjointXor),
    // 0.11+
    const_iv (PictOpMultiply),
    const_iv (PictOpScreen),
    const_iv (PictOpOverlay),
    const_iv (PictOpDarken),
    const_iv (PictOpLighten),
    const_iv (PictOpColorDodge),
    const_iv (PictOpColorBurn),
    const_iv (PictOpHardLight),
    const_iv (PictOpSoftLight),
    const_iv (PictOpDifference),
    const_iv (PictOpExclusion),
    const_iv (PictOpHSLHue),
    const_iv (PictOpHSLSaturation),
    const_iv (PictOpHSLColor),
    const_iv (PictOpHSLLuminosity),
#endif
#   if 0
    const_iv (XIMForwardChar),
    const_iv (XIMBackwardChar),
    const_iv (XIMForwardWord),
    const_iv (XIMBackwardWord),
    const_iv (XIMCaretUp),
    const_iv (XIMCaretDown),
    const_iv (XIMNextLine),
    const_iv (XIMPreviousLine),
    const_iv (XIMLineStart),
    const_iv (XIMLineEnd),
    const_iv (XIMAbsolutePosition),
    const_iv (XIMDontChange),
#   endif
#   endif

    /* DEC private modes */
    const_iv (PrivMode_132),
    const_iv (PrivMode_132OK),
    const_iv (PrivMode_rVideo),
    const_iv (PrivMode_relOrigin),
    const_iv (PrivMode_Screen),
    const_iv (PrivMode_Autowrap),
    const_iv (PrivMode_aplCUR),
    const_iv (PrivMode_aplKP),
    const_iv (PrivMode_HaveBackSpace),
    const_iv (PrivMode_BackSpace),
    const_iv (PrivMode_ShiftKeys),
    const_iv (PrivMode_VisibleCursor),
    const_iv (PrivMode_MouseX10),
    const_iv (PrivMode_MouseX11),
    const_iv (PrivMode_scrollBar),
    const_iv (PrivMode_TtyOutputInh),
    const_iv (PrivMode_Keypress),
    const_iv (PrivMode_smoothScroll),
    const_iv (PrivMode_vt52),
    const_iv (PrivMode_LFNL),
    const_iv (PrivMode_MouseBtnEvent),
    const_iv (PrivMode_MouseAnyEvent),
    const_iv (PrivMode_BracketPaste),
    const_iv (PrivMode_ExtMouseUTF8),
    const_iv (PrivMode_ExtMouseUrxvt),
    const_iv (PrivMode_BlinkingCursor),
    const_iv (PrivMode_mouse_report),
    const_iv (PrivMode_Default),
  };

  for (civ = const_iv + ecb_array_length (const_iv); civ > const_iv; civ--)
    newCONSTSUB (stash, (char *)civ[-1].name, newSViv (civ[-1].iv));
}

void
log (utf8_string msg)
	CODE:
        rxvt_log ("%s", msg);

void
warn (utf8_string msg)
	CODE:
        rxvt_warn ("%s", msg);

void
fatal (utf8_string msg)
	CODE:
        rxvt_fatal ("%s", msg);

void
_exit (int status)

void
catch_fatal (SV *block)
	PROTOTYPE: &
        CODE:
        try
          {
            PUSHMARK (SP);
            PUTBACK;
            call_sv (block, G_VOID | G_DISCARD);
            SPAGAIN;
          }
        catch (const rxvt_failure_exception &e)
          {
            croak ("rxvt_fatal exception caught, trying to continue.");
          }

NV
NOW ()
	CODE:
        RETVAL = ev::now ();
        OUTPUT:
        RETVAL

int
GET_BASEFG (int rend)
	CODE:
        RETVAL = GET_BASEFG (rend);
	OUTPUT:
        RETVAL

int
GET_BASEBG (int rend)
	CODE:
        RETVAL = GET_BASEBG (rend);
	OUTPUT:
        RETVAL

int
SET_FGCOLOR (int rend, int new_color)
	CODE:
        RETVAL = SET_FGCOLOR (rend, clamp (new_color, 0, TOTAL_COLORS - 1));
	OUTPUT:
        RETVAL

int
SET_BGCOLOR (int rend, int new_color)
	CODE:
        RETVAL = SET_BGCOLOR (rend, clamp (new_color, 0, TOTAL_COLORS - 1));
	OUTPUT:
        RETVAL

int
GET_CUSTOM (int rend)
	CODE:
        RETVAL = (rend & RS_customMask) >> RS_customShift;
	OUTPUT:
        RETVAL

int
SET_CUSTOM (int rend, int new_value)
	CODE:
{
        if (!IN_RANGE_EXC (new_value, 0, RS_customCount))
          croak ("custom value out of range, must be 0..%lu", RS_customCount - 1);

        RETVAL = (rend & ~RS_customMask)
               | ((new_value << RS_customShift) & RS_customMask);
}
	OUTPUT:
        RETVAL

void
termlist ()
	PPCODE:
{
        EXTEND (SP, rxvt_term::termlist.size ());

        for (rxvt_term **t = rxvt_term::termlist.begin (); t < rxvt_term::termlist.end (); t++)
          if ((*t)->perl.self)
            PUSHs (sv_2mortal (newSVterm (*t)));
}

IV
_new_selection_request (rxvt_term *term, int selnum, Time tm, Window win, Atom prop, SV *cb)
	CODE:
        rxvt_selection *req = new rxvt_selection (term->display, selnum, tm, win, prop, term);
        req->cb_sv = newSVsv (cb);
        AV *av = (AV *)SvRV (*hv_fetch ((HV *)SvRV ((SV *)term->perl.self), "_selection", 10, 0));
        av_push (av, newSViv ((IV)req));
        RETVAL = (IV)req;
	OUTPUT:
        RETVAL

void
_delete_selection_request (IV req_)
	CODE:
        rxvt_selection *req = (rxvt_selection *)req_;
        AV *av = (AV *)SvRV (*hv_fetch ((HV *)SvRV ((SV *)req->term->perl.self), "_selection", 10, 0));
        int i;

        for (i = AvFILL (av); i >= 0; i--)
          if (SvIV (*av_fetch (av, i, 1)) == req_)
            break;

        for (; i < AvFILL (av); i++)
          av_store (av, i, SvREFCNT_inc (*av_fetch (av, i + 1, 0)));

        av_pop (av);

        delete req;

MODULE = urxvt             PACKAGE = urxvt::term

SV *
_new (AV *env, AV *arg)
	CODE:
{
        rxvt_term *term = new rxvt_term;

	stringvec *argv = new stringvec;
        for (int i = 0; i <= AvFILL (arg); i++)
          argv->push_back (strdup (SvPVbyte_nolen (*av_fetch (arg, i, 1))));

	stringvec *envv = new stringvec;
        for (int i = AvFILL (env) + 1; i--; )
          envv->push_back (strdup (SvPVbyte_nolen (*av_fetch (env, i, 1))));

        try
          {
            term->init (argv, envv);
          }
        catch (const class rxvt_failure_exception &e)
          {
            term->destroy ();
            croak ("error while initializing new terminal instance");
          }

        RETVAL = term && term->perl.self
                 ? newSVterm (term) : &PL_sv_undef;
}
	OUTPUT:
        RETVAL

void
rxvt_term::destroy ()

void
rxvt_term::set_should_invoke (int htype, int inc)
	CODE:
        uint8_t &count = THIS->perl.should_invoke [htype];
        uint8_t prev = count;
        count += inc;
        if (!prev != !count)
	  {
            // hook status changed, react
            switch (htype)
              {
                case HOOK_POSITION_CHANGE:
                  if (count)
                    THIS->get_window_origin (THIS->parent_x, THIS->parent_y);
              }
	  }

void
rxvt_term::put_option_db (octet_string specifier, octet_string value)
	CODE:
        XrmPutStringResource (&THIS->option_db, specifier, value);

void
rxvt_term::_keysym_resources ()
	PPCODE:
        PUTBACK;
        _keysym_resources (THIS);
        SPAGAIN;

int
rxvt_term::grab_button (int button, U32 modifiers, Window window = THIS->vt)
	CODE:
        RETVAL = XGrabButton (THIS->dpy, button, modifiers, window, 1,
                              ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask,
                              GrabModeSync, GrabModeSync, None, GRAB_CURSOR);
	OUTPUT: RETVAL

int
rxvt_term::ungrab_button (int button, U32 modifiers, Window window = THIS->vt)
	CODE:
        RETVAL = XUngrabButton (THIS->dpy, button, modifiers, window);
	OUTPUT: RETVAL

void
rxvt_term::XGrabKey (int keycode, U32 modifiers, Window window = THIS->vt, \
                     int owner_events = 1, int pointer_mode = GrabModeAsync, int keyboard_mode = GrabModeAsync)
	CODE:
        XGrabKey (THIS->dpy, keycode, modifiers, window, owner_events, pointer_mode, keyboard_mode);

void
rxvt_term::XUngrabKey (int keycode, U32 modifiers, Window window = THIS->vt)
	CODE:
	XUngrabKey (THIS->dpy, keycode, modifiers, window);

bool
rxvt_term::grab (Time eventtime, int sync = 0)
	CODE:
{
        int mode = sync ? GrabModeSync : GrabModeAsync;

        THIS->perl.grabtime = 0;

        if (!XGrabPointer (THIS->dpy, THIS->vt, 0,
                           ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask,
                           mode, mode, None, GRAB_CURSOR, eventtime))
          if (!XGrabKeyboard (THIS->dpy, THIS->vt, 0, mode, mode, eventtime))
            THIS->perl.grabtime = eventtime;
          else
            XUngrabPointer (THIS->dpy, eventtime);

        RETVAL = !!THIS->perl.grabtime;
}
	OUTPUT:
        RETVAL

void
rxvt_term::allow_events_async ()
	CODE:
        XAllowEvents (THIS->dpy, AsyncBoth,      THIS->perl.grabtime);

void
rxvt_term::allow_events_sync ()
	CODE:
        XAllowEvents (THIS->dpy, SyncBoth,       THIS->perl.grabtime);

void
rxvt_term::allow_events_replay ()
	CODE:
        XAllowEvents (THIS->dpy, ReplayPointer,  THIS->perl.grabtime);
        XAllowEvents (THIS->dpy, ReplayKeyboard, THIS->perl.grabtime);

void
rxvt_term::ungrab ()
	CODE:
        ungrab (THIS);

int
rxvt_term::XStringToKeysym (octet_string string)
	CODE:
        RETVAL = XStringToKeysym (string);
	OUTPUT: RETVAL

char *
rxvt_term::XKeysymToString (int sym)
	CODE:
        RETVAL = XKeysymToString (sym);
	OUTPUT: RETVAL

int
rxvt_term::XKeysymToKeycode (int sym)
	CODE:
        RETVAL = XKeysymToKeycode (THIS->dpy, sym);
	OUTPUT: RETVAL

int
rxvt_term::XKeycodeToKeysym (int code, int index)
	CODE:
        RETVAL = rxvt_XKeycodeToKeysym (THIS->dpy, code, index);
	OUTPUT: RETVAL

int
rxvt_term::strwidth (SV *str)
	CODE:
{
        wchar_t *wstr = sv2wcs (str);

	rxvt_push_locale (THIS->locale);
        RETVAL = 0;
        for (wchar_t *wc = wstr; *wc; wc++)
          {
            int w = WCWIDTH (*wc);

            if (w)
              RETVAL += max (w, 1);
          }
        rxvt_pop_locale ();

        free (wstr);
}
	OUTPUT:
        RETVAL

SV *
rxvt_term::locale_encode (SV *str)
	CODE:
{
	if (!SvOK (str))
          XSRETURN_UNDEF;

        wchar_t *wstr = sv2wcs (str);

	rxvt_push_locale (THIS->locale);
        char *mbstr = rxvt_wcstombs (wstr);
        rxvt_pop_locale ();

        free (wstr);

        RETVAL = newSVpv (mbstr, 0);
        free (mbstr);
}
	OUTPUT:
        RETVAL

SV *
rxvt_term::locale_decode (SV *octets)
	CODE:
{
	if (!SvOK (octets))
          XSRETURN_UNDEF;

	STRLEN len;
        char *data = SvPVbyte (octets, len);

	rxvt_push_locale (THIS->locale);
        wchar_t *wstr = rxvt_mbstowcs (data, len);
        rxvt_pop_locale ();

        RETVAL = wcs2sv (wstr);
        free (wstr);
}
	OUTPUT:
        RETVAL

#define TERM_OFFSET(sym) offsetof (TermWin_t, sym)

#define TERM_OFFSET_width       TERM_OFFSET(width)
#define TERM_OFFSET_height      TERM_OFFSET(height)
#define TERM_OFFSET_fwidth      TERM_OFFSET(fwidth)
#define TERM_OFFSET_fheight     TERM_OFFSET(fheight)
#define TERM_OFFSET_fbase       TERM_OFFSET(fbase)
#define TERM_OFFSET_nrow        TERM_OFFSET(nrow)
#define TERM_OFFSET_ncol        TERM_OFFSET(ncol)
#define TERM_OFFSET_focus       TERM_OFFSET(focus)
#define TERM_OFFSET_mapped      TERM_OFFSET(mapped)
#define TERM_OFFSET_int_bwidth  TERM_OFFSET(int_bwidth)
#define TERM_OFFSET_ext_bwidth  TERM_OFFSET(ext_bwidth)
#define TERM_OFFSET_lineSpace   TERM_OFFSET(lineSpace)
#define TERM_OFFSET_letterSpace TERM_OFFSET(letterSpace)
#define TERM_OFFSET_saveLines   TERM_OFFSET(saveLines)
#define TERM_OFFSET_total_rows  TERM_OFFSET(total_rows)
#define TERM_OFFSET_top_row     TERM_OFFSET(top_row)

int
rxvt_term::width (int new_value = NO_INIT)
	ALIAS:
           width       = TERM_OFFSET_width
           height      = TERM_OFFSET_height
           fwidth      = TERM_OFFSET_fwidth
           fheight     = TERM_OFFSET_fheight
           fbase       = TERM_OFFSET_fbase
           nrow        = TERM_OFFSET_nrow
           ncol        = TERM_OFFSET_ncol
           focus       = TERM_OFFSET_focus
           mapped      = TERM_OFFSET_mapped
           int_bwidth  = TERM_OFFSET_int_bwidth
           ext_bwidth  = TERM_OFFSET_ext_bwidth
           lineSpace   = TERM_OFFSET_lineSpace
           letterSpace = TERM_OFFSET_letterSpace
           saveLines   = TERM_OFFSET_saveLines
           total_rows  = TERM_OFFSET_total_rows
           top_row     = TERM_OFFSET_top_row
	CODE:
        RETVAL = *(int *)((char *)THIS + ix);
        if (items > 1)
          *(int *)((char *)THIS + ix) = new_value;
        OUTPUT:
        RETVAL

unsigned int
rxvt_term::ModLevel3Mask ()
	ALIAS:
           ModLevel3Mask  = 0
           ModMetaMask    = 1
           ModNumLockMask = 2
           current_screen = 3
           hidden_cursor  = 4
           priv_modes     = 5
	CODE:
        switch (ix)
          {
            case 0: RETVAL = THIS->ModLevel3Mask;  break;
            case 1: RETVAL = THIS->ModMetaMask;    break;
            case 2: RETVAL = THIS->ModNumLockMask; break;
            case 3: RETVAL = THIS->current_screen; break;
#ifdef CURSOR_BLINK
            case 4: RETVAL = THIS->hidden_cursor;  break;
#else
            case 4: RETVAL = 0;                    break;
#endif
            case 5: RETVAL = THIS->priv_modes;     break;
          }
        OUTPUT:
        RETVAL

char *
rxvt_term::display_id ()
	ALIAS:
           display_id = 0
           locale     = 1
	CODE:
        switch (ix)
          {
            case 0: RETVAL = THIS->display->id; break;
            case 1: RETVAL = THIS->locale;      break;
          }
        OUTPUT:
        RETVAL

SV *
rxvt_term::envv ()
	ALIAS:
        argv = 1
	PPCODE:
{
	stringvec *vec = ix ? THIS->argv : THIS->envv;

        EXTEND (SP, vec->size ());

        for (char **i = vec->begin (); i != vec->end (); ++i)
          if (*i)
            PUSHs (sv_2mortal (newSVpv (*i, 0)));
}

int
rxvt_term::pty_ev_events (int events = ev::UNDEF)
	CODE:
        RETVAL = THIS->pty_ev.events;
        if (events != ev::UNDEF)
          THIS->pty_ev.set (events);
	OUTPUT:
        RETVAL

int
rxvt_term::pty_fd ()
	CODE:
        RETVAL = THIS->pty->pty;
	OUTPUT:
        RETVAL

Window
rxvt_term::parent ()
	CODE:
        RETVAL = THIS->parent;
        OUTPUT:
        RETVAL

int
rxvt_term::parent_x ()
	CODE:
        RETVAL = THIS->parent_x;
        OUTPUT:
        RETVAL

int
rxvt_term::parent_y ()
	CODE:
        RETVAL = THIS->parent_y;
        OUTPUT:
        RETVAL

Window
rxvt_term::vt ()
	CODE:
        RETVAL = THIS->vt;
        OUTPUT:
        RETVAL

void
rxvt_term::vt_emask_add (U32 emask)
	CODE:
        THIS->vt_emask_perl |= emask;
        THIS->vt_select_input ();

U32
rxvt_term::rstyle (U32 new_rstyle = THIS->rstyle)
	CODE:
        RETVAL = THIS->rstyle;
        THIS->rstyle = new_rstyle;
        OUTPUT:
	RETVAL

int
rxvt_term::view_start (int newval = 0)
	PROTOTYPE: $;$
	CODE:
{
        RETVAL = THIS->view_start;
        if (items > 1)
          THIS->scr_changeview (newval);
}
        OUTPUT:
	RETVAL

void
rxvt_term::set_urgency (bool enable)

void
rxvt_term::focus_in ()

void
rxvt_term::focus_out ()

void
rxvt_term::key_press (unsigned int state, unsigned int keycode, Time time = CurrentTime)
	ALIAS:
           key_release = 1
	CODE:
{
        XKeyEvent xkey;

        memset (&xkey, 0, sizeof (xkey));

        xkey.time      = time;
        xkey.state     = state;
        xkey.keycode   = keycode;

        xkey.type      = ix ? KeyRelease : KeyPress;
        xkey.display   = THIS->dpy;
        xkey.window    = THIS->vt;
        xkey.root      = THIS->display->root;
        xkey.subwindow = THIS->vt;

        if (ix)
          THIS->key_release (xkey);
        else
          THIS->key_press (xkey);
}

void
rxvt_term::want_refresh ()
	CODE:
        THIS->want_refresh = 1;
	THIS->refresh_check ();

void
rxvt_term::refresh_check ()

void
rxvt_term::ROW_t (int row_number, SV *new_text = 0, int start_col = 0, int start_ofs = 0, int max_len = MAX_COLS)
	PPCODE:
{
        if (!IN_RANGE_EXC (row_number, THIS->top_row, THIS->nrow))
          XSRETURN_EMPTY;

        line_t &l = ROW(row_number);

        if (GIMME_V != G_VOID)
          {
            wchar_t *wstr = rxvt_temp_buf<wchar_t> (THIS->ncol);

            for (int col = 0; col < THIS->ncol; col++)
              wstr [col] = l.t [col];

            XPUSHs (sv_2mortal (wcs2sv (wstr, THIS->ncol)));
          }

        if (new_text)
          {
            wchar_t *wstr = sv2wcs (new_text);

            int len = min (wcslen (wstr) - start_ofs, max_len);

            if (start_col < 0 || start_col + len > THIS->ncol)
              {
                free (wstr);
                croak ("new_text extends beyond horizontal margins");
              }

            for (int col = start_col; col < start_col + len; col++)
              {
                l.t [col] = wstr [start_ofs + col - start_col];
                l.r [col] = SET_FONT (l.r [col], THIS->fontset [GET_STYLE (l.r [col])]->find_font (l.t [col]));
              }

            free (wstr);
          }
}

void
rxvt_term::ROW_r (int row_number, SV *new_rend = 0, int start_col = 0, int start_ofs = 0, int max_len = MAX_COLS)
	PPCODE:
{
        if (!IN_RANGE_EXC (row_number, THIS->top_row, THIS->nrow))
          XSRETURN_EMPTY;

        line_t &l = ROW(row_number);

        if (GIMME_V != G_VOID)
          {
            AV *av = newAV ();

            av_extend (av, THIS->ncol - 1);
            for (int col = 0; col < THIS->ncol; col++)
              av_store (av, col, newSViv (l.r [col]));

            XPUSHs (sv_2mortal (newRV_noinc ((SV *)av)));
          }

        if (new_rend)
          {
            if (!SvROK (new_rend) || SvTYPE (SvRV (new_rend)) != SVt_PVAV)
              croak ("new_rend must be arrayref");

            AV *av = (AV *)SvRV (new_rend);
            int len = min (AvFILL (av) + 1 - start_ofs, max_len);

            if (start_col < 0 || start_col + len > THIS->ncol)
              croak ("new_rend array extends beyond horizontal margins");

            for (int col = start_col; col < start_col + len; col++)
              {
                rend_t r = SvIV (*av_fetch (av, start_ofs + col - start_col, 1)) & ~RS_fontMask;

                l.r [col] = SET_FONT (r, THIS->fontset [GET_STYLE (r)]->find_font (l.t [col]));
              }
          }
}

int
rxvt_term::ROW_l (int row_number, int new_length = -1)
	CODE:
{
        if (!IN_RANGE_EXC (row_number, THIS->top_row, THIS->nrow))
          XSRETURN_EMPTY;

        line_t &l = ROW(row_number);
        RETVAL = l.l;

        if (new_length >= 0)
          l.l = new_length;
}
        OUTPUT:
        RETVAL

bool
rxvt_term::ROW_is_longer (int row_number, int new_is_longer = -1)
	CODE:
{
        if (!IN_RANGE_EXC (row_number, THIS->top_row, THIS->nrow))
          XSRETURN_EMPTY;

        line_t &l = ROW(row_number);
        RETVAL = l.is_longer ();

        if (new_is_longer >= 0)
          l.is_longer (new_is_longer);
}
        OUTPUT:
        RETVAL

SV *
rxvt_term::special_encode (SV *string)
	CODE:
{
        wchar_t *wstr = sv2wcs (string);
        int wlen = wcslen (wstr);
        wchar_t *rstr = rxvt_temp_buf<wchar_t> (wlen * 2); // cannot become longer

	rxvt_push_locale (THIS->locale);

        wchar_t *r = rstr;
        for (wchar_t *s = wstr; *s; s++)
          {
            int w = WCWIDTH (*s);

            if (w == 0)
              {
                if (r == rstr)
                  croak ("leading combining character unencodable");

                unicode_t n = rxvt_compose (r[-1], *s);
                if (n == NOCHAR)
                  n = rxvt_composite.compose (r[-1], *s);

                r[-1] = n;
              }
#if !UNICODE_3
            else if (*s >= 0x10000)
              *r++ = rxvt_composite.compose (*s);
#endif
            else
              *r++ = *s;

            // the *2 above only allows wcwidth <= 2
            if (w > 1)
              *r++ = NOCHAR;
          }

	rxvt_pop_locale ();

        free (wstr);
        RETVAL = wcs2sv (rstr, r - rstr);
}
	OUTPUT:
        RETVAL

SV *
rxvt_term::special_decode (SV *text)
	CODE:
{
        wchar_t *wstr = sv2wcs (text);
        int dlen = 0;

        // find length
        for (wchar_t *s = wstr; *s; s++)
          if (*s == NOCHAR)
            ;
          else if (IS_COMPOSE (*s))
            dlen += rxvt_composite.expand (*s);
          else
            dlen++;

        wchar_t *rstr = rxvt_temp_buf<wchar_t> (dlen);

        // decode
        wchar_t *r = rstr;
        for (wchar_t *s = wstr; *s; s++)
          if (*s == NOCHAR)
            ;
          else if (IS_COMPOSE (*s))
            r += rxvt_composite.expand (*s, r);
          else
            *r++ = *s;

        free (wstr);
        RETVAL = wcs2sv (rstr, r - rstr);
}
	OUTPUT:
        RETVAL

void
rxvt_term::_resource (octet_string name, int index, SV *newval = 0)
	PPCODE:
{
	static const struct resval { const char *name; int value; } *rs, rslist [] = {
#	  define def(name) { # name, Rs_ ## name },
#	  define reserve(name,count)
#	  include "rsinc.h"
#	  undef def
#	  undef reserve
        };

        rs = rslist + ecb_array_length (rslist);

        if (*name)
          {
            do {
              if (rs-- == rslist)
                croak ("no such resource '%s', requested", name);
            } while (strcmp (name, rs->name));

            index += rs->value;
          }
        else
          {
            --rs;
            name = (octet_string)"";
          }

        if (!IN_RANGE_EXC (index, 0, NUM_RESOURCES))
          croak ("requested out-of-bound resource %s+%d,", name, index - rs->value);

        if (GIMME_V != G_VOID)
          XPUSHs (THIS->rs [index] ? sv_2mortal (newSVpv (THIS->rs [index], 0)) : &PL_sv_undef);

        if (newval)
          {
            if (SvOK (newval))
              {
                char *str = strdup (SvPVbyte_nolen (newval));
                THIS->rs [index] = str;
                THIS->allocated.push_back (str);
              }
            else
              THIS->rs [index] = 0;
          }
}

const char *
rxvt_term::x_resource (octet_string name)

bool
rxvt_term::option (U8 optval, int set = -1)
	CODE:
{
	RETVAL = THIS->option (optval);

        if (set >= 0)
          {
            THIS->set_option (optval, set);

            if (THIS->init_done) // avoid doing this before START
              switch (optval)
                {
                  case Opt_skipBuiltinGlyphs:
                    THIS->set_fonts ();
                    THIS->scr_remap_chars ();
                    THIS->scr_touch (true);
                    THIS->want_refresh = 1;
                    THIS->refresh_check ();
                    break;
#ifdef CURSOR_BLINK
                  case Opt_cursorBlink:
                    THIS->cursor_blink_reset ();
                    break;
#endif

                  case Opt_cursorUnderline:
                    THIS->cursor_type = set ? 1 : 0;
                    THIS->want_refresh = 1;
                    THIS->refresh_check ();
                    break;

#                  case Opt_scrollBar_floating:
#                  case Opt_scrollBar_right:
#                    THIS->resize_all_windows (THIS->width, THIS->height, 1);
#                    break;
                }
          }
}
        OUTPUT:
        RETVAL

SV *
rxvt_term::lookup_keysym (int keysym, unsigned int state)
        CODE:
{
        keysym_t *key = THIS->keyboard->lookup_keysym (THIS, keysym, state);
        RETVAL = key ? sv_2mortal (newSVpv (key->str, 0)) : &PL_sv_undef;
}
        OUTPUT:
        RETVAL

bool
rxvt_term::bind_action (octet_string keysym, octet_string action)
        ALIAS:
           parse_keysym = 1
	CODE:
        RETVAL = 0 < THIS->bind_action (keysym, action);
        THIS->keyboard->register_done ();
	OUTPUT:
        RETVAL

void
rxvt_term::register_command (int keysym, unsigned int state, SV *str)
        CODE:
        wchar_t *wstr = sv2wcs (str);
        THIS->keyboard->register_action (keysym, state, wstr);
        free (wstr);

void
rxvt_term::screen_cur (...)
	PROTOTYPE: $;$$
        ALIAS:
           screen_cur     = 0
           selection_beg  = 1
           selection_end  = 2
           selection_mark = 3
        PPCODE:
{
        row_col_t &rc = ix == 0 ? THIS->screen.cur
                      : ix == 1 ? THIS->selection.beg
                      : ix == 2 ? THIS->selection.end
                      :           THIS->selection.mark;

        if (GIMME_V != G_VOID)
          {
            EXTEND (SP, 2);
            PUSHs (sv_2mortal (newSViv (rc.row)));
            PUSHs (sv_2mortal (newSViv (rc.col)));
          }

        if (items >= 3)
          {
            rc.row = SvIV (ST (1));
            rc.col = SvIV (ST (2));

            if (ix == 2)
              {
                if (rc.col == 0)
                  {
                    // col == 0 means end of previous line
                    rc.row--;
                    rc.col = THIS->ncol;
                  }
                else if (IN_RANGE_EXC (rc.row, THIS->top_row, THIS->nrow)
                         && rc.col > ROW(rc.row).l)
                  {
                    // col >= length means while line and add newline
                    rc.col = THIS->ncol;
                  }
              }

            clamp_it (rc.col, 0, THIS->ncol);
            clamp_it (rc.row, THIS->top_row, THIS->nrow - 1);

            if (ix)
              {
                THIS->selection.screen = THIS->current_screen;

                THIS->selection_changed ();
                THIS->refresh_check ();
              }
          }
}

int
rxvt_term::selection_screen (int screen = -1)
	CODE:
        RETVAL = THIS->selection.screen;
        if (screen >= 0)
          THIS->selection.screen = screen;
        OUTPUT:
        RETVAL

void
rxvt_term::selection_request (Time tm, int selnum)

void
rxvt_term::selection_clear (bool clipboard = false)

void
rxvt_term::selection_make (Time eventtime, bool rect = false)
	CODE:
        THIS->selection.op = SELECTION_CONT;
        THIS->selection.rect = rect;
        THIS->selection_make (eventtime);

int
rxvt_term::selection_grab (Time eventtime, bool clipboard = false)

void
rxvt_term::selection (SV *newtext = 0, bool clipboard = false)
        PPCODE:
{
        wchar_t * &text   = clipboard ? THIS->selection.clip_text : THIS->selection.text;
        unsigned int &len = clipboard ? THIS->selection.clip_len  : THIS->selection.len;

        if (GIMME_V != G_VOID)
          XPUSHs (text
                  ? sv_2mortal (wcs2sv (text, len))
                  : &PL_sv_undef);

        if (newtext)
          {
            free (text);

            text = sv2wcs (newtext);
            len = wcslen (text);
          }
}

char
rxvt_term::cur_charset ()
	CODE:
        RETVAL = THIS->charsets [THIS->screen.charset];
	OUTPUT:
        RETVAL

void
rxvt_term::scr_xor_rect (int beg_row, int beg_col, int end_row, int end_col, U32 rstyle1 = RS_RVid, U32 rstyle2 = RS_RVid | RS_Uline)

void
rxvt_term::scr_xor_span (int beg_row, int beg_col, int end_row, int end_col, U32 rstyle = RS_RVid)

void
rxvt_term::scr_bell ()

void
rxvt_term::scr_recolor (bool refresh = true);

void
rxvt_term::scr_change_screen (int screen)

void
rxvt_term::scr_add_lines (SV *string)
	CODE:
{
        wchar_t *wstr = sv2wcs (string);
        THIS->scr_add_lines (wstr, wcslen (wstr));
        free (wstr);
}

void
rxvt_term::tt_write_user_input (SV *octets)
        INIT:
          STRLEN len;
          char *str = SvPVbyte (octets, len);
	C_ARGS:
          str, len

void
rxvt_term::tt_write (SV *octets)
        INIT:
          STRLEN len;
          char *str = SvPVbyte (octets, len);
	C_ARGS:
          str, len

void
rxvt_term::tt_paste (SV *octets)
      INIT:
        STRLEN len;
        char *str = SvPVbyte (octets, len);
      C_ARGS:
        str, len

void
rxvt_term::cmd_parse (SV *octets)
	CODE:
{
	STRLEN len;
        char *str = SvPVbyte (octets, len);

        char *old_cmdbuf_ptr  = THIS->cmdbuf_ptr;
        char *old_cmdbuf_endp = THIS->cmdbuf_endp;

        THIS->cmdbuf_ptr  = str;
        THIS->cmdbuf_endp = str + len;

	rxvt_push_locale (THIS->locale);
        THIS->cmd_parse ();
	rxvt_pop_locale ();

        THIS->cmdbuf_ptr  = old_cmdbuf_ptr;
        THIS->cmdbuf_endp = old_cmdbuf_endp;
}

SV *
rxvt_term::overlay (int x, int y, int w, int h, int rstyle = OVERLAY_RSTYLE, int border = 2)
	CODE:
{
        overlay *o = new overlay (THIS, x, y, w, h, rstyle, border);
        RETVAL = newSVptr ((void *)o, "urxvt::overlay");
        o->self = (HV *)SvRV (RETVAL);
}
	OUTPUT:
        RETVAL

#############################################################################
# Various X Utility Functions
#############################################################################

void
rxvt_term::XListProperties (Window window)
	PPCODE:
{
	int count;
	Atom *props = XListProperties (THIS->dpy, window, &count);

        EXTEND (SP, count);
        while (count--)
          PUSHs (newSVuv ((U32)props [count]));

        XFree (props);
}

void
rxvt_term::XGetWindowProperty (Window window, Atom property)
	PPCODE:
{
        Atom type;
        int format;
        unsigned long nitems;
        unsigned long bytes_after;
        unsigned char *prop;

	XGetWindowProperty (THIS->dpy, window, property,
                            0, 1<<24, 0, AnyPropertyType,
                            &type, &format, &nitems, &bytes_after, &prop);

        if (type != None)
          {
            int elemsize = format == 16 ? sizeof (short)
                         : format == 32 ? sizeof (long)
                         :                1;

            EXTEND (SP, 3);
            PUSHs (newSVuv ((U32)type));
            PUSHs (newSViv (format));
            PUSHs (newSVpvn ((char *)prop, nitems * elemsize));
            XFree (prop);
          }
}

void
rxvt_term::XChangeProperty (Window window, Atom property, Atom type, int format, SV *data)
	CODE:
{
	STRLEN len;
        char *data_ = SvPVbyte (data, len);

        int elemsize = format == 16 ? sizeof (short)
                     : format == 32 ? sizeof (long)
                     :                1;

	XChangeProperty (THIS->dpy, window, property,
                         type, format, PropModeReplace,
                         (unsigned char *)data_, len / elemsize);
}

Atom
XInternAtom (rxvt_term *term, octet_string atom_name, int only_if_exists = FALSE)
	C_ARGS: term->dpy, atom_name, only_if_exists

char *
XGetAtomName (rxvt_term *term, Atom atom)
	C_ARGS: term->dpy, atom
        CLEANUP:
        XFree (RETVAL);

void
XDeleteProperty (rxvt_term *term, Window window, Atom property)
	C_ARGS: term->dpy, window, property

Window
rxvt_term::DefaultRootWindow ()
	CODE:
        RETVAL = THIS->display->root;
        OUTPUT:
        RETVAL

#if 0

Window
XCreateSimpleWindow (rxvt_term *term, Window parent, int x, int y, unsigned int width, unsigned int height)
	C_ARGS: term->dpy, (Window)parent,
                x, y, width, height, 0,
                term->pix_colors_focused[Color_border],
                term->pix_colors_focused[Color_border]

#endif

void
XReparentWindow (rxvt_term *term, Window window, Window parent, int x = 0, int y = 0)
	C_ARGS: term->dpy, window, parent, x, y

void
XMapWindow (rxvt_term *term, Window window)
	C_ARGS: term->dpy, window

void
XUnmapWindow (rxvt_term *term, Window window)
	C_ARGS: term->dpy, window

void
XMoveResizeWindow (rxvt_term *term, Window window, int x, int y, unsigned int width, unsigned int height)
	C_ARGS: term->dpy, window, x, y, width, height

void
rxvt_term::XChangeInput (Window window, U32 add_events, U32 del_events = 0)
	CODE:
{
	XWindowAttributes attr;
        XGetWindowAttributes (THIS->dpy, window, &attr);
        XSelectInput (THIS->dpy, window, attr.your_event_mask | add_events & ~del_events);
}

void
rxvt_term::XTranslateCoordinates (Window src, Window dst, int x, int y)
	PPCODE:
{
        int dx, dy;
        Window child;

        if (XTranslateCoordinates (THIS->dpy, src, dst, x, y, &dx, &dy, &child))
          {
            EXTEND (SP, 3);
            PUSHs (newSViv (dx));
            PUSHs (newSViv (dy));
            PUSHs (newSVuv (child));
          }
}

#############################################################################
# fancy bg bloatstuff (TODO: should be moved up somewhere)

bool
rxvt_term::has_render ()
	CODE:
        RETVAL = THIS->display->flags & DISPLAY_HAS_RENDER;
	OUTPUT:
        RETVAL

void
rxvt_term::background_geometry (bool border = false)
	PPCODE:
	EXTEND (SP, 4);
        PUSHs (sv_2mortal (newSViv (THIS->parent_x + (border ? THIS->window_vt_x : 0))));
        PUSHs (sv_2mortal (newSViv (THIS->parent_y + (border ? THIS->window_vt_y : 0))));
        PUSHs (sv_2mortal (newSViv (border ? THIS->vt_width  : THIS->szHint.width )));
        PUSHs (sv_2mortal (newSViv (border ? THIS->vt_height : THIS->szHint.height)));

#if HAVE_IMG

rxvt_img *
rxvt_term::new_img (SV *format = &PL_sv_undef, int x = 0, int y = 0, int width = 1, int height = 1)
	CODE:
        XRenderPictFormat *f = SvOK (format)
                             ? XRenderFindStandardFormat (THIS->dpy, SvIV (format))
                             : XRenderFindVisualFormat   (THIS->dpy, THIS->visual);
        RETVAL = new rxvt_img (THIS, f, x, y, width, height);
        RETVAL->alloc ();
	OUTPUT:
        RETVAL

#if ENABLE_TRANSPARENCY

rxvt_img *
rxvt_term::new_img_from_root ()
	CODE:
        RETVAL = rxvt_img::new_from_root (THIS);
	OUTPUT:
        RETVAL

#endif

#if HAVE_PIXBUF

rxvt_img *
rxvt_term::new_img_from_file (octet_string filename)
	CODE:
        try
          {
            RETVAL = rxvt_img::new_from_file (THIS, filename);
          }
        catch (const class rxvt_failure_exception &e)
          {
            croak ("new_img_from_file failed");
          }
	OUTPUT:
        RETVAL

#endif

void
rxvt_term::clr_background ()
	CODE:
        delete THIS->bg_img;
        THIS->bg_img = 0;
        THIS->bg_flags = rxvt_term::BG_NEEDS_REFRESH;

void
rxvt_term::set_background (rxvt_img *img, bool border = false)
	CODE:
        delete THIS->bg_img;
        THIS->bg_img = 0;
        THIS->bg_flags = rxvt_term::BG_NEEDS_REFRESH;

        //if (img) // TODO: cannot be false, maybe allow and get rid of clr_background?
          {
            img = img->clone (); // own the img

            if (img->repeat != RepeatNormal) // X11 only supports RepeatNormal as bg pixmap
              img->sub_rect (0, 0,
                             border ? THIS->vt_width  : THIS->szHint.width,
                             border ? THIS->vt_height : THIS->szHint.height)
                 ->replace (img);

            // just in case, should usually be a nop
            img->reify ()
               ->replace (img);

            img->convert_format (XRenderFindVisualFormat (THIS->dpy, THIS->visual), THIS->pix_colors [Color_bg])
               ->replace (img);

            THIS->bg_img = img;

            if (!border)
              THIS->bg_flags |= rxvt_term::BG_IS_TRANSPARENT;
          }

#endif

#############################################################################
# urxvt::overlay
#############################################################################

MODULE = urxvt             PACKAGE = urxvt::overlay

void
overlay::set (int x, int y, SV *text, SV *rend = 0)

void
overlay::show ()

void
overlay::hide ()

void
overlay::DESTROY ()

INCLUDE: $PERL <iom_perl.xs -pe s/IOM_MODULE/urxvt/g,s/IOM_CLASS/urxvt/g |

MODULE = urxvt             PACKAGE = urxvt::pixbuf	PREFIX = gdk_pixbuf_

#if HAVE_PIXBUF

urxvt::pixbuf gdk_pixbuf_new_from_file (SV *klass, octet_string filename)
        C_ARGS: filename, 0

void
DESTROY (urxvt::pixbuf self)
	CODE:
        g_object_unref (self);

#endif

MODULE = urxvt             PACKAGE = urxvt::img

#if HAVE_IMG

# rxvt_img *new (rxvt_screen *screen, XRenderPictFormat *format, int width, int height)
# rxvt_img *rxvt_img (rxvt_screen *screen, XRenderPictFormat *format, int width, int height, Pixmap pixmap);

void
rxvt_img::geometry ()
	PPCODE:
        EXTEND (SP, 4);
        PUSHs (sv_2mortal (newSViv (THIS->x)));
        PUSHs (sv_2mortal (newSViv (THIS->y)));
        PUSHs (sv_2mortal (newSViv (THIS->w)));
        PUSHs (sv_2mortal (newSViv (THIS->h)));

int
rxvt_img::x ()
	ALIAS:
        x = 0
        y = 1
        w = 2
        h = 3
	CODE:
        switch (ix)
          {
            case 0: RETVAL = THIS->x; break;
            case 1: RETVAL = THIS->y; break;
            case 2: RETVAL = THIS->w; break;
            case 3: RETVAL = THIS->h; break;
          }
	OUTPUT:
        RETVAL

Pixmap
rxvt_img::pm ()
	CODE:
        RETVAL = THIS->pm;
	OUTPUT:
        RETVAL

void
rxvt_img::fill (SV *c, int x = 0, int y = 0, int w = THIS->w, int h = THIS->h)
	PROTOTYPE: $;$$$$
	INIT:
	rxvt_screen screen;
	screen.set (THIS->d);
	rgba cc = parse_rgba (c, &screen);
	C_ARGS: cc, x, y, w, h

void
rxvt_img::DESTROY ()
	CODE:
        delete THIS;

void
rxvt_img::add_alpha ()

void
rxvt_img::unshare ()

void
rxvt_img::repeat_mode (render_repeat_mode repeat = 0)
	PPCODE:
        if (items >= 2)
          THIS->repeat_mode (repeat);
        if (GIMME_V != G_VOID)
          XPUSHs (sv_2mortal (newSViv (THIS->repeat)));

void
rxvt_img::move (int dx, int dy)

void
rxvt_img::brightness (rxvt_img::nv r, rxvt_img::nv g, rxvt_img::nv b, rxvt_img::nv a = 1.)

void
rxvt_img::contrast (rxvt_img::nv r, rxvt_img::nv g, rxvt_img::nv b, rxvt_img::nv a = 1.)

void
rxvt_img::draw (rxvt_img *img, int op = PictOpOver, rxvt_img::nv mask = 1.);

rxvt_img *
rxvt_img::clone ()

rxvt_img *
rxvt_img::reify ()

rxvt_img *
rxvt_img::sub_rect (int x, int y, int width, int height)

rxvt_img *
rxvt_img::blur (int rh, int rv)

rxvt_img *
rxvt_img::muladd (rxvt_img::nv mul, rxvt_img::nv add)

rxvt_img *
rxvt_img::transform (rxvt_img::nv p11, rxvt_img::nv p12, rxvt_img::nv p13, rxvt_img::nv p21, rxvt_img::nv p22, rxvt_img::nv p23, rxvt_img::nv p31, rxvt_img::nv p32, rxvt_img::nv p33)
	INIT:
        rxvt_img::nv matrix[3][3] = {
          { p11, p12, p13 },
          { p21, p22, p23 },
          { p31, p32, p33 }
        };
	C_ARGS: matrix

rxvt_img *
rxvt_img::scale (int new_width, int new_height)

rxvt_img *
rxvt_img::rotate (int x, int y, rxvt_img::nv phi)

rxvt_img *
rxvt_img::tint (SV *c)
	INIT:
	rxvt_screen screen;
	screen.set (THIS->d);
	rgba cc = parse_rgba (c, &screen);
	C_ARGS: cc

rxvt_img *
rxvt_img::shade (rxvt_img::nv factor)

rxvt_img *
rxvt_img::filter (octet_string name, SV *params = &PL_sv_undef)
	CODE:
        rxvt_img::nv *vparams = 0;
        int nparams = 0;

        if (SvOK (params))
	  {
            if (!SvROK (params) || SvTYPE (SvRV (params)) != SVt_PVAV)
              croak ("rxvt_img::filter: params must be an array reference with parameter values");

            nparams = av_len ((AV *)SvRV (params)) + 1;
            vparams = (rxvt_img::nv *)malloc (nparams * sizeof (rxvt_img::nv));

            for (int i = 0; i < nparams; ++i)
              vparams [i] = SvNV (*av_fetch ((AV *)SvRV (params), i, 1));
          }

        RETVAL = THIS->filter (name, nparams, vparams);
        free (vparams);
	OUTPUT:
        RETVAL

#endif

