#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#include <linux/videodev.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>

#include <gtk/gtk.h>
#include <gdk/gdkx.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/Xproto.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/extensions/xf86vmode.h>


#include "reminder.h"

#include <string.h>
#include <ctype.h>

#include "tv-freq.h"
#include "fftv.h"
#include "xkey.h"
#include "vidstr.h"


static void set_dpy_visibily_clips();
void disp_channel_name_idx();
static gboolean cb_unmap();
static gboolean key_eve(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
static gboolean VisibilityNotify_event(GtkWidget *widget, GdkEvent *event, gpointer user_data);
static gboolean cb_ConfEve(GtkWidget *widget,GdkEventConfigure *event,
                        gpointer user_data);
#if LIRC
extern int lirc_fd;
#endif
extern int rem_sockfd;
Display *dpy;
static GdkWindow *gdk_root;
static GdkCursor* xcursor;
Window frame_win;


void p_err(char *fmt,...)
{
	va_list args;

	va_start(args, fmt);
	fprintf(stderr,"fftv:");
	vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr,"\n");
	exit(-1);
}


void error(char *fmt,...)
{
	va_list args;

	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
}


void dbg(char *fmt,...)
{
	va_list args;

	va_start(args, fmt);
	vprintf(fmt, args);
	fflush(stdout);
	va_end(args);
}


long current_time()
{
  struct timeval timev;
  struct timezone tz;

  gettimeofday(&timev,&tz);
  return timev.tv_sec;
}


void save_original_dpy_mode();
void init_xk_ff();


void init_X_0();

void init_X_arg(int *argc, char ***argv)
{
   if (!getenv("DISPLAY")) {

     putenv("DISPLAY=:0.0");
#if 0
     sprintf(tt,"XAUTHORITY=%s/.Xauthority", getenv("HOME"));
     putenv(tt);
#endif
   }

#if LIRC
   open_lirc();
#endif
   init_rem_serv();

   if (fftv_mode & FFTV_MODE_RADIO)
     set_fftv_cmd_state("FM-radio");

   init_xk_ff();

   dbg("init_X_arg rem_sockfd: %d\n",rem_sockfd);

   gtk_init (argc, argv);

   init_X_0();

   save_original_dpy_mode();
}


static Colormap colormap;
long foreground, background;
long color_red, color_pink;
long white, grey, black, cyan, blue, brown, gold;
static char *fg_color="yellow",*bg_color="blue";
Window tv_win;
GtkWidget *gtv_win;
GdkWindow *gdk_tv_win;
static Window root;
GC gc, gc_sfont, gc_rec;
static int screen_no;
static int xsockfd;
int win_borderless, owin_borderless;
static int b_pointer_timer;       // should use semaphore
gboolean b_fftv_invisible;
static int full_win;
static int full_win640;

char *geomstr="640x480";

int win_xl,win_yl,win_x,win_y;
static int owin_xl=640,owin_yl=480;
static int owin_x=0,owin_y=0;

struct {
  char *color_name;
  long *color_addr;
} color_arr[] = {
  {"white",&white},
  {"black",&black},
  {"grey",&grey},
  {"red",&color_red},
  {"pink",&color_pink},
  {"cyan",&cyan},
  {"blue",&blue},
  {"brown",&brown},
  {"gold",&gold},
};

int color_arrN=sizeof(color_arr)/sizeof(color_arr[0]);

int pic_adj_menu_mode;

static GdkPixmap *bm_off;
static GdkCursor *ptr_off;

static void free_mouse_pointer()
{
  if (bm_off) {
    gdk_cursor_unref(ptr_off);
    gdk_pixmap_unref(bm_off);
    bm_off=0;
  }
}

static void move_mouse_xy(int x, int y)
{
  gdk_flush();
  dbg("move mouse %d %d  tv_win:%x\n",x,y,tv_win);
  if (!tv_win)
    return;
  XWarpPointer(dpy,None,tv_win,0,0,0,0,x,y);
}

static void init_hide_mouse_pointer()
{
  static unsigned char bm_null[] = { 0,0,0,0, 0,0,0,0 };
  GdkColor gblack = {0, 0, 0, 0};

  free_mouse_pointer();

  bm_off = gdk_bitmap_create_from_data(NULL,bm_null, 8,8);
  ptr_off = gdk_cursor_new_from_pixmap(bm_off, bm_off,&gblack, &gblack,0, 0);
}

static void hide_mouse_pointer()
{
//  dbg("a hide mouse ptr\n");
  b_pointer_timer=0;
  gdk_window_set_cursor(gdk_tv_win, ptr_off);
//  dbg("b hide mouse ptr\n");
}

XFontStruct *afont, *afont_small;

static void create_gc()
{
  XGCValues gcv, gcv_sfont, gcv_rec;
  gcv.foreground = foreground;
  gcv.background = background;
  gcv.graphics_exposures = False;
  gcv.font=afont->fid;
  if (gc) XFreeGC(dpy,gc);
  gc = XCreateGC(dpy,tv_win,GCForeground|GCBackground|GCFont|
  GCGraphicsExposures,&gcv);

  gcv_sfont.foreground = foreground;
  gcv_sfont.background = background;
  gcv_sfont.graphics_exposures = False;
  gcv_sfont.font=afont_small->fid;
  if (gc_sfont) XFreeGC(dpy,gc_sfont);
  gc_sfont = XCreateGC(dpy,tv_win,GCForeground|GCBackground|GCFont|
  GCGraphicsExposures,&gcv_sfont);

  gcv_rec.foreground = foreground;
  gcv_rec.background = background;
  gcv_rec.graphics_exposures = False;
  gcv_rec.font=afont_small->fid;
  if (gc_rec) XFreeGC(dpy,gc_rec);
  gc_rec = XCreateGC(dpy,tv_win,GCForeground|GCBackground|GCFont|
  GCGraphicsExposures,&gcv_rec);

}

void change_win_name(char *str)
{
  gtk_window_set_title (GTK_WINDOW(gtv_win), str);
}

extern CHANLIST *pchan_current;
static void getGlobalWinRect(Window win, int *x0, int *y0, int *x1, int *y1);
#if LIRC
void lirc_callback(gpointer data, gint source, GdkInputCondition condition);
#endif
void reminder_callback(gpointer data, gint source, GdkInputCondition condition);
void feed_xkey(int skey, int state, int chr);

static int mouse_x, mouse_y;

static void mouse_button_callback( GtkWidget *widget,GdkEventButton *event, gpointer data)
{
  guint xkey=0;

  if (!tv_win)
    return;

//  dbg("------ mouse_button_callback\n");
  mouse_x = event->x;
  mouse_y = event->y;

  switch (event->button) {
    case 1:
      xkey=MOUSE_BUTTON1;
      break;
    case 2:
      xkey=MOUSE_BUTTON2;
      break;
    case 3:
      xkey=MOUSE_BUTTON3;
      break;
  }

  feed_xkey(xkey,event->state,0);
}

void mouse_scroll_callback(GtkWidget *widget,GdkEvent *event, gpointer data)
{
  GdkEventScroll *pscroll=&event->scroll;
  guint xkey=0;

  if (pscroll->direction==GDK_SCROLL_UP) {
    xkey = MOUSE_BUTTON4;
  }
  else
  if (pscroll->direction==GDK_SCROLL_DOWN) {
    xkey = MOUSE_BUTTON5;
  }

  feed_xkey(xkey,pscroll->state,0);
}


gint mouse_pointer_hide_callback(gpointer data)
{
//  dbg("---- mouse_pointer_hide_callback\n");
  b_pointer_timer=0;

  if (!(fftv_mode & FFTV_MODE_TV))
    return FALSE;

//  dbg("motion notify cb ..\n");

  hide_mouse_pointer();
  return FALSE;
}


gboolean motion_notify_callback(GtkWidget *widget, GdkEventMotion *event,
                                gpointer user_data)
{
//  dbg("------ motion_notify_callback\n");
  if (!(fftv_mode & FFTV_MODE_TV))
    return TRUE;

  if (!b_pointer_timer) {
//    dbg("motion notify ..\n");
    b_pointer_timer=1;
    gdk_window_set_cursor(gdk_tv_win,xcursor);
    gtk_timeout_add(3000,mouse_pointer_hide_callback,NULL);
  }

  return TRUE;
}

void reset_sreensaver();

gint reset_screensaver_callback(gpointer data)
{
  if ((win_borderless && (fftv_mode & FFTV_MODE_TV))
       || record_status==RECORD_ON)
    reset_sreensaver();

  return TRUE;
}

void ExposeEve0();

GdkFilterReturn root_event_callback(GdkXEvent *gdkxevent,
                                    GdkEvent *event,gpointer data)
{
  XEvent *xeve=gdkxevent;
  if (b_fftv_invisible || !tv_win)
    return TRUE;

  switch (xeve->type) {
#if 0
    case Expose:
    case GraphicsExpose:
      dbg("expose\n");
      break;
#endif
#if 1
    case VisibilityNotify:
      dbg("root visibility\n");
      break;
#endif
    case ConfigureNotify:
//      dbg("root configure\n");
      ExposeEve0();
      break;
#if 0
    case CreateNotify:
      dbg("root CreateNotify\n");
      break;
    case DestroyNotify:
      dbg("root DestroyNotify\n");
      break;
    default:
      dbg("unknown %d\n", xeve->type);
      break;
#endif
  }

  return TRUE;
}


void init_X_0()
{
  dpy = GDK_DISPLAY();
  xsockfd=ConnectionNumber(dpy);
  gdk_root=gdk_get_default_root_window();
  xcursor = gdk_cursor_new(GDK_STAR);
  root=DefaultRootWindow(dpy);
  screen_no = DefaultScreen(dpy);
  init_hide_mouse_pointer();
#if LIRC
  gdk_input_add(lirc_fd,GDK_INPUT_READ, lirc_callback, NULL);
#endif
  gdk_input_add(rem_sockfd,GDK_INPUT_READ, reminder_callback, NULL);
  gtk_timeout_add(60000,reset_screensaver_callback,NULL);
  gdk_window_set_events(gdk_root, GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK |
                     GDK_SUBSTRUCTURE_MASK | GDK_STRUCTURE_MASK);
  gdk_window_add_filter(gdk_root,root_event_callback,NULL);
}


static void getTVWinXY(int *gx, int *gy);
static Window get_frame_win();

static time_t last_tv_preview_time;


gboolean cb_ExposeEve(GtkWidget *widget, GdkEventExpose *event, gpointer user_data);
void create_button_widget();

void create_tv_win()
{
  dbg("create_tv_win\n");

  if (gtv_win) {
    g_signal_handlers_block_by_func(G_OBJECT(gtv_win),G_CALLBACK(do_exit_fftv),NULL);
    gtk_widget_destroy(gtv_win);
    gtv_win=NULL;
    tv_win=(Window)NULL;
    gdk_tv_win=NULL;
    last_tv_preview_time = 0;
  }

  gtv_win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_double_buffered (gtv_win, FALSE);
  gtk_widget_set_app_paintable (gtv_win, TRUE);

  gtk_widget_set_events(gtv_win,GDK_POINTER_MOTION_MASK|GDK_EXPOSURE_MASK|
    GDK_SCROLL_MASK|GDK_VISIBILITY_NOTIFY_MASK);

  gtk_window_set_title (GTK_WINDOW(gtv_win), "fftv");
  dbg("gtk_window_set_title\n");

  if (geomstr) {
    gtk_window_parse_geometry(GTK_WINDOW(gtv_win),geomstr);
    geomstr=NULL;
  }
  else
    gtk_window_set_default_size(GTK_WINDOW(gtv_win),win_xl,win_yl);

  if (win_borderless) {
    gtk_window_set_decorated(GTK_WINDOW(gtv_win),FALSE);
  }

#if 1
  if (win_borderless)
    gtk_window_move(GTK_WINDOW(gtv_win),win_x,win_y);  // need  this for gnome 1.4 sawfish
#endif


  gtk_widget_show_now (gtv_win);

  gdk_tv_win = gtv_win->window;
  tv_win = GDK_WINDOW_XWINDOW(gdk_tv_win);

  create_gc();

  dbg("create_gc\n");

  hide_mouse_pointer();

  dbg("hide_mouse\n");

  if (pchan_current)
    change_win_name(pchan_current->name);

  g_signal_connect (G_OBJECT(gtv_win), "scroll-event",
                      G_CALLBACK (mouse_scroll_callback), NULL);

  getTVWinXY(&win_x,&win_y);
  gtk_window_get_size(GTK_WINDOW(gtv_win),&win_xl,&win_yl);

  frame_win = get_frame_win();

  dbg("---- create_tv %d %d   %d %d  frame:%x\n",win_x,win_y,win_xl,win_yl,frame_win);


  GtkWidget *win_icon=gtk_image_new_from_file(ICON_BASENAME "/fftv.png");
  gtk_window_set_icon(GTK_WINDOW(gtv_win),
     gtk_image_get_pixbuf(GTK_IMAGE(win_icon)));

  create_button_widget();

//  gdk_flush();

  g_signal_connect(G_OBJECT(gtv_win),"destroy", G_CALLBACK(do_exit_fftv), NULL);
  g_signal_connect(G_OBJECT(gtv_win),"configure-event", G_CALLBACK(cb_ConfEve), NULL);
  g_signal_connect(G_OBJECT(gtv_win),"unmap-event", G_CALLBACK(cb_unmap), NULL);
  g_signal_connect(G_OBJECT(gtv_win),"key-press-event", G_CALLBACK(key_eve), NULL);
  g_signal_connect(G_OBJECT(gtv_win),"button-press-event", G_CALLBACK(mouse_button_callback), NULL);
  g_signal_connect(G_OBJECT(gtv_win),"visibility-notify-event",
     G_CALLBACK(VisibilityNotify_event), NULL);
#if 1
  g_signal_connect(G_OBJECT(gtv_win),"expose-event", G_CALLBACK(cb_ExposeEve), NULL);
#endif
  g_signal_connect(G_OBJECT(gtv_win),"motion-notify-event",
     G_CALLBACK(motion_notify_callback), NULL);
}


static void create_colors()
{
  XColor color;
  int i;

  if (!XParseColor(dpy,colormap,fg_color, &color))
    p_err("invalid color %s",fg_color);
  if (!XAllocColor(dpy,colormap,&color))
    p_err("can't allocate color %s, will use private colormap.", fg_color);
  foreground=color.pixel;

  if (!XParseColor(dpy,colormap,bg_color, &color))
    p_err("invalid color %s",bg_color);
  if (!XAllocColor(dpy,colormap,&color))
    p_err("can't allocate color %s, will use private colormap.", bg_color);
  background=color.pixel;

  for(i=0;i<color_arrN;i++) {
    if (!XParseColor(dpy,colormap,color_arr[i].color_name, &color))
      p_err("invalid color %s",color_arr[i].color_name);
    if (!XAllocColor(dpy,colormap,&color))
      p_err("can't allocate color %s ", color_arr[i].color_name);
    *color_arr[i].color_addr=color.pixel;
  }

}

void show_tv_win()
{
  if (!gtv_win)
    create_tv_win();
  gtk_widget_show(gtv_win);
}

static char *FontName="-adobe-courier-bold-r-normal--40-0-0-0-m-0-iso8859-9";
static char *sFontName="12x24";
int fwidth,fheight;
int sfwidth,sfheight;


void putStr(char *s, int x, int y)
{
   XSetForeground(dpy,gc_sfont,foreground);
   XDrawImageString(dpy,tv_win,gc_sfont,x*sfwidth,(y+1)*sfheight,s,strlen(s));
   XFlush(dpy);
}


static void load_font()
{

  if (afont) XFreeFont(dpy, afont);
  if (!(afont=XLoadQueryFont(dpy, FontName)))
    p_err("Cannot load ascii font %s", FontName);
  fwidth=afont->max_bounds.width;
  fheight=afont->ascent + afont->descent;

  if (afont_small) XFreeFont(dpy, afont_small);
  if (!(afont_small=XLoadQueryFont(dpy, sFontName)))
    p_err("Cannot load ascii font %s", sFontName);
  sfwidth=afont_small->max_bounds.width;
  sfheight=afont_small->ascent + afont_small->descent;

}

void show_radio_win();

void init_X_win(int argc, char **argv)
{
  owin_borderless=win_borderless;
  colormap = DefaultColormap(dpy,screen_no);

  load_font();

  create_colors();

  if (fftv_mode & FFTV_MODE_TV)
    create_tv_win();
  else
    show_radio_win();
}


static void getTVWinXY(int *gx, int *gy)
{
  gdk_window_get_origin(gdk_tv_win,&win_x,&win_y);
}


static void getGlobalWinRect(Window win, int *x0, int *y0, int *x1, int *y1)
{
  Window ow;
  XWindowAttributes watt;

  XGetWindowAttributes(dpy,win,&watt);

  XTranslateCoordinates(dpy,win,root,0,0,x0,y0,&ow);

  *x1=*x0 + watt.width;
  *y1=*y0 + watt.height;
}

void fftv_invisible()
{
#if 0
  if (record_status==RECORD_ON)
    return;
#endif
  tv_pic_off();
  b_fftv_invisible=1;
}

#define USE_GRAB (1)

static void set_dpy_visibily_clips()
{
  Window r_root,r_parent;
  Window *child;
  int childn;
  int tvx0,tvy0,tvx1,tvy1,i;

  if (!tv_win)
    return;

  gdk_flush();

  clear_clips();

//  dbg("--- set_dpy_visibily_clips\n");
  frame_win = get_frame_win();
  getGlobalWinRect(tv_win,&tvx0,&tvy0,&tvx1,&tvy1);
  win_x=tvx0; win_y=tvy0;
//  dbg("tv: %d %d %d %d\n",tvx0,tvy0,tvx1,tvy1);

#if USE_GRAB
  XGrabServer(dpy);
#endif

  XQueryTree(dpy,root,&r_root,&r_parent,&child,&childn);

  for(i=0;i<childn && child[i]!=frame_win;i++);

  for(i++;i<childn;i++) {
    Window win=child[i];
    int x0,y0,x1,y1;
    XWindowAttributes watt;
    int cx0,cy0,cx1,cy1,cxl,cyl;
    int cx,cy;
    struct video_clip *pclip;

    XGetWindowAttributes(dpy, win, &watt);

    if (!(watt.map_state & IsViewable))
      continue;
    getGlobalWinRect(win,&x0,&y0,&x1,&y1);

    if (x0>tvx1 || y0>tvy1 || x1<tvx0 || y1<tvy0)
      continue;

    cx0=MAX(x0,tvx0);
    cy0=MAX(y0,tvy0);
    cx1=MIN(x1,tvx1);
    cy1=MIN(y1,tvy1);

    cxl=cx1 - cx0;
    if (cxl > max_vxl)
      cxl=max_vxl;
    cyl=cy1 - cy0;
    if (cyl > max_vyl)
      cyl=max_vyl;

    cx=cx0 - tvx0;
    cy=cy0 - tvy0;

    if (cx>=max_vxl || cy>=max_vyl)
      continue;

    pclip=incr_clips();
    pclip->x=cx; pclip->y=cy;
    pclip->width=cxl; pclip->height=cyl;
    pclip->next=NULL;
#if 0
    dbg("-- x0:%d x1:%d   tx0:%d tx1:%d\n",x0,x1,tvx0,tvx1);
    dbg("clip  %d %d %d %d\n",pclip->x, pclip->y,pclip->width,pclip->height);
#endif
  }

#if USE_GRAB
  XUngrabServer(dpy);
#endif

  XFree(child);
}


int Row=4,Col=4;
int ch_per_page=16;
int page_chan_idx=0;
int pre_cx, pre_cy;
int pre_vxl, pre_vyl;
static int *preview_arr;
static int preview_arrN;
extern int *full_chan_arr, full_chan_arrN;

void set_channel_idx(int idx);

static void pre_cxy_idx()
{
  int x,y;
  int preidx=page_chan_idx + pre_cy * Col + pre_cx;

  x=pre_cx*pre_vxl;
  y=(Row-1-pre_cy)*pre_vyl;
  if (preidx<0)
    preidx=0;
  if (preidx >= preview_arrN)
    preidx %= preview_arrN;
  int rno=preview_arr[preidx];

  set_channel_idx(rno2chidx(rno));
  local_vwin(x,y,pre_vxl,pre_vyl);
  usleep(110000);
  tv_audio_on();
}

static void draw_pre_tv_ch_rno(int rno, int x, int y)
{
   char s[16];
   int i,j;

   sprintf(s,"%d",rno);

   XSetForeground(dpy,gc_sfont,background);

#define SHADOW (2)

   for(i=0;i<SHADOW;i++)
     for(j=0;j<SHADOW;j++)
       XDrawString(dpy,tv_win,gc_sfont,x+i,y+sfheight+j,s,strlen(s));

   XSetForeground(dpy,gc_sfont,foreground);
   XDrawString(dpy,tv_win,gc_sfont,x,y+sfheight,s,strlen(s));
   XFlush(dpy);
}

static void update_pre_win_size()
{
  pre_vxl=win_xl/Col; pre_vyl=win_yl/Row;
}


static void draw_tv_preview()
{
  int ix,iy;
  int pidx;

  if (preview_arr==full_chan_arr)
    change_win_name("Channels Preview ");
  else
    change_win_name("Channels Selection History");

  update_pre_win_size();
  tv_audio_off();

  pidx=page_chan_idx;

  for(iy=Row-1;iy>=0;iy--) {
    int y=iy*pre_vyl;

    for(ix=0;ix<Col;ix++) {
      int x=ix*pre_vxl;
      int x0=x+win_x,y0=y+win_y;
      int x1=x0+pre_vxl-1,y1=y0+pre_vyl-1;
      int i;

      for(i=0;i<clipsN;i++) {
        int cx0=win_x+clips[i].x,cy0=win_y+clips[i].y;
        int cx1=cx0 + clips[i].width -1;
        int cy1=cy0 + clips[i].height -1;

        if (x0>=cx0 && y0>=cy0 && x1<=cx1 && y1<=cy1)
          break;
      }

      if (i<clipsN) {
//        dbg("skip %d %d\n",ix,iy);
        continue;
      }

      if (pidx>=preview_arrN)
        pidx = preview_arrN - 1;

      int rno=preview_arr[pidx];
      int chidx=rno2chidx(rno);

      set_channel_pch_0(&chanlist[chidx]);

      local_vwin(x,y,pre_vxl,pre_vyl);

      usleep(110000);
      tv_pic_off();

      draw_pre_tv_ch_rno(rno,x,y);

      pidx=(pidx+1)%chanlistN;
    }
  }
#if 0
  pre_cx=pre_cy=0;
#endif
  pre_cxy_idx();
}


static void before_preview_switch()
{
  int x,y,preidx,rno;

  x=pre_cx*pre_vxl; y=(Row-1-pre_cy)*pre_vyl;
  preidx=page_chan_idx + pre_cy * Col + pre_cx;
  if (preidx >= preview_arrN)
    preidx = preview_arrN - 1;
  rno=preview_arr[preidx];

  tv_audio_off();

  usleep(110000);
  tv_pic_off();

  draw_pre_tv_ch_rno(rno,x,y);
}

void fullscreen_toggle();

void preview_select_channel()
{
   int preidx;

   set_fftv_cmd_state("Normal");
   preidx=page_chan_idx + pre_cy * Col + pre_cx;
   if (preidx >= preview_arrN)
     preidx = preview_arrN - 1;

   set_chan_idx(rno2chidx(preview_arr[preidx]));
   set_vwin();
   tv_audio_on();
   tv_preview_mode=0;
   if (full_win)
     fullscreen_toggle();

   change_win_name(pchan_current->name);
   disp_channel_name_idx();
}

void preview_cursor_left()
{
  before_preview_switch();

  pre_cx--;
  if (pre_cx<0)
    pre_cx=Col-1;
  pre_cxy_idx();
}

void preview_cursor_right()
{
  before_preview_switch();

  pre_cx=(pre_cx+1)%Row;
  pre_cxy_idx();
}

void preview_cursor_up()
{
  before_preview_switch();
  pre_cy=(pre_cy+1)%Row;
  pre_cxy_idx();
}

void preview_cursor_down()
{
  before_preview_switch();
  pre_cy--;
  if (pre_cy<0)
    pre_cy=Row-1;
  pre_cxy_idx();
}


void PreviewSelMouseXY()
{
  int tpre_cx=mouse_x / pre_vxl;
  int tpre_cy=Row - 1 - (mouse_y / pre_vyl);

  if (tpre_cx==pre_cx && tpre_cy==pre_cy) {
    preview_select_channel();
    return;
  }

  before_preview_switch();

  pre_cx=tpre_cx;
  pre_cy=tpre_cy;
  pre_cxy_idx();
}

void setChannelFreq_rno(int rno);

void prev_channel(),next_channel();
void return_last_channel();
void scan_channel();

static char remkeys[4];
static int remkeysN;
int record_status=0;

void soundcard_mute_toggle();
void soundcard_raise_vol();
void soundcard_lower_vol();

void save_channel_last();
void hide_tv_list_window(),hide_tv_ui_window();

void preview_page_up()
{
  if (!(fftv_mode & FFTV_MODE_TV))
     return;

  hide_tv_list_window();
  hide_tv_ui_window();

  set_fftv_cmd_state("Preview");
  if (!tv_preview_mode) {
    tv_preview_mode=1;
    save_channel_last();
    preview_arr=full_chan_arr;
    preview_arrN=full_chan_arrN;
  }
  else
    page_chan_idx+=ch_per_page;

  if (page_chan_idx>=preview_arrN)
    page_chan_idx=0;
  draw_tv_preview();
}

void preview_page_down()
{
   if (!(fftv_mode & FFTV_MODE_TV))
     return;

  hide_tv_list_window();
  hide_tv_ui_window();

   set_fftv_cmd_state("Preview");
   if (!tv_preview_mode) {
     tv_preview_mode=1;
     save_channel_last();
     preview_arr=full_chan_arr;
     preview_arrN=full_chan_arrN;
   }
   else
     page_chan_idx-=ch_per_page;

   if (page_chan_idx<0)
     page_chan_idx=preview_arrN-ch_per_page;
   draw_tv_preview();
}

extern int channel_stack[], channel_stackN;
void preview_channel_stack()
{
  preview_arr=channel_stack;
  preview_arrN=channel_stackN;
  pre_cx=pre_cy=0;
  draw_tv_preview();
}


static void remote_control_channel(char *remkeys, int *remkeysN)
{
  int rno;

  rno=atoi(remkeys);
  *remkeysN=0;
  tv_preview_mode=0;
  setChannelFreq_rno(rno);
  if (fftv_mode & FFTV_MODE_TV) {
    set_fftv_cmd_state("Normal");
    if (full_win)
      fullscreen_toggle();
  }
  else
  if (fftv_mode & FFTV_MODE_RADIO)
    set_fftv_cmd_state("FM-radio");
}

void scan_FM();

#define K100 '.'

#define REM_CLEAR_SEC (5)
static int feed_digit(char achar)
{
   char stname[32];
   int stnameN;

   if (remkeysN==0 && achar==K100) {
     remkeys[remkeysN++]=achar;
     strcpy(stname,"1--");
     stnameN=3;
     stname[stnameN]=0;
     draw_channel_name(stname,REM_CLEAR_SEC);
     return 1;
   } else
   if (achar>='0' && achar<='9') {
     remkeys[remkeysN++]=achar;
     remkeys[remkeysN]=0;
     if (remkeysN==3 && remkeys[0]==K100) {
       remkeys[0]='1';
       remote_control_channel(remkeys,&remkeysN);
     } else
     if (remkeys[0]!=K100) {
       if (remkeysN==2) {
         remote_control_channel(remkeys,&remkeysN);
       } else {
         stname[0]=remkeys[0];
         stname[1]='-';
         stnameN=2;
         tv_preview_mode=0;
         stname[stnameN]=0;
         draw_channel_name(stname,REM_CLEAR_SEC);
       }
     } else {
       if (remkeysN==2) {
         stname[0]='1';
         stname[1]=remkeys[1];
         stname[2]='-';
         stnameN=3;
         stname[stnameN]=0;
         tv_preview_mode=0;
         draw_channel_name(stname,REM_CLEAR_SEC);
       }
     }

     return 1;
   }

   return 0;
}

void feed_digit_int(int val)
{
  if (val<10)
    feed_digit('0'+val);
  else
  if (val==100)
    feed_digit(K100);
}


void clear_remkeys()
{
  pch_str->xl=0;
  remkeysN=0;
}

void toggle_channel_monitor();
extern gboolean b_ch_monitor;
void save_current_channel_name();
void disp_rec_status();
void disp_rec_output_status();
static void refresh_covering_window();

void start_recording()
{
  if (record_status == RECORD_ON) {
    if (fftv_mode & FFTV_MODE_TV)
      disp_rec_output_status();
    return;
  }

  set_record_status(RECORD_ON);

  if (fftv_mode & FFTV_MODE_TV) {
    if (b_ch_monitor)
      toggle_channel_monitor();

    set_record_status(RECORD_ON);

    set_fftv_cmd_state("Record");
    disp_rec_status();
  } else {
    set_record_status(RECORD_ON);

    set_fftv_cmd_state("Record-Radio");
    dbg(".... radio record\n");
  }

  save_current_channel_name();
  gtk_main_quit();
}

void pause_recording()
{
  disp_rec_output_status();

  if (record_status != RECORD_ON)
    return;

  if (fftv_mode & FFTV_MODE_TV) {
    set_fftv_cmd_state("Pause");
    set_record_status(RECORD_PAUSE);
    disp_rec_status();
  } else {
    set_fftv_cmd_state("Pause-Radio");
    set_record_status(RECORD_PAUSE);
  }
}

void stop_recording()
{
  if (record_status == RECORD_OFF) {
    return;
  }

  if (fftv_mode & FFTV_MODE_TV) {
    set_fftv_cmd_state("Normal");
    set_record_status(RECORD_STOP);
    disp_rec_status();
  } else {
    set_fftv_cmd_state("FM-radio");
    set_record_status(RECORD_STOP);
  }

  gtk_main_quit();
}


static gboolean key_eve(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
    gchar chr=0;
//    dbg("--- key_eve\n");
    if (event->string)
      chr=event->string[0];

    feed_xkey(event->keyval,event->state,chr);

    return TRUE;
}


#define SKIP_PERIOD (3)

static gboolean cb_ConfEve(GtkWidget *widget,GdkEventConfigure *event,
                     gpointer user_data)
{
#if 0
  dbg("--- ConfEve %x %x\n",gtv_win,tv_win);
#endif
  tv_pic_off();

  if (b_fftv_invisible)
    return FALSE;

  if (!tv_win)
    return FALSE;

  set_dpy_visibily_clips();

  gtk_window_get_size(GTK_WINDOW(gtv_win),&win_xl,&win_yl);
  getTVWinXY(&win_x,&win_y);

#if 0
  dbg("Configure xl:%d yl:%d  x:%d y:%d\n",win_xl,win_yl,win_x,win_y);
#endif
  if (tv_preview_mode) {
    update_pre_win_size();

    if (current_time() - last_tv_preview_time > SKIP_PERIOD) {
      last_tv_preview_time=current_time();
      draw_tv_preview();
    }
  }
  else {
    set_vwin();
  }

  return FALSE;
}

static gboolean cb_unmap()
{
//   dbg(".... unmap event\n");
   fftv_invisible();
   return FALSE;
}


#define STR640 "\"640x480\""
static XF86VidModeModeLine *disp_mode()
{
  char buf[128];
  static char mod_str[16];
  FILE *fp;
  static XF86VidModeModeLine mod;
  int dotclock;

  int modarrN;
  XF86VidModeModeInfo **modarr;
  XF86VidModeGetAllModeLines(dpy,screen_no,&modarrN, &modarr);

  int i;
  for(i=0; i<modarrN; i++) {
    XF86VidModeModeInfo *pmod = modarr[i];

    dbg("eeeeeee %d] xl: %d  %d\n",i,pmod->hdisplay, pmod->vdisplay);
  }

  XF86VidModeGetModeLine(dpy, screen_no,&dotclock,&mod);
//  dbg("xl: %d  %d\n", mod.hdisplay, mod.vdisplay);
//  sprintf(mod_str, "%dx%d",mod.hdisplay, mod.vdisplay);
  return mod_str;
}

static char mod_original[16];

void save_original_dpy_mode()
{
  strcpy(mod_original,disp_mode());
}

void restore_original_dpy_mode()
{
  int i;

  for(i=0;i<5;i++) {
    if (!strcmp(disp_mode(),mod_original))
      break;
    system("xvidtune -next");
  }
}


void close_v4l();
void free_chanlist();
extern GtkWidget *tv_ui_window;

void do_exit_fftv()
{
  dbg("......... do_exit_fftv ...\n");

  save_current_channel_name();

  tv_pic_off();
  tv_audio_off();
  close_v4l();
  usleep(100000);
  restore_original_dpy_mode();

  dbg("......... do_exit_fftv b ...\n");
  free_chanlist();

  dbg("program will exit\n");
#if 0
  gtk_widget_destroy(gtv_win);
  gtk_widget_destroy(tv_ui_window);
#endif
  exit(0);
}

#if 0
static void reset_tv_win_state()
{
  dbg("zzzzzzzzzzzzzzzzzzz resize %d %d\n",win_xl,win_yl);
  gtk_window_set_decorated(GTK_WINDOW(gtv_win),win_borderless ? FALSE : TRUE);
  gtk_window_resize(GTK_WINDOW(gtv_win),win_xl,win_yl);
  gtk_window_move(GTK_WINDOW(gtv_win),win_x,win_y);
  gdk_flush();
  gtk_widget_set_app_paintable (gtv_win, FALSE);
  gtk_widget_set_double_buffered (gtv_win, TRUE);
  gdk_flush();
  gtk_widget_set_double_buffered (gtv_win, FALSE);
  gtk_widget_set_app_paintable (gtv_win, TRUE);
}
#endif

void fullscreen_toggle()
{
  int mxl = DisplayWidth(dpy,screen_no);
  int myl = DisplayHeight(dpy,screen_no);

  if (full_win640 && !full_win) {
    return;
  }

  tv_pic_off();

  if (!full_win) {
    owin_xl = win_xl;
    owin_yl = win_yl;
    owin_x = win_x;
    owin_y = win_y;
    win_xl = mxl;
    win_yl = myl;
    win_borderless=1;
    win_x=win_y=0;
#if 0
    reset_tv_win_state();
#else
    create_tv_win();
#endif
    full_win=1;
    move_mouse_xy(0,0);
  } else {
    win_xl = owin_xl;
    win_yl = owin_yl;
    win_x = owin_x;
    win_y = owin_y;
//    dbg("---------- %d %d\n",owin_xl,owin_yl);
    win_borderless=owin_borderless;
#if 0
    reset_tv_win_state();
#else
    create_tv_win();
#endif
    full_win=0;
  }

  update_pre_win_size();
}


void center_mouse_pointer()
{
  move_mouse_xy(win_xl-1,win_yl-1);
  move_mouse_xy(0,0);
  move_mouse_xy(win_xl/2,win_yl/2);
}


void fullscreen_toggle640()
{
  int mxl = 640;
  int myl = 480;
  int i;
  static char mod_last[16];

  tv_pic_off();

  if (!full_win640) {
    owin_xl = win_xl;
    owin_yl = win_yl;
    owin_x = win_x;
    owin_y = win_y;
    win_xl = mxl;
    win_yl = myl;
    win_borderless=1;
    win_x=win_y=0;
#if 0
    reset_tv_win_state();
#else
    create_tv_win();
#endif
    full_win640=1;
    strcpy(mod_last,disp_mode());
    for(i=0;i<5;i++) {
      if (!strcmp(disp_mode(),STR640))
        break;
      system("xvidtune -next");
    }
  } else {
    win_xl = owin_xl;
    win_yl = owin_yl;
    win_x = owin_x;
    win_y = owin_y;
//    win_borderless=owin_borderless;
    win_borderless=0;
#if 0
    reset_tv_win_state();
#else
    create_tv_win();
#endif
    full_win640=0;
    for(i=0;i<5;i++) {
      if (!strcmp(disp_mode(),mod_last))
        break;
      system("xvidtune -next");
    }
  }

  center_mouse_pointer();
}


int b_fftv_invisible;

void ExposeEve0()
{
  b_fftv_invisible=0;
#if 0
  dbg("--- ExposeEve0 %d %d\n",win_x,win_y);
#endif
  tv_pic_off();

  getTVWinXY(&win_x,&win_y);

  set_dpy_visibily_clips();

  if (tv_preview_mode) {
    if (current_time() - last_tv_preview_time > SKIP_PERIOD)
    {
      last_tv_preview_time=current_time();
      draw_tv_preview();
    } else {
      update_pre_win_size();
    }
  }
  else
    set_vwin();

  return;
}


gboolean cb_ExposeEve(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
{
  ExposeEve0();
#if 1
  refresh_covering_window();
#endif
//  dbg("cb_ExposeEve ...\n");
  return FALSE;
}

static gboolean update_win(Window win, int tvx0, int tvy0, int tvx1,int tvy1)
{
    int x0,y0,x1,y1;
    XWindowAttributes watt;
    int cx0,cy0,cx1,cy1,cxl,cyl;
    int cx,cy;
    XEvent xeve;
    XExposeEvent *pxexpo= &xeve.xexpose;
#if 0
    if (win == tv_win || frame_win)
      return FALSE;
#endif
//    dbg("proc win %x\n",win);
    XGetWindowAttributes(dpy, win, &watt);

    if (!(watt.map_state & IsViewable))
      return FALSE;
    getGlobalWinRect(win,&x0,&y0,&x1,&y1);

    if (x0>tvx1 || y0>tvy1 || x1<tvx0 || y1<tvy0)
      return FALSE;

    cx0=MAX(x0,tvx0);
    cy0=MAX(y0,tvy0);
    cx1=MIN(x1,tvx1);
    cy1=MIN(y1,tvy1);

    cxl=cx1 - cx0 + 1;
    if (cxl > max_vxl)
      cxl=max_vxl;
    cyl=cy1 - cy0 + 1;
    if (cyl > max_vyl)
      cyl=max_vyl;

    cx=cx0 - tvx0;
    cy=cy0 - tvy0;

    if (cx>=max_vxl || cy>=max_vyl)
      return FALSE;

    pxexpo->type = Expose;
    pxexpo->width = cxl;
    pxexpo->height = cy1;
    pxexpo->window = win;
    pxexpo->x = cx0 - x0;
    pxexpo->y = cy0 - y0;
    pxexpo->send_event = True;
#if 1
    XSendEvent(dpy,win,False,0, &xeve);
#endif
//    dbg("send %d %d   %d %d\n",xexpo.x,xexpo.y,xexpo.width,xexpo.height);
    return TRUE;
}


void refreshTree(Window inwin, int tvx0, int tvy0, int tvx1,int tvy1, int depth)
{
  Window r_root,r_parent;
  int i;
  Window *child;
  int childn;

  if (!update_win(inwin,tvx0,tvy0,tvx1,tvy1))
    return;

#if 1
  XQueryTree(dpy,inwin,&r_root,&r_parent,&child,&childn);

//  dbg("%d %d %d %d depth:%d\n",tvx0,tvy0,tvx1,tvy1,depth);

  // will cause deadlock for mozilla if depth is not limited
  if (depth > 3)
    return;

  for(i=0;i<childn;i++) {
    Window win=child[i];

    if (win==inwin || win==tv_win)
      continue;
    refreshTree(win,tvx0,tvy0,tvx1,tvy1, depth+1);
  }

  XFree(child);
#endif
}


static Window get_frame_win()
{
  Window r_root,r_parent;
  Window framewin;
  Window *child;
  int childn;

  r_parent=tv_win;
  do {
    framewin=r_parent;
    XQueryTree(dpy,framewin,&r_root,&r_parent,&child,&childn);
    XFree(child);
  } while (r_parent!=r_root);

  return framewin;
}


static void refresh_covering_window()
{
  Window r_root,r_parent;
  Window *child;
  int childn;
  int tvx0,tvy0,tvx1,tvy1,i;

  // find the frame win that encloses tv_win
#if USE_GRAB
  XGrabServer(dpy);
#endif

  XQueryTree(dpy,root,&r_root,&r_parent,&child,&childn);

  getGlobalWinRect(tv_win,&tvx0,&tvy0,&tvx1,&tvy1);
  win_x=tvx0; win_y=tvy0;

  for(i=0;i<childn && child[i]!=frame_win;i++);

  for(i++;i<childn;i++) {
    Window win=child[i];

    refreshTree(win,tvx0, tvy0, tvx1,tvy1, 0);
  }

#if USE_GRAB
  XUngrabServer(dpy);
#endif

  XFree(child);
}

#define DBGXEVE (0)

static gboolean VisibilityNotify_event(GtkWidget *widget, GdkEvent *event,
                                       gpointer user_data)
{
     if (!tv_win)
       return TRUE;

     tv_pic_off();
     GdkEventVisibility *peve=&event->visibility;
#if DBGXEVE
      dbg("VisibilityNotify: %d ",peve->state);
#endif
      if(peve->state == GDK_VISIBILITY_FULLY_OBSCURED) {
#if DBGXEVE
        dbg("FO\n");
#endif
        fftv_invisible();
      } else
      if(peve->state == GDK_VISIBILITY_UNOBSCURED) {
#if DBGXEVE
        dbg("U\n");
#endif
        clear_clips();
        ExposeEve0();
      } else {
#if DBGXEVE
        dbg("PO\n");
#endif
        ExposeEve0();
#if 1
        refresh_covering_window();
#endif
      }

      return TRUE;
}

static long last_reset_sreensaver_time;

void reset_sreensaver()
{
   dbg(".. reset screensaver ..\n");
   if (current_time() - last_reset_sreensaver_time < 60)
     return;

   last_reset_sreensaver_time = current_time();
   KeyCode kc_shift_l = XKeysymToKeycode(dpy, XK_Shift_L);
   XTestFakeKeyEvent(dpy, kc_shift_l, True, CurrentTime);
   XTestFakeKeyEvent(dpy, kc_shift_l, False, CurrentTime);
   XSync(dpy, False);
}

#if LIRC
void lirc_callback(gpointer data, gint source, GdkInputCondition condition)
{
  reset_sreensaver();

  lirc_fetch();
}
#endif

void X_event()
{
  reset_sreensaver();
  gtk_main ();

  exit(0);
}

void update_time_frame();

void wait_for_recording()
{
  do {
    gtk_main();
  } while (record_status==RECORD_PAUSE || record_status==RECORD_OFF);

  dbg("record_status:%d\n",record_status);

  update_time_frame();
}


void hide_tv_win()
{
  tv_pic_off();
  gtk_widget_hide(gtv_win);
  hide_tv_list_window();
  hide_tv_ui_window();
}

void process_gtk_events()
{
  while (gtk_events_pending ())
     gtk_main_iteration ();
}

void show_tv_ui_window(), show_tv_list_window();

void show_tv_ui_list_window()
{
  show_tv_ui_window();
  show_tv_list_window();
}
