/*
 * Openmotif user interface
 *
 *   (c) 2000-2002 Gerd Knorr <kraxel@bytesex.org>
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <sys/ioctl.h>

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/XmStrDefs.h>
#include <Xm/Primitive.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/ToggleB.h>
#include <Xm/CascadeB.h>
#include <Xm/Separator.h>
#include <Xm/Scale.h>
#include <Xm/Protocols.h>
#include <Xm/Display.h>
#include <Xm/Text.h>
#include <Xm/FileSB.h>
#include <Xm/ComboBox.h>
#include <Xm/ScrolledW.h>
#include <Xm/MessageB.h>
#include <Xm/Frame.h>
#include <Xm/SelectioB.h>
#include <Xm/TransferP.h>
#include <Xm/DragIcon.h>
#include <X11/extensions/XShm.h>

#include "grab-ng.h"
#include "channel.h"
#include "commands.h"
#include "frequencies.h"
#include "capture.h"
#include "wmhooks.h"
#include "atoms.h"
#include "x11.h"
#include "xt.h"
#include "xv.h"
#include "man.h"
#include "RegEdit.h"
#include "icons.h"
#include "sound.h"
#include "complete.h"
#include "writefile.h"
#include "list.h"
#include "vbi-data.h"
#include "vbi-x11.h"
#include "blit.h"

/*----------------------------------------------------------------------*/

int jpeg_quality, mjpeg_quality, debug;

/*----------------------------------------------------------------------*/

static void PopupAction(Widget, XEvent*, String*, Cardinal*);
static void DebugAction(Widget, XEvent*, String*, Cardinal*);
static void IpcAction(Widget, XEvent*, String*, Cardinal*);
static void ontop_ac(Widget, XEvent*, String*, Cardinal*);
static void chan_makebutton(struct CHANNEL *channel);
static void channel_menu(void);

#ifdef HAVE_ZVBI
static void chscan_cb(Widget widget, XtPointer clientdata,
		      XtPointer call_data);
#endif
static void pref_manage_cb(Widget widget, XtPointer clientdata,
			   XtPointer call_data);
static void add_cmd_callback(Widget widget, String callback,char *command,
			     const char *arg1, const char *arg2);

static XtActionsRec actionTable[] = {
    { "CloseMain",   CloseMainAction   },
    { "Command",     CommandAction     },
    { "Popup",       PopupAction       },
    { "Debug",       DebugAction       },
    { "Remote",      RemoteAction      },
    { "Zap",         ZapAction         },
    { "Scan",        ScanAction        },
    { "man",         man_action        },
    { "Ratio",       RatioAction       },
    { "Launch",      LaunchAction      },
#ifdef HAVE_ZVBI
    { "Vtx",         VtxAction         },
#endif
    { "Complete",    CompleteAction    },
    { "Ipc",         IpcAction         },
    { "Filter",      FilterAction      },
    { "StayOnTop",   ontop_ac          },
    { "Event",       EventAction       },
};

static String fallback_ressources[] = {
#include "MoTV.h"
    NULL
};

XtIntervalId audio_timer;

static Widget st_menu1,st_menu2;
static Widget control_shell,str_shell,levels_shell,levels_toggle;
static Widget launch_menu,opt_menu,cap_menu,freq_menu;
static Widget chan_viewport,chan_box;
static Widget st_freq,st_chan,st_name,st_key;
static Widget scale_shell,filter_shell;
static Widget w_full;
static Widget b_ontop;
#ifdef HAVE_ZVBI
static struct vbi_window *vtx;
#endif

/* properties */
static Widget prop_dlg,prop_name,prop_key,prop_channel,prop_button;
static Widget prop_group;

/* preferences */
static Widget pref_dlg,pref_fs_toggle,pref_fs_menu,pref_fs_option;
static Widget pref_osd,pref_ntsc,pref_partial,pref_quality;
static Widget pref_mix_toggle,pref_mix1_menu,pref_mix1_option;
static Widget pref_mix2_menu,pref_mix2_option;

/* streamer */
static Widget driver_menu, driver_option;
static Widget audio_menu, audio_option;
static Widget video_menu, video_option;
static Widget m_rate,m_fps,m_fvideo,m_status;
static Widget m_faudio,m_faudioL,m_faudioB;

static struct ng_writer *movie_driver;
static unsigned int i_movie_driver;
static unsigned int movie_audio;
static unsigned int movie_video;
static XtWorkProcId rec_work_id;

static struct MY_TOPLEVELS {
    char        *name;
    Widget      *shell;
    int         mapped;
} my_toplevels [] = {
    { "control",   &control_shell },
    { "streamer",  &str_shell     },
    { "scale",     &scale_shell   },
    { "filter",    &filter_shell  },
    { "levels",    &levels_shell  },
};
#define TOPLEVELS (sizeof(my_toplevels)/sizeof(struct MY_TOPLEVELS))

struct motif_attribute {
    struct motif_attribute  *next;
    struct ng_attribute     *attr;
    Widget                  widget;
};
static struct motif_attribute *motif_attrs;

struct filter_attribute {
    struct filter_attribute *next;
    struct ng_filter        *filter;
    struct ng_attribute     *attr;
    int                     value;
    Widget                  widget;
};
static struct filter_attribute *filter_attrs;

/*----------------------------------------------------------------------*/
/* debug/test code                                                      */

#if 0
static void
print_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    char *msg = (char*) clientdata;
    fprintf(stderr,"debug: %s\n",msg);
}

static void
toggle_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmToggleButtonCallbackStruct *tb = call_data;

    if (tb->reason != XmCR_VALUE_CHANGED)
	return;
    fprintf(stderr,"toggle: [%s] set=%s\n",
	    clientdata ? (char*)clientdata : "???",
	    tb->set ? "on" : "off");
}
#endif

void
DebugAction(Widget widget, XEvent *event,
	  String *params, Cardinal *num_params)
{
    fprintf(stderr,"debug: foo\n");
}

/*----------------------------------------------------------------------*/

void toolkit_set_label(Widget widget, char *str)
{
    XmString xmstr;

    xmstr = XmStringGenerate(str, NULL, XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(widget,XmNlabelString,xmstr,NULL);
    XmStringFree(xmstr);
}

static void delete_children(Widget widget)
{
    WidgetList children,list;
    Cardinal nchildren;
    unsigned int i;

    XtVaGetValues(widget,XtNchildren,&children,
		  XtNnumChildren,&nchildren,NULL);
    if (0 == nchildren)
	return;
    list = malloc(sizeof(Widget*)*nchildren);
    memcpy(list,children,sizeof(Widget*)*nchildren);
    for (i = 0; i < nchildren; i++)
	XtDestroyWidget(list[i]);
    free(list);
}

static void
watch_audio(XtPointer data, XtIntervalId *id)
{
    if (-1 != cur_sender)
	change_audio(channels[cur_sender]->audio);
    audio_timer = 0;
}

static void
new_channel(void)
{
    char line[1024];

    set_property(cur_freq,
		 (cur_channel == -1) ? NULL : chanlist[cur_channel].name,
		 (cur_sender == -1)  ? NULL : channels[cur_sender]->name);

    sprintf(line,"%d.%02d MHz",cur_freq / 16, (cur_freq % 16) * 100 / 16);
    toolkit_set_label(st_freq,line);
    toolkit_set_label(st_chan, (cur_channel != -1) ?
		      chanlist[cur_channel].name : "");
    toolkit_set_label(st_name, (cur_sender != -1) ?
		      channels[cur_sender]->name : "");
    toolkit_set_label(st_key,(cur_sender != -1 &&
			      NULL != channels[cur_sender]->key) ?
	channels[cur_sender]->key : "");

    if (zap_timer) {
	XtRemoveTimeOut(zap_timer);
	zap_timer = 0;
    }
    if (scan_timer) {
	XtRemoveTimeOut(scan_timer);
	scan_timer = 0;
    }
    if (audio_timer) {
	XtRemoveTimeOut(audio_timer);
	audio_timer = 0;
    }
    audio_timer = XtAppAddTimeOut(app_context, 5000, watch_audio, NULL);
}

static void
do_ontop(Boolean state)
{
    unsigned int i;

    if (!wm_stay_on_top)
	return;

    stay_on_top = state;
    wm_stay_on_top(dpy,XtWindow(app_shell),stay_on_top);
    wm_stay_on_top(dpy,XtWindow(on_shell),stay_on_top);
    for (i = 0; i < TOPLEVELS; i++)
	wm_stay_on_top(dpy,XtWindow(*(my_toplevels[i].shell)),
		       (stay_on_top == -1) ? 0 : stay_on_top);
    XmToggleButtonSetState(b_ontop,state,False);
}

static void
ontop_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmToggleButtonCallbackStruct *tb = call_data;

    if (tb->reason != XmCR_VALUE_CHANGED)
	return;
    do_ontop(tb->set);
}

static void
ontop_ac(Widget widget, XEvent *event, String *params, Cardinal *num_params)
{
    do_ontop(stay_on_top ? False : True);
}

/*----------------------------------------------------------------------*/

static void
resize_event(Widget widget, XtPointer client_data, XEvent *event, Boolean *d)
{
    static int width = 0, height = 0, first = 1;
    
    switch(event->type) {
    case ConfigureNotify:
	if (first) {
	    video_gd_init(tv,args.gl);
	    first = 0;
	}
	if (width  != event->xconfigure.width ||
	    height != event->xconfigure.height) {
	    width  = event->xconfigure.width;
	    height = event->xconfigure.height;
	    video_gd_configure(width, height);
	    XClearWindow(XtDisplay(tv),XtWindow(tv));
	}
	break;
    }
}

static void
PopupAction(Widget widget, XEvent *event,
	    String *params, Cardinal *num_params)
{
    unsigned int i;

    /* which window we are talking about ? */
    if (*num_params > 0) {
	for (i = 0; i < TOPLEVELS; i++) {
	    if (0 == strcasecmp(my_toplevels[i].name,params[0]))
		break;
	}
	if (i == TOPLEVELS) {
	    fprintf(stderr,"PopupAction: oops: shell not found (name=%s)\n",
		    params[0]);
	    return;
	}
    } else {
	for (i = 0; i < TOPLEVELS; i++) {
	    if (*(my_toplevels[i].shell) == widget)
		break;
	}
	if (i == TOPLEVELS) {
	    fprintf(stderr,"PopupAction: oops: shell not found (%p:%s)\n",
		    widget,XtName(widget));
	    return;
	}
    }

    /* popup/down window */
    if (!my_toplevels[i].mapped) {
	XtPopup(*(my_toplevels[i].shell), XtGrabNone);
	if (wm_stay_on_top && stay_on_top > 0)
	    wm_stay_on_top(dpy,XtWindow(*(my_toplevels[i].shell)),1);
	my_toplevels[i].mapped = 1;
    } else {
	XtPopdown(*(my_toplevels[i].shell));
	my_toplevels[i].mapped = 0;
    }
}

static void
popup_eh(Widget widget, XtPointer clientdata, XEvent *event, Boolean *cont)
{
    Widget popup = clientdata;

    XmMenuPosition(popup,(XButtonPressedEvent*)event);
    XtManageChild(popup);
}

static void
popupdown_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    int i = 0;
    PopupAction(clientdata, NULL, NULL, &i);
}

static void
destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XtDestroyWidget(clientdata);
}

static void
free_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    free(clientdata);
}

static void
about_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Widget msgbox;
    
    msgbox = XmCreateInformationDialog(app_shell,"about_box",NULL,0);
    XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
    XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
    XtManageChild(msgbox);
}

static void
action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    char *calls, *action, *argv[8];
    int argc;

    calls = strdup(clientdata);
    action = strtok(calls,"(),");
    for (argc = 0; NULL != (argv[argc] = strtok(NULL,"(),")); argc++)
	/* nothing */;
    XtCallActionProc(widget,action,NULL,argv,argc);
    free(calls);
}

/*--- videotext ----------------------------------------------------------*/

#if TT
static void
rend_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmDisplayCallbackStruct *arg = call_data;
    XmRendition rend;
    Colormap cmap;
    XColor color,dummy;
    char fg[16],bg[16];
    Arg args[4];
    int n = 0;

    if (arg->reason != XmCR_NO_RENDITION)
	return;
    if (2 != sscanf(arg->tag,"%[a-z]_on_%[a-z]",fg,bg))
	return;

    XtVaGetValues(app_shell, XmNcolormap, &cmap, NULL);
    if (0 != strcmp(fg,"def"))
	if (XAllocNamedColor(dpy, cmap, fg, &color, &dummy))
	    XtSetArg(args[n],XmNrenditionForeground,color.pixel), n++;
    if (0 != strcmp(bg,"def"))
	if (XAllocNamedColor(dpy, cmap, bg, &color, &dummy))
	    XtSetArg(args[n],XmNrenditionBackground,color.pixel), n++;
    if (debug)
	fprintf(stderr,"rend_cb: %s: %d %s/%s\n",arg->tag,n,fg,bg);
    rend = XmRenditionCreate(app_shell, arg->tag, args, n);
    arg->render_table = XmRenderTableAddRenditions
	(arg->render_table,&rend,1,XmMERGE_NEW);
}
#endif

static void create_vtx(void)
{
    Widget shell,label;
    
    shell = XtVaCreateWidget("vtx",transientShellWidgetClass,
			     app_shell,
			     XtNoverrideRedirect,True,
			     XtNvisual,vinfo.visual,
			     XtNcolormap,colormap,
			     XtNdepth,vinfo.depth,
			     NULL);
    label = XtVaCreateManagedWidget("label", xmLabelWidgetClass, shell,
				    NULL);
#if TT
    XtAddCallback(XmGetXmDisplay(dpy),XmNnoRenditionCallback,rend_cb,NULL);
#endif
#ifdef HAVE_ZVBI
    vtx = vbi_render_init(shell,label,NULL);
#endif
}

#if TT
static void
display_vtx(struct TEXTELEM *tt)
{
    static int first = 1;
    Dimension x,y,w,h,sw,sh;
    char tag[32];
    int i,lastline;
    XmString str,elem;

    if (NULL == tt) {
	XtPopdown(vtx_shell);
	return;
    }

    /* build xmstring */
    str = NULL;
    lastline = tt[0].line;
    for (i = 0; tt[i].len > 0; i++) {
	tt[i].str[tt[i].len] = 0;
	sprintf(tag,"%s_on_%s",
		tt[i].fg ? tt[i].fg : "def",
		tt[i].bg ? tt[i].bg : "def");
	if (NULL != str && tt[i].line != lastline) {
	    lastline = tt[i].line;
	    str = XmStringConcatAndFree(str,XmStringSeparatorCreate());
	}
	elem = XmStringGenerate(tt[i].str, NULL, XmMULTIBYTE_TEXT, tag);
	if (NULL != str)
	    str = XmStringConcatAndFree(str,elem);
	else
	    str = elem;
    }
    XtVaSetValues(vtx_label,XmNlabelString,str,NULL);
    XmStringFree(str);

    /* show popup */
    XtVaGetValues(app_shell,XtNx,&x,XtNy,&y,XtNwidth,&w,XtNheight,&h,NULL);
    XtVaGetValues(vtx_shell,XtNwidth,&sw,XtNheight,&sh,NULL);
    XtVaSetValues(vtx_shell,XtNx,x+(w-sw)/2,XtNy,y+h-10-sh,NULL);
    XtPopup(vtx_shell, XtGrabNone);
    if (wm_stay_on_top && stay_on_top > 0)
	wm_stay_on_top(dpy,XtWindow(vtx_shell),1);

    if (first) {
	first = 0;
	XDefineCursor(dpy, XtWindow(vtx_shell), left_ptr);
	XDefineCursor(dpy, XtWindow(vtx_label), left_ptr);
    }
}
#endif

#ifdef HAVE_ZVBI
static void
display_subtitle(struct vbi_page *pg, struct vbi_rect *rect)
{
    static int first = 1;
    static Pixmap pix;
    Dimension x,y,w,h,sw,sh;

    if (NULL == pg) {
	XtPopdown(vtx->shell);
	return;
    }

    if (pix)
	XFreePixmap(dpy,pix);
    pix = vbi_export_pixmap(vtx,pg,rect);
    XtVaSetValues(vtx->tt,XmNlabelPixmap,pix,XmNlabelType,XmPIXMAP,NULL);
    
    XtVaGetValues(app_shell,XtNx,&x,XtNy,&y,XtNwidth,&w,XtNheight,&h,NULL);
    XtVaGetValues(vtx->shell,XtNwidth,&sw,XtNheight,&sh,NULL);
    XtVaSetValues(vtx->shell,XtNx,x+(w-sw)/2,XtNy,y+h-10-sh,NULL);
    XtPopup(vtx->shell, XtGrabNone);
    if (wm_stay_on_top && stay_on_top > 0)
	wm_stay_on_top(dpy,XtWindow(vtx->shell),1);

    if (first) {
	first = 0;
	XDefineCursor(dpy, XtWindow(vtx->shell), left_ptr);
	XDefineCursor(dpy, XtWindow(vtx->tt), left_ptr);
    }
}
#endif

/*----------------------------------------------------------------------*/

static void
new_attr(struct ng_attribute *attr, int val)
{
    struct motif_attribute *a;
    WidgetList children;
    Cardinal nchildren;
    unsigned int i;

    for (a = motif_attrs; NULL != a; a = a->next) {
	if (a->attr->id == attr->id)
	    break;
    }
    if (NULL == a)
	return;

    switch (attr->type) {
    case ATTR_TYPE_CHOICE:
	XtVaGetValues(a->widget,XtNchildren,&children,
		      XtNnumChildren,&nchildren,NULL);
	for (i = 0; i < nchildren; i++) {
	    XmToggleButtonSetState(children[i],a->attr->choices[i].nr == val,
				   False);
	}
	break;
    case ATTR_TYPE_BOOL:
	XmToggleButtonSetState(a->widget,val,False);
	break;
    case ATTR_TYPE_INTEGER:
	XmScaleSetValue(a->widget,val);
	break;
    }
    return;
}

static void
new_volume(void)
{
    struct ng_attribute *attr;

    attr = ng_attr_byid(attrs,ATTR_ID_VOLUME);
    if (NULL != attr)
	new_attr(attr,cur_attrs[ATTR_ID_VOLUME]);
    attr = ng_attr_byid(attrs,ATTR_ID_MUTE);
    if (NULL != attr)
	new_attr(attr,cur_attrs[ATTR_ID_MUTE]);
}

static void
new_freqtab(void)
{
    WidgetList children;
    Cardinal nchildren;
    XmStringTable tab;
    int i;

    /* update menu */
    XtVaGetValues(freq_menu,XtNchildren,&children,
		  XtNnumChildren,&nchildren,NULL);
    for (i = 0; chanlist_names[i].str != NULL; i++)
	XmToggleButtonSetState(children[i],chanlist_names[i].nr == chantab,
			       False);

    /* update property window */
    tab = malloc(chancount*sizeof(*tab));
    for (i = 0; i < chancount; i++)
	tab[i] = XmStringGenerate(chanlist[i].name,
				  NULL, XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(prop_channel,
		  XmNitemCount,chancount,
		  XmNitems,tab,
		  XmNselectedItem,tab[0],
		  NULL);
    for (i = 0; i < chancount; i++)
	XmStringFree(tab[i]);
    free(tab);
}

/*----------------------------------------------------------------------*/

static void
chan_key_eh(Widget widget, XtPointer client_data, XEvent *event, Boolean *cont)
{
    XKeyEvent *ke = (XKeyEvent*)event;
    KeySym sym;
    char *key;
    char line[64];

    sym = XKeycodeToKeysym(dpy,ke->keycode,0);
    if (NoSymbol == sym) {
	fprintf(stderr,"can't translate keycode %d\n",ke->keycode);
	return;
    }
    key = XKeysymToString(sym);

    line[0] = '\0';
    if (ke->state & ShiftMask)   strcpy(line,"Shift+");
    if (ke->state & ControlMask) strcpy(line,"Ctrl+");
    strcat(line,key);
    XmTextSetString(prop_key,line);
}

static void
chan_add_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmString str;
    int i;

    /* init stuff */
    prop_button = NULL;
    XmTextSetString(prop_name,"");
    XmTextSetString(prop_key,"");
    XmTextSetString(prop_group,"main");
    i = (-1 == cur_channel) ? 0 : cur_channel;
    str = XmStringGenerate(chanlist[i].name, NULL, XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(prop_channel,XmNselectedItem,str,NULL);
    XmStringFree(str);

    XtManageChild(prop_dlg);
}

static void
chan_edit_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmString str;
    int i;

    /* find channel */
    prop_button = clientdata;
    for (i = 0; i < count; i++)
	if (prop_button == channels[i]->button)
	    break;
    if (i == count)
	return;

    /* init stuff */
    XmTextSetString(prop_name,channels[i]->name);
    XmTextSetString(prop_key,channels[i]->key);
    XmTextSetString(prop_group,channels[i]->group);
    i = (-1 == channels[i]->channel) ? 0 : channels[i]->channel;
    str = XmStringGenerate(chanlist[i].name, NULL, XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(prop_channel,XmNselectedItem,str,NULL);
    XmStringFree(str);

    XtManageChild(prop_dlg);
}

static void
chan_apply_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    char *name, *key, *cname, *group;
    struct CHANNEL *c;
    XmString str;
    int i,channel;
    Widget msgbox;

    /* find channel */
    i = count;
    if (NULL != prop_button)
	for (i = 0; i < count; i++)
	    if (prop_button == channels[i]->button)
		break;

    name  = XmTextGetString(prop_name);
    key   = XmTextGetString(prop_key);
    group = XmTextGetString(prop_group);
    if (0 == strlen(group))
	group = "main";
	
    XtVaGetValues(prop_channel,XmNselectedItem,&str,NULL);
    cname = XmStringUnparse(str,NULL,XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
			    NULL,0,0);
    channel = lookup_channel(cname);

    if (0 == strlen(name)) {
	msgbox = XmCreateErrorDialog(prop_dlg,"no_name",NULL,0);
	XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
	XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
	XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
	XtManageChild(msgbox);
	return;
    }
    
    if (i == count) {
	/* add */
	c = add_channel(name);
	if (strlen(key) > 0)
	    c->key = strdup(key);
	c->cname   = strdup(cname);
	c->group   = strdup(group);
	c->channel = channel;
    } else {
	/* update */
	c = channels[i];
	free(c->name);
	c->name = strdup(name);
	if (c->key) {
	    free(c->key);
	    c->key = NULL;
	}
	if (0 != strlen(key)) 
	    c->key = strdup(key);
	c->cname   = strdup(cname);
	c->group   = strdup(group);
	c->channel = channel;
	XtRemoveAllCallbacks(c->button, XmNactivateCallback);
	add_cmd_callback(c->button, XmNactivateCallback,
			 "setstation", c->name, NULL);
	toolkit_set_label(c->button,c->name);
    }
#if 0
    c->input   = cur_attrs[ATTR_ID_INPUT];
    c->fine    = cur_fine;
#endif
    channel_menu();
}

static void
chan_save_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    save_config();
}

static void
chan_tune_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmComboBoxCallbackStruct *cb = call_data;
    char *line;

    line = XmStringUnparse(cb->item_or_text,NULL,
			   XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
			   NULL,0,0);
    do_va_cmd(3,"setchannel",line);
}

static void
create_prop(void)
{
    Widget label,rowcol;

    prop_dlg = XmCreatePromptDialog(control_shell,"prop",NULL,0);
    XmdRegisterEditres(XtParent(prop_dlg));
    XtUnmanageChild(XmSelectionBoxGetChild(prop_dlg,XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(prop_dlg,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(prop_dlg,XmDIALOG_TEXT));

    rowcol = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass, prop_dlg,
				     NULL);
    label = XtVaCreateManagedWidget("nameL", xmLabelWidgetClass, rowcol,
				    NULL);
    prop_name = XtVaCreateManagedWidget("name", xmTextWidgetClass, rowcol,
					NULL);

    label = XtVaCreateManagedWidget("keyL", xmLabelWidgetClass, rowcol,
				    NULL);
    prop_key = XtVaCreateManagedWidget("key", xmTextWidgetClass, rowcol,
				       NULL);
    XtAddEventHandler(prop_key, KeyPressMask, False, chan_key_eh, NULL);

    label = XtVaCreateManagedWidget("groupL", xmLabelWidgetClass, rowcol,
				    NULL);
    prop_group = XtVaCreateManagedWidget("group", xmTextWidgetClass, rowcol,
					 NULL);

    label = XtVaCreateManagedWidget("channelL", xmLabelWidgetClass, rowcol,
				    NULL);
    prop_channel = XtVaCreateManagedWidget("channel",xmComboBoxWidgetClass,
					   rowcol,NULL);
    XtAddCallback(prop_channel,XmNselectionCallback, chan_tune_cb, NULL);

    XtAddCallback(prop_dlg,XmNokCallback, chan_apply_cb, NULL);
}

/*----------------------------------------------------------------------*/

static void
filter_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct filter_attribute *f = clientdata;
    Widget w;

    switch (f->attr->type) {
    case ATTR_TYPE_INTEGER:
	XmScaleGetValue(f->widget,&f->value);
	break;
    case ATTR_TYPE_BOOL:
	f->value = XmToggleButtonGetState(f->widget);
	break;
    case ATTR_TYPE_CHOICE:
	w = NULL;
	XtVaGetValues(f->widget,XmNmenuHistory,&w,NULL);
	f->value = ng_attr_getint(f->attr,XtName(w));
	break;
    }
    f->attr->write(f->attr,f->value);
}

static void
filter_add_ctrls(Widget rc, struct ng_filter *filter,
		 struct ng_attribute *attr)
{
    struct filter_attribute *f;
    Widget opt,push;
    XmString str;
    Arg args[2];
    int i;

    f = malloc(sizeof(*f));
    memset(f,0,sizeof(*f));
    f->filter = filter;
    f->attr   = attr;
    f->next   = filter_attrs;
    f->value  = attr->read(attr);
    filter_attrs = f;
    
    str = XmStringGenerate((char*)attr->name, NULL, XmMULTIBYTE_TEXT, NULL);
    switch (attr->type) {
    case ATTR_TYPE_INTEGER:
	f->widget = XtVaCreateManagedWidget("scale",
					    xmScaleWidgetClass,rc,
					    XmNtitleString,str,
					    XmNminimum,attr->min,
					    XmNmaximum,attr->max,
					    XmNdecimalPoints,attr->points,
					    NULL);
	XmScaleSetValue(f->widget,f->value);
	XtAddCallback(f->widget,XmNvalueChangedCallback,filter_cb,f);
	break;
    case ATTR_TYPE_BOOL:
	f->widget = XtVaCreateManagedWidget("bool",
					    xmToggleButtonWidgetClass,rc,
					    XmNlabelString,str,
					    NULL);
	XmToggleButtonSetState(f->widget,f->value,False);
	XtAddCallback(f->widget,XmNvalueChangedCallback,filter_cb,f);
	break;
    case ATTR_TYPE_CHOICE:
	f->widget = XmCreatePulldownMenu(rc,"choiceM",NULL,0);
	XtSetArg(args[0],XmNsubMenuId,f->widget);
	XtSetArg(args[1],XmNlabelString,str);
	opt = XmCreateOptionMenu(rc,"choiceO",args,2);
	XtManageChild(opt);
	for (i = 0; attr->choices[i].str != NULL; i++) {
	    push = XtVaCreateManagedWidget(attr->choices[i].str,
					   xmPushButtonWidgetClass,f->widget,
					   NULL);
	    XtAddCallback(push,XmNactivateCallback,filter_cb,f);
	    if (f->value == attr->choices[i].nr)
		XtVaSetValues(f->widget,XmNmenuHistory,push,NULL);
	}
	break;
    }
    XmStringFree(str);
}

static void
create_filter_prop(void)
{
    Widget rc1,frame,rc2;
    XmString str;
    struct list_head *item;
    struct ng_filter *filter;
    int j;

    filter_shell = XtVaAppCreateShell("filter","MoTV",
				      topLevelShellWidgetClass,
				      dpy,
				      XtNclientLeader,app_shell,
				      XtNvisual,vinfo.visual,
				      XtNcolormap,colormap,
				      XtNdepth,vinfo.depth,
				      XmNdeleteResponse,XmDO_NOTHING,
				      NULL);
    XmdRegisterEditres(filter_shell);
    XmAddWMProtocolCallback(filter_shell,WM_DELETE_WINDOW,
			    popupdown_cb,filter_shell);
    
    rc1 = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass, filter_shell,
				  NULL);

    list_for_each(item,&ng_filters) {
	filter = list_entry(item, struct ng_filter, list);
	if (NULL == filter->attrs)
	    continue;
	str = XmStringGenerate(filter->name, NULL,
			       XmMULTIBYTE_TEXT, NULL);
	frame = XtVaCreateManagedWidget("frame",xmFrameWidgetClass,rc1,NULL);
	XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,
				XmNlabelString,str,
				NULL);
	rc2 = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass,frame,NULL);
	XmStringFree(str);
	for (j = 0; NULL != filter->attrs[j].name; j++)
	    filter_add_ctrls(rc2,filter,&filter->attrs[j]);
    }
}

/*----------------------------------------------------------------------*/

static void
scroll_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct motif_attribute *a = clientdata;
    int ret;
    char val[10];

    XmScaleGetValue(a->widget,&ret);
    sprintf(val,"%d", ret);
    do_va_cmd(3,"setattr",a->attr->name,val);
}


void
add_cmd_callback(Widget widget, String callback,
		 char *command, const char *arg1, const char *arg2)
{
    struct DO_CMD *cmd;

    cmd = malloc(sizeof(*cmd));
    cmd->argc    = 1;
    cmd->argv[0] = command;
    if (arg1) {
	cmd->argc    = 2;
	cmd->argv[1] = (char*)arg1;
    }
    if (arg2) {
	cmd->argc    = 3;
	cmd->argv[2] = (char*)arg2;
    }
    XtAddCallback(widget,callback,command_cb,cmd);
    XtAddCallback(widget,XmNdestroyCallback,free_cb,cmd);
}

static Widget
add_cmd_menuitem(const char *n, int nr, Widget parent, const char *l,
		 char *k, char *a,  int toggle,
		 char *c, const char *arg1, const char *arg2)
{
    char name[16];
    XmString label,accel;
    Widget w;
    WidgetClass class;
    String callback;

    sprintf(name,"%.10s%d",n,nr);
    label = XmStringGenerate((char*)l, NULL, XmMULTIBYTE_TEXT, NULL);
    if (toggle) {
	class    = xmToggleButtonWidgetClass;
	callback = XmNvalueChangedCallback;
    } else {
	class    = xmPushButtonWidgetClass;
	callback = XmNactivateCallback;
    }
    if (k && a) {
	accel = XmStringGenerate(k, NULL, XmMULTIBYTE_TEXT, NULL);
	w = XtVaCreateManagedWidget(name,class,parent,
				    XmNlabelString,label,
				    XmNacceleratorText,accel,
				    XmNaccelerator,a,
				    NULL);
    } else {
	w = XtVaCreateManagedWidget(name,class,parent,
				    XmNlabelString,label,
				    NULL);
    }
    if (toggle)
	XtVaSetValues(w,XmNindicatorType,toggle,NULL);
    if (c)
	add_cmd_callback(w,callback,c,arg1,arg2);
    XmStringFree(label);
    return w;
}

static void
add_attr_option(Widget menu, struct ng_attribute *attr)
{
    int i;
    struct motif_attribute *a;

    a = malloc(sizeof(*a));
    memset(a,0,sizeof(*a));
    a->attr = attr;
    
    switch (attr->type) {
    case ATTR_TYPE_CHOICE:
	a->widget = XmCreatePulldownMenu(menu,(char*)attr->name,NULL,0);
	XtVaCreateManagedWidget(attr->name,xmCascadeButtonWidgetClass,menu,
				XmNsubMenuId,a->widget,NULL);
	for (i = 0; attr->choices[i].str != NULL; i++)
	    add_cmd_menuitem(attr->name, i, a->widget,
			     attr->choices[i].str, NULL, NULL, XmONE_OF_MANY,
			     "setattr",attr->name,attr->choices[i].str);
	break;
    case ATTR_TYPE_BOOL:
	a->widget = XtVaCreateManagedWidget(attr->name,
					    xmToggleButtonWidgetClass,menu,
					    NULL);
	add_cmd_callback(a->widget,XmNvalueChangedCallback,
			 "setattr", attr->name, "toggle");
	break;
    }
    a->next = motif_attrs;
    motif_attrs = a;
}

/* use more columns until the menu fits onto the screen */
static void
menu_cols_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Dimension height,num;
    int i = 8;

    for (;i;i--) {
	XtVaGetValues(widget,
		      XtNheight,&height,
		      XmNnumColumns,&num,
		      NULL);
	if (height < XtScreen(widget)->height - 100)
	    break;
	XtVaSetValues(widget,XmNnumColumns,num+1,NULL);
    }
}

void
channel_menu(void)
{
    struct {
	char *name;
	Widget menu1;
	Widget menu2;
    } *sub = NULL;
    int subs = 0;

    Widget menu1,menu2;
    char ctrl[16],key[32],accel[64];
    int  i,j;

    if (0 == st_menu2) {
	st_menu2 = XmCreatePopupMenu(tv,"stationsM",NULL,0);
	XtAddEventHandler(tv,ButtonPressMask,False,popup_eh,st_menu2);
	XtAddCallback(st_menu2,XmNmapCallback,menu_cols_cb,NULL);
    }

    /* delete entries */
    delete_children(st_menu1);
    delete_children(st_menu2);

    /* rebuild everything */
    for (i = 0; i < count; i++) {
	if (channels[i]->key) {
	    if (2 == sscanf(channels[i]->key,
			    "%15[A-Za-z0-9_]+%31[A-Za-z0-9_]",
			    ctrl,key)) {
		sprintf(accel,"%s<Key>%s",ctrl,key);
	    } else {
		sprintf(accel,"<Key>%s",channels[i]->key);
	    }
	} else {
	    accel[0] = 0;
	}

	menu1 = st_menu1;
	menu2 = st_menu2;
	if (0 != strcmp(channels[i]->group,"main")) {
	    for (j = 0; j < subs; j++)
		if (0 == strcmp(channels[i]->group,sub[j].name))
		    break;
	    if (j == subs) {
		subs++;
		sub = realloc(sub, subs * sizeof(*sub));
		sub[j].name  = channels[i]->group;
		sub[j].menu1 = XmCreatePulldownMenu(st_menu1,
						    channels[i]->group,
						    NULL,0);
		sub[j].menu2 = XmCreatePulldownMenu(st_menu2,
						    channels[i]->group,
						    NULL,0);
		XtVaCreateManagedWidget(channels[i]->group,
					xmCascadeButtonWidgetClass, st_menu1,
					XmNsubMenuId, sub[j].menu1,
					NULL);
		XtVaCreateManagedWidget(channels[i]->group,
					xmCascadeButtonWidgetClass, st_menu2,
					XmNsubMenuId, sub[j].menu2,
					NULL);
	    }
	    menu1 = sub[j].menu1;
	    menu2 = sub[j].menu2;
	}

	add_cmd_menuitem("station", i, menu1,
			 channels[i]->name, channels[i]->key, accel, FALSE,
			 "setstation",channels[i]->name,NULL);
	add_cmd_menuitem("station", i, menu2,
			 channels[i]->name, channels[i]->key, accel, FALSE,
			 "setstation",channels[i]->name,NULL);
	if (NULL == channels[i]->button)
	    chan_makebutton(channels[i]);
    }
    free(sub);
    calc_frequencies();
}

static void
chan_resize_eh(Widget widget, XtPointer client_data, XEvent *event, Boolean *d)
{
    Widget clip;
    Dimension width;

    XtVaGetValues(chan_viewport,XmNclipWindow,&clip,NULL);
    XtVaGetValues(clip,XtNwidth,&width,NULL);
    XtVaSetValues(chan_box,XtNwidth,width,NULL);
}

static void
chan_del_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Widget button = clientdata;
    int i;

    for (i = 0; i < count; i++)
	if (button == channels[i]->button)
	    break;
    if (i == count)
	return;
    XtDestroyWidget(channels[i]->button);
    del_channel(i);
    if (cur_sender == i)
	cur_sender = -1;
    if (cur_sender > i)
	cur_sender--;
    channel_menu();
}

static void
chan_makebutton(struct CHANNEL *channel)
{
    Widget menu,push;

    if (NULL != channel->button)
	return;
    
    channel->button =
	XtVaCreateManagedWidget(channel->name,
				xmPushButtonWidgetClass, chan_box,
				NULL);
    add_cmd_callback(channel->button, XmNactivateCallback,
		     "setstation", channel->name, NULL);
    menu = XmCreatePopupMenu(channel->button,"menu",NULL,0);
    XtAddEventHandler(channel->button,ButtonPressMask,False,popup_eh,menu);
    push = XtVaCreateManagedWidget("del",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,chan_del_cb,channel->button);
    push = XtVaCreateManagedWidget("edit",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,chan_edit_cb,channel->button);
}

static void
create_control(void)
{
    Widget form,menubar,menu,smenu,push,clip,tool,rc,fr;
    char action[256];
    int i;
    
    control_shell = XtVaAppCreateShell("control","MoTV",
				       topLevelShellWidgetClass,
				       dpy,
				       XtNclientLeader,app_shell,
				       XtNvisual,vinfo.visual,
				       XtNcolormap,colormap,
				       XtNdepth,vinfo.depth,
				       XmNdeleteResponse,XmDO_NOTHING,
				       NULL);
    XmdRegisterEditres(control_shell);
    XmAddWMProtocolCallback(control_shell,WM_DELETE_WINDOW,
			    popupdown_cb,control_shell);
    form = XtVaCreateManagedWidget("form", xmFormWidgetClass, control_shell,
				   NULL);

    /* menbar */
    menubar = XmCreateMenuBar(form,"menubar",NULL,0);
    XtManageChild(menubar);

    tool = XtVaCreateManagedWidget("tool",xmRowColumnWidgetClass,form,NULL);

    /* status line */
    rc = XtVaCreateManagedWidget("status", xmRowColumnWidgetClass, form,
				 NULL);
    fr = XtVaCreateManagedWidget("f", xmFrameWidgetClass, rc, NULL);
    st_freq = XtVaCreateManagedWidget("freq", xmLabelWidgetClass, fr, NULL);
    fr = XtVaCreateManagedWidget("f", xmFrameWidgetClass, rc, NULL);
    st_chan = XtVaCreateManagedWidget("chan", xmLabelWidgetClass, fr, NULL);
    fr = XtVaCreateManagedWidget("f", xmFrameWidgetClass, rc, NULL);
    st_name = XtVaCreateManagedWidget("name", xmLabelWidgetClass, fr, NULL);
    fr = XtVaCreateManagedWidget("f", xmFrameWidgetClass, rc, NULL);
    st_key = XtVaCreateManagedWidget("key", xmLabelWidgetClass, fr, NULL);
#if 0
    fr = XtVaCreateManagedWidget("f", xmFrameWidgetClass, rc, NULL);
    st_other = XtVaCreateManagedWidget("other", xmLabelWidgetClass, fr, NULL);
#endif

    /* channel buttons */
    chan_viewport = XmCreateScrolledWindow(form,"view",NULL,0);
    XtManageChild(chan_viewport);
    chan_box = XtVaCreateManagedWidget("box", xmRowColumnWidgetClass,
				       chan_viewport, NULL);
    XtVaGetValues(chan_viewport,XmNclipWindow,&clip,NULL);
    XtAddEventHandler(clip,StructureNotifyMask, True,
		      chan_resize_eh, NULL);
    
    /* menu - file */
    menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
    XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    push = XtVaCreateManagedWidget("rec",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,popupdown_cb,str_shell);
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("quit",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,ExitCB,NULL);

#if 1
    /* menu - edit */
    if (f_drv & CAN_CAPTURE) {
	menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
	XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
				XmNsubMenuId,menu,NULL);
	push = XtVaCreateManagedWidget("copy",xmPushButtonWidgetClass,menu,
				       NULL);
	XtAddCallback(push,XmNactivateCallback,action_cb,"Ipc(clipboard)");
    }
#endif

    /* menu - tv stations */
    st_menu1 = XmCreatePulldownMenu(menubar,"stationsM",NULL,0);
    XtVaCreateManagedWidget("stations",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,st_menu1,NULL);
    XtAddCallback(st_menu1,XmNmapCallback,menu_cols_cb,NULL);
    
    /* menu - tools (name?) */
    menu = XmCreatePulldownMenu(menubar,"toolsM",NULL,0);
    XtVaCreateManagedWidget("tools",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    w_full = XtVaCreateManagedWidget("full",xmToggleButtonWidgetClass,menu,
				     NULL);
    XtAddCallback(w_full,XmNvalueChangedCallback,action_cb,
		  "Command(fullscreen)");
    push = XtVaCreateManagedWidget("ontop",xmToggleButtonWidgetClass,menu,
				   NULL);
    b_ontop = push;
    XtAddCallback(push,XmNvalueChangedCallback,ontop_cb,NULL);
    push = XtVaCreateManagedWidget("levels",xmPushButtonWidgetClass,menu,
				   NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Popup(levels)");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("st_up",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setstation,next)");
    push = XtVaCreateManagedWidget("st_dn",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setstation,prev)");

    /* menu - tools / tuner */
    smenu = XmCreatePulldownMenu(menu,"tuneM",NULL,0);
    XtVaCreateManagedWidget("tune",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,smenu,NULL);
    push = XtVaCreateManagedWidget("ch_up",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setchannel,next)");
    push = XtVaCreateManagedWidget("ch_dn",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setchannel,prev)");
    push = XtVaCreateManagedWidget("fi_up",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setchannel,fine_up)");
    push = XtVaCreateManagedWidget("fi_dn",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setchannel,fine_down)");

    /* menu - tools / capture */
    smenu = XmCreatePulldownMenu(menu,"grabM",NULL,0);
    XtVaCreateManagedWidget("grab",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,smenu,NULL);
    push = XtVaCreateManagedWidget("ppm_f",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(snap,ppm,full)");
    push = XtVaCreateManagedWidget("ppm_w",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(snap,ppm,win)");
    push = XtVaCreateManagedWidget("jpg_f",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(snap,jpeg,full)");
    push = XtVaCreateManagedWidget("jpg_w",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(snap,jpeg,win)");
    
    /* menu - tools / aspect ratio */
    smenu = XmCreatePulldownMenu(menu,"ratioM",NULL,0);
    XtVaCreateManagedWidget("ratio",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,smenu,NULL);
    push = XtVaCreateManagedWidget("r_no",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Ratio(0,0)");
    push = XtVaCreateManagedWidget("r_43",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Ratio(4,3)");

    /* menu - tools / launch */
    launch_menu = XmCreatePulldownMenu(menu,"launchM",NULL,0);
    XtVaCreateManagedWidget("launch",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,launch_menu,NULL);

#ifdef HAVE_ZVBI
    /* menu - tools / subtitles */
    smenu = XmCreatePulldownMenu(menu,"subM",NULL,0);
    XtVaCreateManagedWidget("sub",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,smenu,NULL);
    push = XtVaCreateManagedWidget("s_off",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Vtx(stop)");
    push = XtVaCreateManagedWidget("s_150",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Vtx(start,150)");
    push = XtVaCreateManagedWidget("s_333",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Vtx(start,333)");
    push = XtVaCreateManagedWidget("s_777",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Vtx(start,777)");
    push = XtVaCreateManagedWidget("s_801",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Vtx(start,801)");
    push = XtVaCreateManagedWidget("s_888",xmPushButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Vtx(start,888)");
#endif

    /* menu - internal options */
    opt_menu = menu = XmCreatePulldownMenu(menubar,"optionsM",NULL,0);
    XtVaCreateManagedWidget("options",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    push = XtVaCreateManagedWidget("add",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,chan_add_cb,NULL);
#ifdef HAVE_ZVBI
    push = XtVaCreateManagedWidget("scan",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,chscan_cb,NULL);
#endif
#if 1
    push = XtVaCreateManagedWidget("pref",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,pref_manage_cb,NULL);
#endif
    push = XtVaCreateManagedWidget("save",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,chan_save_cb,NULL);
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);

    cap_menu = XmCreatePulldownMenu(menu,"captureM",NULL,0);
    XtVaCreateManagedWidget("capture",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,cap_menu,NULL);
    push = XtVaCreateManagedWidget("overlay",xmToggleButtonWidgetClass,
				   cap_menu,XmNindicatorType,XmONE_OF_MANY,
				   NULL);
    add_cmd_callback(push,XmNvalueChangedCallback,"capture","overlay",NULL);
    push = XtVaCreateManagedWidget("grabdisplay",xmToggleButtonWidgetClass,
				   cap_menu,XmNindicatorType,XmONE_OF_MANY,
				   NULL);
    add_cmd_callback(push,XmNvalueChangedCallback,"capture","grab",NULL);
    push = XtVaCreateManagedWidget("none",xmToggleButtonWidgetClass,
				   cap_menu,XmNindicatorType,XmONE_OF_MANY,
				   NULL);
    add_cmd_callback(push,XmNvalueChangedCallback,"capture","off",NULL);
    
    freq_menu = XmCreatePulldownMenu(menu,"freqM",NULL,0);
    XtVaCreateManagedWidget("freq",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,freq_menu,NULL);
    for (i = 0; chanlist_names[i].str != NULL; i++) {
	push = XtVaCreateManagedWidget(chanlist_names[i].str,
				       xmToggleButtonWidgetClass,freq_menu,
				       XmNindicatorType,XmONE_OF_MANY,
				       NULL);
	add_cmd_callback(push,XmNvalueChangedCallback,
			 "setfreqtab", chanlist_names[i].str, NULL);
    }
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);

    /* menu - filter */
    if ((f_drv & CAN_CAPTURE)  &&  !list_empty(&ng_filters))  {
	struct list_head *item;
	struct ng_filter *filter;
	
	menu = XmCreatePulldownMenu(menubar,"filterM",NULL,0);
	XtVaCreateManagedWidget("filter",xmCascadeButtonWidgetClass,menubar,
				XmNsubMenuId,menu,NULL);
	push = XtVaCreateManagedWidget("fnone",
				       xmPushButtonWidgetClass,menu,
				       NULL);
	XtAddCallback(push,XmNactivateCallback,action_cb,"Filter()");
	list_for_each(item,&ng_filters) {
	    filter = list_entry(item, struct ng_filter, list);
	    push = XtVaCreateManagedWidget(filter->name,
					   xmPushButtonWidgetClass,menu,
					   NULL);
	    sprintf(action,"Filter(%s)",filter->name);
	    XtAddCallback(push,XmNactivateCallback,action_cb,strdup(action));
	}
	XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
	push = XtVaCreateManagedWidget("fopts",xmPushButtonWidgetClass,menu,
				       NULL);
	XtAddCallback(push,XmNactivateCallback,action_cb,"Popup(filter)");
    }

    /* menu - help */
    menu = XmCreatePulldownMenu(menubar,"helpM",NULL,0);
    push = XtVaCreateManagedWidget("help",xmCascadeButtonWidgetClass,menubar,
				   XmNsubMenuId,menu,NULL);
    XtVaSetValues(menubar,XmNmenuHelpWidget,push,NULL);
    push = XtVaCreateManagedWidget("man",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,man_cb,"motv");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("about",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,about_cb,NULL);

    /* toolbar */
    push = XtVaCreateManagedWidget("prev",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setstation,prev)");
    push = XtVaCreateManagedWidget("next",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(setstation,next)");

    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,tool,NULL);
    push = XtVaCreateManagedWidget("snap",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(snap,jpeg,full)");
    push = XtVaCreateManagedWidget("movie",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,popupdown_cb,str_shell);
    push = XtVaCreateManagedWidget("mute",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "Command(volume,mute,toggle)");

    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,tool,NULL);
    push = XtVaCreateManagedWidget("exit",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,ExitCB,NULL);
}

static void create_attr_widgets(void)
{
    struct ng_attribute *attr;
    Widget push;
    XmString label,accel;
    char str[100],key[32],ctrl[16];
    Arg argv[8];
    Cardinal argc;
    int i;

    /* menu - driver options
       input + norm */
    attr = ng_attr_byid(attrs,ATTR_ID_NORM);
    if (NULL != attr)
	add_attr_option(opt_menu, attr);
    attr = ng_attr_byid(attrs,ATTR_ID_INPUT);
    if (NULL != attr)
	add_attr_option(opt_menu,attr);
    attr = ng_attr_byid(attrs,ATTR_ID_AUDIO_MODE);
    if (NULL != attr)
	add_attr_option(opt_menu,attr);
    for (attr = attrs; attr->name != NULL; attr++) {
	if (attr->id < ATTR_ID_COUNT)
	    continue;
	if (attr->type != ATTR_TYPE_CHOICE)
	    continue;
	add_attr_option(opt_menu,attr);
    }

    /* bools */
    attr = ng_attr_byid(attrs,ATTR_ID_MUTE);
    if (NULL != attr)
	add_attr_option(opt_menu,attr);
    for (attr = attrs; attr->name != NULL; attr++) {
	if (attr->id < ATTR_ID_COUNT)
	    continue;
	if (attr->type != ATTR_TYPE_BOOL)
	    continue;
	add_attr_option(opt_menu,attr);
    }

    /* integer (scales) */
    push = XtVaCreateManagedWidget("scale",xmPushButtonWidgetClass,
				   opt_menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Popup(scale)");

    /* launch menu entries */
    for (i = 0; i < nlaunch; i++) {
	argc  = 0;
	label = NULL;
	accel = NULL;

	label = XmStringGenerate(launch[i].name,NULL,XmMULTIBYTE_TEXT,NULL);
	XtSetArg(argv[argc],XmNlabelString,label); argc++;
	if (NULL != launch[i].key) {
	    accel = XmStringGenerate(launch[i].key,NULL,XmMULTIBYTE_TEXT,NULL);
	    XtSetArg(argv[argc],XmNacceleratorText,accel); argc++;
	    if (2 == sscanf(launch[i].key,"%15[A-Za-z0-9_]+%31[A-Za-z0-9_]",
			    ctrl,key))
		sprintf(str,"%s<Key>%s",ctrl,key);
	    else
		sprintf(str,"<Key>%s",launch[i].key);
	    XtSetArg(argv[argc],XmNaccelerator,str); argc++;
	}
	push = XtCreateManagedWidget(launch[i].name,
				     xmPushButtonWidgetClass,
				     launch_menu,argv,argc);
	if (label)
	    XmStringFree(label);
	if (accel)
	    XmStringFree(accel);

	/* translations */
	strcat(str,": Launch(");
	strcat(str,launch[i].name);
	strcat(str,")");
	XtOverrideTranslations(tv,XtParseTranslationTable(str));

	/* button callback */
	sprintf(str,"Launch(%s)",launch[i].name);
	XtAddCallback(push,XmNactivateCallback,action_cb,
		      strdup(str));
    }
}

static void
create_scale(void)
{
    Widget form,attach;
    struct ng_attribute *attr;
    struct motif_attribute *a;
    int vol = 0;
    XmString str;

    scale_shell = XtVaAppCreateShell("scale","MoTV",
				     topLevelShellWidgetClass,
				     dpy,
				     XtNclientLeader,app_shell,
				     XtNvisual,vinfo.visual,
				     XtNcolormap,colormap,
				     XtNdepth,vinfo.depth,
				     XmNdeleteResponse,XmDO_NOTHING,
				     NULL);
    XmdRegisterEditres(scale_shell);
    XmAddWMProtocolCallback(scale_shell,WM_DELETE_WINDOW,
			    popupdown_cb,scale_shell);
    form = XtVaCreateManagedWidget("form", xmFormWidgetClass, scale_shell,
				   NULL);
    /* scales */
    attach = NULL;
    for (attr = attrs; attr->name != NULL; attr++) {
	if (attr->type != ATTR_TYPE_INTEGER)
	    continue;
	if (attr->id == ATTR_ID_VOLUME) {
	    if (vol)
		continue;
	    vol++;
	}
	a = malloc(sizeof(*a));
	memset(a,0,sizeof(*a));
	a->attr = attr;
	a->next = motif_attrs;
	motif_attrs = a;
	if (a->attr->id < ATTR_ID_COUNT) {
	    a->widget = XtVaCreateManagedWidget(attr->name,
						xmScaleWidgetClass, form,
						XmNtopWidget,attach,
						XmNminimum,attr->min,
						XmNmaximum,attr->max,
						XmNdecimalPoints,attr->points,
						NULL);
	} else {
	    str = XmStringGenerate((char*)attr->name, NULL,
				   XmMULTIBYTE_TEXT, NULL);
	    a->widget = XtVaCreateManagedWidget(attr->name,
						xmScaleWidgetClass, form,
						XmNtopWidget,attach,
						XmNtitleString,str,
						XmNminimum,attr->min,
						XmNmaximum,attr->max,
						XmNdecimalPoints,attr->points,
						NULL);
	    XmStringFree(str);
	}
	XtAddCallback(a->widget,XmNvalueChangedCallback,scroll_cb,a);
	XtAddCallback(a->widget,XmNdragCallback,scroll_cb,a);
	attach = a->widget;
    }
}

/*----------------------------------------------------------------------*/

#if 0
void create_chanwin(void)
{
    Widget form,clip,menu,push;
    
}
#endif

/* gets called before switching away from a channel */
static void
pixit(void)
{
    Pixmap pix;
    struct ng_video_fmt fmt;
    struct ng_video_buf *buf;

    if (cur_sender == -1)
	return;

    /* save picture settings */
    channels[cur_sender]->color    = cur_attrs[ATTR_ID_COLOR];
    channels[cur_sender]->bright   = cur_attrs[ATTR_ID_BRIGHT];
    channels[cur_sender]->hue      = cur_attrs[ATTR_ID_HUE];
    channels[cur_sender]->contrast = cur_attrs[ATTR_ID_CONTRAST];
    channels[cur_sender]->input    = cur_attrs[ATTR_ID_INPUT];
    channels[cur_sender]->norm     = cur_attrs[ATTR_ID_NORM];

    if (0 == pix_width || 0 == pix_height)
	return;

    /* capture mini picture */
    if (!(f_drv & CAN_CAPTURE))
	return;

    video_gd_suspend();
    memset(&fmt,0,sizeof(fmt));
    fmt.fmtid  = x11_dpy_fmtid;
    fmt.width  = pix_width;
    fmt.height = pix_height;
    if (NULL == (buf = ng_grabber_get_image(&fmt)))
	goto done1;
    buf = ng_filter_single(cur_filter,buf);
    if (0 == (pix = x11_create_pixmap(dpy,&vinfo,buf)))
	goto done2;
    x11_label_pixmap(dpy,colormap,pix,buf->fmt.height,
		     channels[cur_sender]->name);
    XtVaSetValues(channels[cur_sender]->button,
		  XmNlabelPixmap,pix,
		  XmNlabelType,XmPIXMAP,
		  NULL);
    if (channels[cur_sender]->pixmap)
	XFreePixmap(dpy,channels[cur_sender]->pixmap);
    channels[cur_sender]->pixmap = pix;
 done2:
    ng_release_video_buf(buf);
 done1:
    video_gd_restart();
}

/*----------------------------------------------------------------------*/

static void
do_capture(int from, int to, int tmp_switch)
{
    static int niced = 0;
    WidgetList children;

    /* off */
    switch (from) {
    case CAPTURE_GRABDISPLAY:
	video_gd_stop();
	XClearArea(XtDisplay(tv), XtWindow(tv), 0,0,0,0, True);
	break;
    case CAPTURE_OVERLAY:
	video_overlay(0);
	break;
    }

    /* on */
    switch (to) {
    case CAPTURE_GRABDISPLAY:
	if (!niced)
	    nice(niced = 10);
	video_gd_start();
	break;
    case CAPTURE_OVERLAY:
	video_overlay(1);
	break;
    }

    /* update menu */
    XtVaGetValues(cap_menu,XtNchildren,&children,NULL);
    XmToggleButtonSetState(children[0],to == CAPTURE_OVERLAY,    False);
    XmToggleButtonSetState(children[1],to == CAPTURE_GRABDISPLAY,False);
    XmToggleButtonSetState(children[2],to == CAPTURE_OFF,        False);
}

static void
do_motif_fullscreen(void)
{
    XmToggleButtonSetState(w_full,!fs,False);
    do_fullscreen();
}

/*----------------------------------------------------------------------*/

struct FILE_DATA {
    Widget filebox;
    Widget text;
    Widget push;
};

static void
file_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmFileSelectionBoxCallbackStruct *cb = call_data;
    struct FILE_DATA *h = clientdata;
    char *line;

    if (cb->reason == XmCR_OK) {
	line = XmStringUnparse(cb->value,NULL,
			       XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
			       NULL,0,0);
	XmTextSetString(h->text,line);
    }
    XtUnmanageChild(h->filebox);
}

static void
file_browse_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct FILE_DATA *h = clientdata;
    Widget help;
    /* XmString str; */

    if (NULL == h->filebox) {
	h->filebox = XmCreateFileSelectionDialog(h->push,"filebox",NULL,0);
	help = XmFileSelectionBoxGetChild(h->filebox,XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(help);
	XtAddCallback(h->filebox,XmNokCallback,file_done_cb,h);
	XtAddCallback(h->filebox,XmNcancelCallback,file_done_cb,h);
    }
#if 0
    str = XmStringGenerate(XmTextGetString(h->text),
			   NULL, XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(h->filebox,XmNdirMask,str,NULL);
    XmStringFree(str);
#endif
    XtManageChild(h->filebox);
}

static void
exec_player_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    char *filename;

    filename = XmTextGetString(m_fvideo);
    exec_player(filename);
}

static void
create_strwin(void)
{
    Widget form,push,rowcol,frame,fbox;
    struct FILE_DATA *h;
    Arg args[2];
    
    str_shell = XtVaAppCreateShell("streamer", "MoTV",
				   topLevelShellWidgetClass,
				   dpy,
				   XtNclientLeader,app_shell,
				   XtNvisual,vinfo.visual,
				   XtNcolormap,colormap,
				   XtNdepth,vinfo.depth,
				   XmNdeleteResponse,XmDO_NOTHING,
				   NULL);
    XmdRegisterEditres(str_shell);
    XmAddWMProtocolCallback(str_shell,WM_DELETE_WINDOW,
			    popupdown_cb,str_shell);
    form = XtVaCreateManagedWidget("form", xmFormWidgetClass, str_shell,
				   NULL);

    /* driver */
    frame = XtVaCreateManagedWidget("driverF", xmFrameWidgetClass, form, NULL);
    XtVaCreateManagedWidget("driverL",xmLabelWidgetClass,frame,NULL);
    driver_menu = XmCreatePulldownMenu(form,"driverM",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,driver_menu);
    driver_option = XmCreateOptionMenu(frame,"driver",args,1);
    XtManageChild(driver_option);

    /* video format + frame rate */
    frame = XtVaCreateManagedWidget("videoF", xmFrameWidgetClass, form, NULL);
    XtVaCreateManagedWidget("videoL",xmLabelWidgetClass,frame,NULL);
    rowcol = XtVaCreateManagedWidget("videoB",xmRowColumnWidgetClass,
				     frame,NULL);
    video_menu = XmCreatePulldownMenu(rowcol,"videoM",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,video_menu);
    video_option = XmCreateOptionMenu(rowcol,"video",args,1);
    XtManageChild(video_option);
    XtVaCreateManagedWidget("fpsL",xmLabelWidgetClass,rowcol,NULL);
    m_fps = XtVaCreateManagedWidget("fps",xmComboBoxWidgetClass,rowcol,NULL);
    
    /* audio format + sample rate */
    frame = XtVaCreateManagedWidget("audioF", xmFrameWidgetClass, form, NULL);
    XtVaCreateManagedWidget("audioL",xmLabelWidgetClass,frame,NULL);
    rowcol = XtVaCreateManagedWidget("audioB",xmRowColumnWidgetClass,
				     frame,NULL);
    audio_menu = XmCreatePulldownMenu(rowcol,"audioM",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,audio_menu);
    audio_option = XmCreateOptionMenu(rowcol,"audio",args,1);
    XtManageChild(audio_option);
    XtVaCreateManagedWidget("rateL",xmLabelWidgetClass,rowcol,NULL);
    m_rate = XtVaCreateManagedWidget("rate",xmComboBoxWidgetClass,rowcol,NULL);

    /* filenames */
    frame = XtVaCreateManagedWidget("fileF", xmFrameWidgetClass, form, NULL);
    XtVaCreateManagedWidget("fileL",xmLabelWidgetClass,frame,NULL);
    fbox = XtVaCreateManagedWidget("fbox",xmRowColumnWidgetClass,
				   frame,NULL);

    rowcol = XtVaCreateManagedWidget("fvideoB",xmRowColumnWidgetClass,
				     fbox,NULL);
    XtVaCreateManagedWidget("fvideoL",xmLabelWidgetClass,rowcol,NULL);
    h = malloc(sizeof(*h));
    memset(h,0,sizeof(*h));
    h->text = XtVaCreateManagedWidget("fvideo",xmTextWidgetClass,
				      rowcol,NULL);
    m_fvideo = h->text;
    h->push = XtVaCreateManagedWidget("files",xmPushButtonWidgetClass,rowcol,
				      NULL);
    XtAddCallback(h->push,XmNactivateCallback,file_browse_cb,h);

    rowcol = XtVaCreateManagedWidget("faudioB",xmRowColumnWidgetClass,
				     fbox,NULL);
    m_faudioL = XtVaCreateManagedWidget("faudioL",xmLabelWidgetClass,rowcol,
					NULL);
    h = malloc(sizeof(*h));
    memset(h,0,sizeof(*h));
    h->text = XtVaCreateManagedWidget("faudio",xmTextWidgetClass,rowcol,
				      NULL);
    m_faudio = h->text;
    h->push = XtVaCreateManagedWidget("files",xmPushButtonWidgetClass,rowcol,
				      NULL);
    m_faudioB = h->push;
    XtAddCallback(h->push,XmNactivateCallback,file_browse_cb,h);

    /* seperator, buttons */
    m_status = XtVaCreateManagedWidget("status",xmLabelWidgetClass,form,NULL);
    rowcol = XtVaCreateManagedWidget("buttons",xmRowColumnWidgetClass,form,
				   NULL);
    push = XtVaCreateManagedWidget("rec", xmPushButtonWidgetClass, rowcol,
				   NULL);
    add_cmd_callback(push,XmNactivateCallback, "movie","start",NULL);
    push = XtVaCreateManagedWidget("stop", xmPushButtonWidgetClass, rowcol,
				   NULL);
    add_cmd_callback(push,XmNactivateCallback, "movie","stop",NULL);
    push = XtVaCreateManagedWidget("play", xmPushButtonWidgetClass, rowcol,
				   NULL);
    XtAddCallback(push,XmNactivateCallback,exec_player_cb,NULL);
    push = XtVaCreateManagedWidget("cancel", xmPushButtonWidgetClass, rowcol,
				   NULL);
    XtAddCallback(push,XmNactivateCallback, popupdown_cb, str_shell);
}

static void
update_movie_menus(void)
{
    struct list_head *item;
    struct ng_writer *writer;
    static int first = 1;
    Widget push;
    XmString str;
    Boolean sensitive;
    int i;
    
    /* drivers  */
    if (first) {
	first = 0;
	i = 0;
	list_for_each(item,&ng_writers) {
	    writer = list_entry(item, struct ng_writer, list);
	    str = XmStringGenerate((char*)writer->desc,
				   NULL, XmMULTIBYTE_TEXT, NULL);
	    push = XtVaCreateManagedWidget(writer->name,
					   xmPushButtonWidgetClass,driver_menu,
					   XmNlabelString,str,
					   NULL);
	    XmStringFree(str);
	    add_cmd_callback(push,XmNactivateCallback,
			     "movie","driver",writer->name);
	    if (NULL == movie_driver ||
		(NULL != mov_driver && 0 == strcasecmp(mov_driver,writer->name))) {
		movie_driver = writer;
		i_movie_driver = i;
		XtVaSetValues(driver_option,XmNmenuHistory,push,NULL);
	    }
	    i++;
	}
    }

    /* audio formats */
    delete_children(audio_menu);
    for (i = 0; NULL != movie_driver->audio[i].name; i++) {
	str = XmStringGenerate
	    ((char*)(movie_driver->audio[i].desc ?
		     movie_driver->audio[i].desc : 
		     ng_afmt_to_desc[movie_driver->audio[i].fmtid]),
	     NULL, XmMULTIBYTE_TEXT, NULL);
	push = XtVaCreateManagedWidget(movie_driver->audio[i].name,
				       xmPushButtonWidgetClass,audio_menu,
				       XmNlabelString,str,
				       NULL);
	XmStringFree(str);
	add_cmd_callback(push,XmNactivateCallback,
			 "movie","audio",movie_driver->audio[i].name);
	if (NULL != mov_audio)
	    if (0 == strcasecmp(mov_audio,movie_driver->audio[i].name)) {
		XtVaSetValues(audio_option,XmNmenuHistory,push,NULL);
		movie_audio = i;
	    }
    }
    str = XmStringGenerate("no sound", NULL, XmMULTIBYTE_TEXT, NULL);
    push = XtVaCreateManagedWidget("none",xmPushButtonWidgetClass,audio_menu,
				   XmNlabelString,str,NULL);
    XmStringFree(str);
    add_cmd_callback(push,XmNactivateCallback, "movie","audio","none");

    /* video formats */
    delete_children(video_menu);
    for (i = 0; NULL != movie_driver->video[i].name; i++) {
	str = XmStringGenerate
	    ((char*)(movie_driver->video[i].desc ?
		     movie_driver->video[i].desc : 
		     ng_vfmt_to_desc[movie_driver->video[i].fmtid]),
	     NULL, XmMULTIBYTE_TEXT, NULL);
	push = XtVaCreateManagedWidget(movie_driver->video[i].name,
				       xmPushButtonWidgetClass,video_menu,
				       XmNlabelString,str,
				       NULL);
	XmStringFree(str);
	add_cmd_callback(push,XmNactivateCallback,
			 "movie","video",movie_driver->video[i].name);
	if (NULL != mov_video)
	    if (0 == strcasecmp(mov_video,movie_driver->video[i].name)) {
		XtVaSetValues(video_option,XmNmenuHistory,push,NULL);
		movie_video = i;
	    }
    }

    /* need audio filename? */
    sensitive = movie_driver->combined ? False : True;
    XtVaSetValues(m_faudio, XtNsensitive,sensitive, NULL);
    XtVaSetValues(m_faudioL, XtNsensitive,sensitive, NULL);
    XtVaSetValues(m_faudioB, XtNsensitive,sensitive, NULL);
}

static void
init_movie_menus(void)
{
    update_movie_menus();

    if (mov_rate)
	do_va_cmd(3,"movie","rate",mov_rate);
    if (mov_fps)
	do_va_cmd(3,"movie","fps",mov_fps);
}

static void
do_movie_record(int argc, char **argv)
{
    char *fvideo,*faudio;
    struct ng_video_fmt video;
    struct ng_audio_fmt audio;
    const struct ng_writer *wr;
    WidgetList children;
    Cardinal nchildren;
    Widget text;
    int i,rate,fps;

    /* set parameters */
    if (argc > 1 && 0 == strcasecmp(argv[0],"driver")) {
	struct list_head *item;
	struct ng_writer *writer;

	if (debug)
	    fprintf(stderr,"set driver: %s\n",argv[1]);
	XtVaGetValues(driver_menu,XtNchildren,&children,
		      XtNnumChildren,&nchildren,NULL);
	i = 0;
	list_for_each(item,&ng_writers) {
	    writer = list_entry(item, struct ng_writer, list);
	    if (0 == strcasecmp(argv[1],writer->name)) {
		movie_driver = writer;
		i_movie_driver = i;
	    }
	    i++;
	}
	update_movie_menus();
    }
    if (argc > 1 && 0 == strcasecmp(argv[0],"audio")) {
	if (debug)
	    fprintf(stderr,"set audio: %s\n",argv[1]);
	XtVaGetValues(audio_menu,XtNchildren,&children,
		      XtNnumChildren,&nchildren,NULL);
	for (i = 0; NULL != movie_driver->audio[i].name; i++) {
	    if (0 == strcasecmp(argv[1],movie_driver->audio[i].name)) {
		XtVaSetValues(audio_option,XmNmenuHistory,children[i],NULL);
		movie_audio = i;
	    }
	}
	if (0 == strcmp(argv[1],"none")) {
	    XtVaSetValues(audio_option,XmNmenuHistory,children[i],NULL);
	    movie_audio = i;
	}
    }
    if (argc > 1 && 0 == strcasecmp(argv[0],"video")) {
	if (debug)
	    fprintf(stderr,"set video: %s\n",argv[1]);
	XtVaGetValues(video_menu,XtNchildren,&children,
		      XtNnumChildren,&nchildren,NULL);
	for (i = 0; NULL != movie_driver->video[i].name; i++) {
	    if (0 == strcasecmp(argv[1],movie_driver->video[i].name)) {
		XtVaSetValues(video_option,XmNmenuHistory,children[i],NULL);
		movie_video = i;
	    }
	}
    }
    if (argc > 1 && 0 == strcasecmp(argv[0],"rate")) {
	XtVaGetValues(m_rate,XmNtextField,&text,NULL);
	XmTextSetString(text,argv[1]);
    }
    if (argc > 1 && 0 == strcasecmp(argv[0],"fps")) {
	XtVaGetValues(m_fps,XmNtextField,&text,NULL);
	XmTextSetString(text,argv[1]);
    }
    if (argc > 1 && 0 == strcasecmp(argv[0],"fvideo")) {
	XmTextSetString(m_fvideo,argv[1]);
    }
    if (argc > 1 && 0 == strcasecmp(argv[0],"faudio")) {
	XmTextSetString(m_faudio,argv[1]);
    }

    /* start */
    if (argc > 0 && 0 == strcasecmp(argv[0],"start")) {
	if (0 != cur_movie)
	    return; /* records already */
	cur_movie = 1;
	movie_blit = (cur_capture == CAPTURE_GRABDISPLAY);
	video_gd_suspend();
	XmToggleButtonSetState(levels_toggle,0,True);
	
	fvideo = XmTextGetString(m_fvideo);
	faudio = XmTextGetString(m_faudio);
	fvideo = tilde_expand(fvideo);
	faudio = tilde_expand(faudio);

	XtVaGetValues(m_rate,XmNtextField,&text,NULL);
	rate = atoi(XmTextGetString(text));
	XtVaGetValues(m_fps,XmNtextField,&text,NULL);
	fps = (int)(atof(XmTextGetString(text))*1000);
	
	memset(&video,0,sizeof(video));
	memset(&audio,0,sizeof(audio));

	wr = movie_driver;
	video.fmtid  = wr->video[movie_video].fmtid;
	video.width  = cur_tv_width;
	video.height = cur_tv_height;
	audio.fmtid  = wr->audio[movie_audio].fmtid;
	audio.rate   = rate;
	
	movie_state = movie_writer_init
	    (fvideo, faudio, wr,
	     &video, wr->video[movie_video].priv, fps,
	     &audio, wr->audio[movie_audio].priv, args.dspdev,
	     args.bufcount,args.parallel);
	if (NULL == movie_state) {
	    /* init failed */
	    video_gd_restart();
	    cur_movie = 0;
	    /* hmm, not the most elegant way to flag an error ... */
	    toolkit_set_label(m_status, "error [init]");
	    return;
	}
	if (0 != movie_writer_start(movie_state)) {
	    /* start failed */
	    movie_writer_stop(movie_state);
	    video_gd_restart();
	    cur_movie = 0;
	    /* hmm, not the most elegant way to flag an error ... */
	    toolkit_set_label(m_status, "error [start]");
	    return;
	}
	rec_work_id  = XtAppAddWorkProc(app_context,rec_work,NULL);
	toolkit_set_label(m_status, "recording");
	return;
    }
    
    /* stop */
    if (argc > 0 && 0 == strcasecmp(argv[0],"stop")) {
	if (0 == cur_movie)
	    return; /* nothing to stop here */

	movie_writer_stop(movie_state);
	XtRemoveWorkProc(rec_work_id);
	rec_work_id = 0;
	video_gd_restart();
	cur_movie = 0;
	return;
    }
}

static void
do_rec_status(char *message)
{
    toolkit_set_label(m_status, message);
}

/*----------------------------------------------------------------------*/

#ifdef HAVE_ZVBI
#define CHSCAN 250
static int chscan;
static int chvbi;
static XtIntervalId chtimer;
static Widget chdlg,chscale;

static void
chscan_timeout(XtPointer client_data, XtIntervalId *id)
{
    struct CHANNEL *c;
    char title[32];
    XmString xmstr;

    if (!x11_vbi_tuned()) {
	if (debug)
	    fprintf(stderr,"scan [%s]: no station\n",chanlist[chscan].name);
	goto next_station;
    }

    if (0 != x11_vbi_station[0]) {
	if (debug)
	    fprintf(stderr,"scan [%s]: %s\n",chanlist[chscan].name,
		    x11_vbi_station);
	c = add_channel(x11_vbi_station);
	c->cname   = strdup(chanlist[chscan].name);
	c->channel = chscan;
	cur_sender = count-1;
	channel_menu();
	goto next_station;
    }

    if (chvbi++ > 3) {
	if (debug)
	    fprintf(stderr,"scan [%s]: no vbi name\n",chanlist[chscan].name);
	sprintf(title,"%s [no name]",chanlist[chscan].name);
	c = add_channel(title);
	c->cname   = strdup(chanlist[chscan].name);
	c->channel = chscan;
	cur_sender = count-1;
	channel_menu();
	goto next_station;
    }

    if (debug)
	fprintf(stderr,"scan [%s] vbi ...\n",chanlist[chscan].name);
    chtimer = XtAppAddTimeOut(app_context, CHSCAN, chscan_timeout, NULL);
    return;

 next_station:
    chscan++;
    if (chscan >= chancount) {
	/* all done */
	x11_vbi_stop();
	chtimer = 0;
	if (count)
	    do_va_cmd(2,"setchannel",channels[0]->name);
	XtDestroyWidget(chdlg);
	chdlg = NULL;
	return;
    }
    chvbi  = 0;
    if (channel_switch_hook)
	channel_switch_hook();
    xmstr = XmStringGenerate(chanlist[chscan].name, NULL,
			     XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(chscale,XmNtitleString,xmstr,NULL);
    XmStringFree(xmstr);
    XmScaleSetValue(chscale,chscan);
    do_va_cmd(2,"setchannel",chanlist[chscan]);
    x11_vbi_station[0] = 0;
    chtimer = XtAppAddTimeOut(app_context, CHSCAN, chscan_timeout,NULL);
    return;
}

static void
chscan_start_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmString xmstr;

    /* check */
    if (!(f_drv & CAN_TUNE))
	return;
    if (channel_switch_hook)
	channel_switch_hook();
    
    /* clear */
    while (count) {
	XtDestroyWidget(channels[count-1]->button);
	del_channel(count-1);
    }
    cur_sender = -1;
    channel_menu();
    
    x11_vbi_start(args.vbidev);
    chscan = 0;
    chvbi  = 0;
    xmstr = XmStringGenerate(chanlist[chscan].name, NULL,
			     XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(chscale,XmNtitleString,xmstr,XmNmaximum,chancount,NULL);
    XmStringFree(xmstr);
    XmScaleSetValue(chscale,chscan);
    do_va_cmd(2,"setchannel",chanlist[chscan]);
    x11_vbi_station[0] = 0;
    chtimer = XtAppAddTimeOut(app_context, CHSCAN, chscan_timeout,NULL);
}
    
static void
chscan_cancel_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    x11_vbi_stop();
    if (chtimer)
	XtRemoveTimeOut(chtimer);
    chtimer = 0;
    XtDestroyWidget(chdlg);
    chdlg = NULL;
}

static void
chscan_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Widget rc;
    
    chdlg = XmCreatePromptDialog(control_shell,"chscan",NULL,0);
    XmdRegisterEditres(XtParent(chdlg));
    XtUnmanageChild(XmSelectionBoxGetChild(chdlg,XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(chdlg,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(chdlg,XmDIALOG_TEXT));
    
    rc = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass,chdlg,NULL);
    XtVaCreateManagedWidget("hints",xmLabelWidgetClass,rc,NULL);
    chscale = XtVaCreateManagedWidget("channel",xmScaleWidgetClass,rc,NULL);
    XtRemoveAllCallbacks(XmSelectionBoxGetChild(chdlg,XmDIALOG_OK_BUTTON),
			 XmNactivateCallback);
    XtAddCallback(XmSelectionBoxGetChild(chdlg,XmDIALOG_OK_BUTTON),
		  XmNactivateCallback,chscan_start_cb,NULL);
    XtAddCallback(chdlg,XmNcancelCallback,chscan_cancel_cb,NULL);
    XtManageChild(chdlg);
}
#endif

/*----------------------------------------------------------------------*/

static void
pref_menu(Widget option, Widget menu, int enable)
{
    delete_children(menu);
    XtVaSetValues(XmOptionButtonGadget(option),XtNsensitive,enable,NULL);
    XtVaSetValues(XmOptionLabelGadget(option),XtNsensitive,enable,NULL);
    if (!enable)
	XtVaCreateManagedWidget("none",xmPushButtonWidgetClass,menu,NULL);
}

#if defined(HAVE_LIBXXF86VM) || defined(HAVE_LIBXRANDR)

static void
pref_fs(void)
{
    Widget push;
    char s[32];
    int i,on;

    on = XmToggleButtonGetState(pref_fs_toggle);
    if (on) {
#if defined(HAVE_LIBXXF86VM)
	if (0 == have_randr  &&  0 == args.vidmode) {
	    args.vidmode = 1;
	    xfree_vm_init(dpy);
	}
#endif
	if (0 == have_randr  &&  0 == have_vm) {
	    on = 0;
	    XtVaSetValues(pref_fs_toggle,XtNsensitive,0,NULL);
	}
    }
    
    XmToggleButtonSetState(pref_fs_toggle,on,False);
    if (on) {
	pref_menu(pref_fs_option,pref_fs_menu,1);
#if defined(HAVE_LIBXRANDR)
	if (have_randr) {
	    for (i = 0; i < nrandr; i++) {
		sprintf(s,"%d x %d",randr[i].width,randr[i].height);
		push = XtVaCreateManagedWidget(s,xmPushButtonWidgetClass,
					       pref_fs_menu,NULL);
		if (randr[i].width  == fs_width &&
		    randr[i].height == fs_height) {
		    XtVaSetValues(pref_fs_menu,XmNmenuHistory,push,NULL);
		}
	    }
	}
#endif
#if defined(HAVE_LIBXXF86VM)
	if (!have_randr) {
	    for (i = 0; i < vm_count; i++) {
		sprintf(s,"%d x %d",
			vm_modelines[i]->hdisplay,
			vm_modelines[i]->vdisplay);
		push = XtVaCreateManagedWidget(s,xmPushButtonWidgetClass,
					       pref_fs_menu,NULL);
		if (vm_modelines[i]->hdisplay == fs_width &&
		    vm_modelines[i]->vdisplay == fs_height) {
		    XtVaSetValues(pref_fs_menu,XmNmenuHistory,push,NULL);
		}
	    }
	}
#endif
    } else {
	pref_menu(pref_fs_option,pref_fs_menu,0);
    }
}

static void
pref_fst_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    pref_fs();
}
#endif

static void
pref_mix2(void)
{
    struct ng_mix_driver *mix;
    Widget push,w = NULL;
    char *name;
    int i,on;
    struct ng_devinfo *info = NULL;
    
    on = XmToggleButtonGetState(pref_mix_toggle);
    XtVaGetValues(pref_mix1_menu,XmNmenuHistory,&w,NULL);
    if (w) {
	name = XtName(w);
	if (!list_empty(&ng_mix_drivers) && 0 != strcmp(name,"none")) {
	    mix = list_entry(ng_mix_drivers.next,struct ng_mix_driver,list);
	    info = mix->channels(name);
	}
    }
    
    if (NULL != info && on) {
 	pref_menu(pref_mix2_option,pref_mix2_menu,1);
	for (i = 0; 0 != strlen(info[i].name); i++) {
	    push = XtVaCreateManagedWidget(info[i].device,
					   xmPushButtonWidgetClass,
					   pref_mix2_menu,NULL);
	    toolkit_set_label(push,info[i].name);
	    if (strcasecmp(info[i].device,mixerctl) == 0)
		XtVaSetValues(pref_mix2_menu,XmNmenuHistory,push,NULL);
	}
    } else {
	pref_menu(pref_mix2_option,pref_mix2_menu,0);
    }
}

static void
pref_mix2_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    pref_mix2();
}

static void
pref_mix1(void)
{
    struct ng_mix_driver *mix;
    Widget push;
    int on,i;
    struct ng_devinfo *info = NULL;

    on = XmToggleButtonGetState(pref_mix_toggle);
    if (!list_empty(&ng_mix_drivers)) {
	mix = list_entry(ng_mix_drivers.next,struct ng_mix_driver,list);
	info = mix->probe();
    }
    if (NULL != info && on) {
	pref_menu(pref_mix1_option,pref_mix1_menu,1);
	for (i = 0; 0 != strlen(info[i].name); i++) {
	    push = XtVaCreateManagedWidget(info[i].device,
					   xmPushButtonWidgetClass,
					   pref_mix1_menu,
					   NULL);
	    XtAddCallback(push,XmNactivateCallback,pref_mix2_cb,NULL);
	    toolkit_set_label(push,info[i].name);
	    if (0 == i  ||  0 == strcmp(info[i].device,mixerdev))
		XtVaSetValues(pref_mix1_menu,XmNmenuHistory,push,NULL);
	}
    } else {
	pref_menu(pref_mix1_option,pref_mix1_menu,0);
    }
}

static void
pref_mix1_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    pref_mix1();
    pref_mix2();
}

static void
pref_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmSelectionBoxCallbackStruct *cb = call_data;
    Widget w;
    char *name = NULL;
    int on,width,height;

    if (cb->reason == XmCR_OK  ||  cb->reason == XmCR_APPLY) {
#ifdef HAVE_LIBXXF86VM
	on = XmToggleButtonGetState(pref_fs_toggle);
	if (on) {
	    XtVaGetValues(pref_fs_menu,XmNmenuHistory,&w,NULL);
	    name = XtName(w);
	    sscanf(name,"%d x %d",&width,&height);
	    fs_width  = width;
	    fs_height = height;
	} else {
	    fs_width  = 0;
	    fs_height = 0;
	}
#endif
	on = XmToggleButtonGetState(pref_mix_toggle);
	if (on) {
	    w = NULL;
	    XtVaGetValues(pref_mix1_menu,XmNmenuHistory,&w,NULL);
	    if (w)
		strcpy(mixerdev,XtName(w));
	    w = NULL;
	    XtVaGetValues(pref_mix2_menu,XmNmenuHistory,&w,NULL);
	    if (w)
		strcpy(mixerctl,XtName(w));
	} else {
	    mixerdev[0] = '\0';
	    mixerctl[0] = '\0';
	}
	use_osd = XmToggleButtonGetState(pref_osd);
	keypad_ntsc = XmToggleButtonGetState(pref_ntsc);
	keypad_partial = XmToggleButtonGetState(pref_partial);
	ng_jpeg_quality = atoi(XmTextGetString(pref_quality));
    }
    if (cb->reason == XmCR_OK) {
	save_config();
    }
    XtUnmanageChild(pref_dlg);
}

static void
pref_manage_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    char tmp[16];
    
#ifdef HAVE_LIBXXF86VM
    XmToggleButtonSetState(pref_fs_toggle,fs_width && fs_height,False);
    pref_fs();
#endif
    XmToggleButtonSetState(pref_mix_toggle,strlen(mixerdev) > 0,False);
    pref_mix1();
    pref_mix2();
    XmToggleButtonSetState(pref_osd,use_osd,False);
    XmToggleButtonSetState(pref_ntsc,keypad_ntsc,False);
    XmToggleButtonSetState(pref_partial,keypad_partial,False);
    sprintf(tmp,"%d",ng_jpeg_quality);
    XmTextSetString(pref_quality,tmp);
    XtManageChild(pref_dlg);
}

static void
create_pref(void)
{
    Widget rc1,frame,rc2,rc3;
    Arg args[2];

    pref_dlg = XmCreatePromptDialog(control_shell,"pref",NULL,0);
    XmdRegisterEditres(XtParent(pref_dlg));
    XtUnmanageChild(XmSelectionBoxGetChild(pref_dlg,XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(pref_dlg,XmDIALOG_HELP_BUTTON));
    XtManageChild(XmSelectionBoxGetChild(pref_dlg,XmDIALOG_APPLY_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(pref_dlg,XmDIALOG_TEXT));

    rc1 = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass, pref_dlg,
				  NULL);

#ifdef HAVE_LIBXXF86VM
    /* first frame */
    frame = XtVaCreateManagedWidget("fsF",xmFrameWidgetClass,rc1,NULL);
    XtVaCreateManagedWidget("fsL",xmLabelWidgetClass,frame,NULL);
    rc2 = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass,frame,NULL);

    /* fullscreen */
    pref_fs_toggle = XtVaCreateManagedWidget("fsT",xmToggleButtonWidgetClass,
					     rc2,NULL);
    XtAddCallback(pref_fs_toggle,XmNvalueChangedCallback,pref_fst_cb,NULL);
    pref_fs_menu = XmCreatePulldownMenu(rc2,"fsM",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,pref_fs_menu);
    pref_fs_option = XmCreateOptionMenu(rc2,"fsO",args,1);
    XtManageChild(pref_fs_option);
#endif
    
    /* second frame */
    frame = XtVaCreateManagedWidget("mixF",xmFrameWidgetClass,rc1,NULL);
    XtVaCreateManagedWidget("mixL",xmLabelWidgetClass,frame,NULL);
    rc2 = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass,frame,NULL);
    pref_mix_toggle = XtVaCreateManagedWidget("mixT",xmToggleButtonWidgetClass,
					      rc2,NULL);
    XtAddCallback(pref_mix_toggle,XmNvalueChangedCallback,pref_mix1_cb,NULL);
    
    pref_mix1_menu = XmCreatePulldownMenu(rc2,"mix1M",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,pref_mix1_menu);
    pref_mix1_option = XmCreateOptionMenu(rc2,"mix1O",args,1);
    XtManageChild(pref_mix1_option);
    
    pref_mix2_menu = XmCreatePulldownMenu(rc2,"mix2M",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,pref_mix2_menu);
    pref_mix2_option = XmCreateOptionMenu(rc2,"mix2O",args,1);
    XtManageChild(pref_mix2_option);
    
    /* third frame */
    frame = XtVaCreateManagedWidget("optF",xmFrameWidgetClass,rc1,NULL);
    XtVaCreateManagedWidget("optL",xmLabelWidgetClass,frame,NULL);
    rc2 = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass,frame,NULL);
    
    /* options */
    pref_osd = XtVaCreateManagedWidget("osd",xmToggleButtonWidgetClass,
				       rc2,NULL);
    pref_ntsc = XtVaCreateManagedWidget("keypad-ntsc",
					xmToggleButtonWidgetClass,
					rc2,NULL);
    pref_partial = XtVaCreateManagedWidget("keypad-partial",
					   xmToggleButtonWidgetClass,
					   rc2,NULL);
    rc3 = XtVaCreateManagedWidget("jpeg", xmRowColumnWidgetClass,
				  rc2,NULL);
    XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc3,NULL);
    pref_quality = XtVaCreateManagedWidget("quality",
					   xmTextWidgetClass,
					   rc3,NULL);
    
    /* buttons */
    XtAddCallback(pref_dlg,XmNokCallback,pref_done_cb,NULL);
    XtAddCallback(pref_dlg,XmNapplyCallback,pref_done_cb,NULL);
    XtAddCallback(pref_dlg,XmNcancelCallback,pref_done_cb,NULL);
}

/*---------------------------------------------------------------------- */
/* selection & dnd support                                               */

static struct ng_video_buf*
convert_buffer(struct ng_video_buf *in, int out_fmt)
{
    struct ng_video_conv *conv;
    struct ng_convert_handle *ch;
    struct ng_video_fmt ofmt;
    int i;

    /* find converter */
    for (i = 0;;) {
	conv = ng_conv_find_to(out_fmt,&i);
	if (NULL == conv)
	    break;
	if (conv->fmtid_in == in->fmt.fmtid)
	    goto found;
    }
    return NULL;

 found:
    memset(&ofmt,0,sizeof(ofmt));
    ofmt.fmtid  = out_fmt;
    ofmt.width  = in->fmt.width;
    ofmt.height = in->fmt.height;
    ch = ng_convert_alloc(conv,&in->fmt,&ofmt);
    return ng_convert_single(ch,in);
}

static struct ng_video_buf*
scale_rgb_buffer(struct ng_video_buf *in, int scale)
{
    struct ng_video_fmt fmt;
    struct ng_video_buf *buf;
    char *src,*dst;
    unsigned int x,y;
    
    fmt = in->fmt;
    fmt.width  = in->fmt.width  / scale;
    fmt.height = in->fmt.height / scale;
    while (fmt.width & 0x03)
	fmt.width++;
    fmt.bytesperline = fmt.width * 3;
    buf = ng_malloc_video_buf(&fmt, fmt.width * fmt.height * 3);
    
    /* scale down */
    dst = buf->data;
    for (y = 0; y < fmt.height; y++) {
	src = in->data + y * scale * in->fmt.bytesperline;
	for (x = 0; x < fmt.width; x++) {
	    dst[0] = src[0];
	    dst[1] = src[1];
	    dst[2] = src[2];
	    dst += 3;
	    src += 3*scale;
	}
    }
    return buf;
}

struct ipc_data {
    struct list_head     list;
    Atom                 atom;
    struct ng_video_buf  *buf;
    char                 *filename;
    Pixmap               pix;
    Pixmap               icon_pixmap;
    Widget               icon_widget;
};
struct list_head ipc_selections;

static void
ipc_iconify(Widget widget, struct ipc_data *ipc)
{
    struct ng_video_buf *small;
    int scale,depth;
    Arg args[4];
    Cardinal n=0;

    /* calc size */
    for (scale = 1;; scale++) {
	if (ipc->buf->fmt.width  / scale < 128 &&
	    ipc->buf->fmt.height / scale < 128)
	    break;
    }

    /* scale down & create pixmap */
    small = scale_rgb_buffer(ipc->buf,scale);
    small = convert_buffer(small, x11_dpy_fmtid);
    ipc->icon_pixmap = x11_create_pixmap(dpy,&vinfo,small);
        
    /* build DnD icon */
    n = 0;
    depth = DefaultDepthOfScreen(XtScreen(widget));
    XtSetArg(args[n], XmNpixmap, ipc->icon_pixmap); n++;
    XtSetArg(args[n], XmNwidth,  small->fmt.width); n++;
    XtSetArg(args[n], XmNheight, small->fmt.height); n++;
    XtSetArg(args[n], XmNdepth,  depth); n++;
    ipc->icon_widget = XmCreateDragIcon(widget,"dragicon",args,n);
    
    ng_release_video_buf(small);
}

static struct ipc_data*
ipc_find(Atom selection)
{
    struct list_head   *item;
    struct ipc_data    *ipc;
    
    list_for_each(item,&ipc_selections) {
	ipc = list_entry(item, struct ipc_data, list);
	if (ipc->atom == selection)
	    return ipc;
    }
    return NULL;
}

static struct ipc_data*
ipc_init(Atom selection)
{
    struct ipc_data *ipc;
    struct ng_video_fmt fmt;
    struct ng_video_buf *buf;
    
    /* capture a frame and save a copy */
    video_gd_suspend();
    memset(&fmt,0,sizeof(fmt));
    fmt.fmtid  = VIDEO_RGB24;
    fmt.width  = cur_tv_width;
    fmt.height = cur_tv_height;
    buf = ng_grabber_get_image(&fmt);
    buf = ng_filter_single(cur_filter,buf);
    ipc = malloc(sizeof(*ipc));
    memset(ipc,0,sizeof(*ipc));
    ipc->buf = ng_malloc_video_buf(&buf->fmt,buf->size);
    ipc->atom = selection;
    ipc->buf->info = buf->info;
    memcpy(ipc->buf->data,buf->data,buf->size);
    ng_release_video_buf(buf);
    video_gd_restart();

    list_add_tail(&ipc->list,&ipc_selections);
    return ipc;
}

static void
ipc_tmpfile(struct ipc_data *ipc)
{
    static char *base = "motv";
    struct ng_video_buf *buf;
    char *tmpdir;
    int fd;

    if (NULL != ipc->filename)
	return;

    tmpdir = getenv("TMPDIR");
    if (NULL == tmpdir)
	tmpdir="/tmp";
    ipc->filename = malloc(strlen(tmpdir)+strlen(base)+16);
    sprintf(ipc->filename,"%s/%s-XXXXXX",tmpdir,base);
    fd = mkstemp(ipc->filename);

    ipc->buf->refcount++;
    buf = convert_buffer(ipc->buf, VIDEO_JPEG);
    write(fd,buf->data,buf->size);
    ng_release_video_buf(buf);
}

static void
ipc_pixmap(struct ipc_data *ipc)
{
    struct ng_video_buf *buf;
    
    if (0 != ipc->pix)
	return;

    ipc->buf->refcount++;
    buf = convert_buffer(ipc->buf, x11_dpy_fmtid);
    ipc->pix = x11_create_pixmap(dpy,&vinfo,buf);
    ng_release_video_buf(buf);
    return;
}

static void
ipc_fini(Atom selection)
{
    struct ipc_data *ipc;

    ipc = ipc_find(selection);
    if (NULL == ipc)
	return;

    /* free stuff */
    if (ipc->buf)
	ng_release_video_buf(ipc->buf);
    if (ipc->filename) {
	unlink(ipc->filename);
	free(ipc->filename);
    }
    if (ipc->icon_widget)
	XtDestroyWidget(ipc->icon_widget);
    if (ipc->icon_pixmap)
	XFreePixmap(dpy,ipc->icon_pixmap);
    if (ipc->pix)
	XFreePixmap(dpy,ipc->pix);

    list_del(&ipc->list);
    free(ipc);
}

static Atom ipc_unique_atom(Widget widget)
{
    char id_name[32];
    Atom id;
    int i;

    for (i = 0;; i++) {
	sprintf(id_name,"_MOTV_IMAGE_%lX_%d",XtWindow(widget),i);
	id = XInternAtom(XtDisplay(widget),id_name,False);
	if (NULL == ipc_find(id))
	    break;
    }
    return id;
}

static void
ipc_convert(Widget widget, XtPointer ignore, XtPointer call_data)
{
    XmConvertCallbackStruct *ccs = call_data;
    struct ipc_data *ipc;
    Atom *targs;
    Pixmap *pix;
    unsigned long *ldata;
    unsigned char *cdata;
    char *filename;
    int n;

    if (debug) {
	char *y = !ccs->type      ? NULL : XGetAtomName(dpy,ccs->type);
	char *t = !ccs->target    ? NULL : XGetAtomName(dpy,ccs->target);
	char *s = !ccs->selection ? NULL : XGetAtomName(dpy,ccs->selection);
	fprintf(stderr,"conv: target=%s type=%s selection=%s\n",t,y,s);
	if (y) XFree(y);
	if (t) XFree(t);
	if (s) XFree(s);
    }
    
    /* tell which formats we can handle */
    if ((ccs->target == XA_TARGETS) ||
	(ccs->target == _MOTIF_CLIPBOARD_TARGETS) ||
	(ccs->target == _MOTIF_DEFERRED_CLIPBOARD_TARGETS) ||
	(ccs->target == _MOTIF_EXPORT_TARGETS)) {
	n = 0;
	targs = (Atom*)XtMalloc(sizeof(Atom)*12);
	if (ccs->target != _MOTIF_CLIPBOARD_TARGETS) {
	    targs[n++] = XA_TARGETS;
	    targs[n++] = MIME_IMAGE_PPM;
	    targs[n++] = XA_PIXMAP;
	    targs[n++] = XA_FOREGROUND;
	    targs[n++] = XA_BACKGROUND;
	    targs[n++] = XA_COLORMAP;
	    targs[n++] = MIME_IMAGE_JPEG;
	    targs[n++] = XA_FILE_NAME;
	    targs[n++] = XA_FILE;
	    targs[n++] = MIME_TEXT_URI_LIST;
	    targs[n++] = _NETSCAPE_URL;
	}
	if (ccs->target == _MOTIF_EXPORT_TARGETS) {
	    /* save away drag'n'drop data */
	    ipc_init(ccs->selection);
	}
	ccs->value  = targs;
	ccs->length = n;
	ccs->type   = XA_ATOM;
	ccs->format = 32;
	ccs->status = XmCONVERT_DONE;
	return;

    } else if (ccs->target == _MOTIF_SNAPSHOT) {
	/* save away clipboard data */
	n = 0;
	targs = (Atom*)XtMalloc(sizeof(Atom));
	targs[n++] = ipc_unique_atom(widget);
	ipc_init(targs[0]);
	ccs->value  = targs;
	ccs->length = n;
	ccs->type   = XA_ATOM;
	ccs->format = 32;
	ccs->status = XmCONVERT_DONE;
	return;
    }
    
    /* find data */
    ipc = ipc_find(ccs->selection);
    if (NULL == ipc) {
	/* shouldn't happen */
	fprintf(stderr,"oops: selection data not found\n");
	ccs->status = XmCONVERT_REFUSE;
	return;
    }
    
    if (ccs->target == _MOTIF_LOSE_SELECTION ||
	ccs->target == XA_DONE) {
	/* cleanup */
	ipc_fini(ccs->selection);
	ccs->value  = NULL;
	ccs->length = 0;
	ccs->type   = XA_INTEGER;
	ccs->format = 32;
	ccs->status = XmCONVERT_DONE;
	return;
    }

    /* convert data */
    if (ccs->target == XA_BACKGROUND ||
	ccs->target == XA_FOREGROUND ||
	ccs->target == XA_COLORMAP) {
	n = 0;
	ldata = (Atom*)XtMalloc(sizeof(Atom)*8);
	if (ccs->target == XA_BACKGROUND) {
	    ldata[n++] = WhitePixelOfScreen(XtScreen(widget));
	    ccs->type  = XA_PIXEL;
	}
	if (ccs->target == XA_FOREGROUND) {
	    ldata[n++] = BlackPixelOfScreen(XtScreen(widget));
	    ccs->type  = XA_PIXEL;
	}
	if (ccs->target == XA_COLORMAP) {
	    ldata[n++] = DefaultColormapOfScreen(XtScreen(widget));
	    ccs->type  = XA_COLORMAP;
	}
	ccs->value  = ldata;
	ccs->length = n;
	ccs->format = 32;
	ccs->status = XmCONVERT_DONE;

    } else if (ccs->target == XA_PIXMAP) {
	/* xfer pixmap */
	ipc_pixmap(ipc);
	pix = (Pixmap*)XtMalloc(sizeof(Pixmap));
	pix[0] = ipc->pix;
	if (debug)
	    fprintf(stderr,"conv: pixmap id is 0x%lx\n",pix[0]);
	ccs->value  = pix;
	ccs->length = 1;
	ccs->type   = XA_DRAWABLE;
	ccs->format = 32;
	ccs->status = XmCONVERT_DONE;

    } else if (ccs->target == MIME_IMAGE_PPM) {
	cdata = XtMalloc(ipc->buf->size + 32);
	n = sprintf(cdata,"P6\n%d %d\n255\n",
		    ipc->buf->fmt.width,ipc->buf->fmt.height);
	memcpy(cdata+n,ipc->buf->data,ipc->buf->size);
	ccs->value  = cdata;
	ccs->length = n+ipc->buf->size;
	ccs->type   = MIME_IMAGE_PPM;
	ccs->format = 8;
	ccs->status = XmCONVERT_DONE;
	
    } else if (ccs->target == MIME_IMAGE_JPEG) {
	struct ng_video_buf *buf;
	ipc->buf->refcount++;
	buf = convert_buffer(ipc->buf, VIDEO_JPEG);
	cdata = XtMalloc(buf->size);
	memcpy(cdata,buf->data,buf->size);
	ng_release_video_buf(buf);
	ccs->value  = cdata;
	ccs->length = buf->size;
	ccs->type   = MIME_IMAGE_JPEG;
	ccs->format = 8;
	ccs->status = XmCONVERT_DONE;
	
    } else if (ccs->target == XA_FILE_NAME       ||
	       ccs->target == XA_FILE            ||
	       ccs->target == XA_STRING          ||
	       ccs->target == MIME_TEXT_URI_LIST ||
	       ccs->target == _NETSCAPE_URL) {
	/* xfer filename (image via tmp file) */
	ipc_tmpfile(ipc);
	if (ccs->target == MIME_TEXT_URI_LIST ||
	    ccs->target == _NETSCAPE_URL) {
	    /* filename => url */
	    filename = XtMalloc(strlen(ipc->filename)+8);
	    sprintf(filename,"file:%s\r\n",ipc->filename);
	    ccs->type = ccs->target;
	    if (debug)
		fprintf(stderr,"conv: tmp url is %s\n",filename);
	} else {
	    filename = XtMalloc(strlen(ipc->filename));
	    strcpy(filename,ipc->filename);
	    ccs->type = XA_STRING;
	    if (debug)
		fprintf(stderr,"conv: tmp file is %s\n",filename);
	}
	ccs->value  = filename;
	ccs->length = strlen(filename);
	ccs->format = 8;
	ccs->status = XmCONVERT_DONE;

    } else {
	/* shouldn't happen */
	fprintf(stderr,"oops: unknown target\n");
	ccs->status = XmCONVERT_REFUSE;
    }
}

static void
ipc_finish(Widget widget, XtPointer ignore, XtPointer call_data)
{
    if (debug)
	fprintf(stderr,"conv: transfer finished\n");
    ipc_fini(_MOTIF_DROP);
}

void IpcAction(Widget widget, XEvent *event, String *argv, Cardinal *argc)
{
    struct    ipc_data *ipc;
    Widget    drag;
    Arg       args[4];
    Cardinal  n=0;

    if (0 == *argc)
	return;
    if (!(f_drv & CAN_CAPTURE)) {
	if (debug)
	    fprintf(stderr,"ipc: can't capture - cancel\n");
	return;
    }

    if (debug)
	fprintf(stderr,"ipc: %s\n",argv[0]);
    if (0 == strcmp(argv[0],"drag")) {
	ipc_fini(_MOTIF_DROP);
	ipc = ipc_init(_MOTIF_DROP);
	ipc_iconify(widget,ipc);
	n = 0;
	XtSetArg(args[n], XmNdragOperations, XmDROP_COPY); n++;
	XtSetArg(args[n], XmNsourcePixmapIcon, ipc->icon_widget); n++;
	drag = XmeDragSource(tv, NULL, event, args, n);
	XtAddCallback(drag, XmNdragDropFinishCallback, ipc_finish, NULL);
    }
    if (0 == strcmp(argv[0],"primary")) {
#if 0
	ipc_fini(XA_PRIMARY);
	ipc_init(XA_PRIMARY);
	XmePrimarySource(tv,XtLastTimestampProcessed(dpy));
#else
	fprintf(stderr,"FIXME [primary called]\n");
#endif
    }
    if (0 == strcmp(argv[0],"clipboard")) {
	XmeClipboardSource(tv,XmCOPY,XtLastTimestampProcessed(dpy));
    }
}

/*----------------------------------------------------------------------*/

Widget levels_left, levels_right;
XtInputId levels_id;
const struct ng_dsp_driver *levels_dsp;
void *levels_hdsp;

static void
levels_input(XtPointer clientdata, int *src, XtInputId *id)
{
    struct ng_audio_buf *buf;
    int left, right;

    buf = levels_dsp->read(levels_hdsp,0);
    oss_levels(buf,&left,&right);
    XmScaleSetValue(levels_left,left);
    XmScaleSetValue(levels_right,right);
    if (debug > 1)
	fprintf(stderr,"levels: left = %3d, right = %3d\r",left,right);
}

static void
levels_toggle_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmToggleButtonCallbackStruct *tb = call_data;
    struct ng_audio_fmt a;

    if (tb->reason != XmCR_VALUE_CHANGED)
	return;

    if (tb->set  &&  NULL == levels_dsp) {
	/* enable */
	a.fmtid = AUDIO_U8_STEREO;
	a.rate  = 44100;
	levels_dsp = ng_dsp_open(args.dspdev,&a,1,&levels_hdsp);
	if (levels_dsp) {
	    levels_dsp->startrec(levels_hdsp);
	    levels_id  = XtAppAddInput(app_context,levels_dsp->fd(levels_hdsp),
				       (XtPointer)XtInputReadMask,
				       levels_input,NULL);
	    if (debug)
		fprintf(stderr,"levels: started sound monitor\n");
	}
    }
    if (!tb->set  &&  NULL != levels_hdsp) {
	/* disable */
	XtRemoveInput(levels_id);
	levels_dsp->close(levels_hdsp);
	levels_dsp = NULL;
	levels_hdsp = NULL;
	XmScaleSetValue(levels_left,0);
	XmScaleSetValue(levels_right,0);
	if (debug)
	    fprintf(stderr,"levels: stopped sound monitor\n");
    }
}

static void
create_levels(void)
{
    Widget rc;
    
    levels_shell = XtVaAppCreateShell("levels", "MoTV",
				      topLevelShellWidgetClass,
				      dpy,
				      XtNclientLeader,app_shell,
				      XtNvisual,vinfo.visual,
				      XtNcolormap,colormap,
				      XtNdepth,vinfo.depth,
				      XmNdeleteResponse,XmDO_NOTHING,
				      NULL);
    XmdRegisterEditres(levels_shell);
    XmAddWMProtocolCallback(levels_shell,WM_DELETE_WINDOW,
			    popupdown_cb,levels_shell);
    rc = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass, levels_shell,
				 NULL);

    levels_toggle = XtVaCreateManagedWidget("enable",
					    xmToggleButtonWidgetClass,rc,
					    NULL);
    XtAddCallback(levels_toggle,XmNvalueChangedCallback,
		  levels_toggle_cb,NULL);

    levels_left = XtVaCreateManagedWidget("left",xmScaleWidgetClass,rc,
					  NULL);
    levels_right = XtVaCreateManagedWidget("right",xmScaleWidgetClass,rc,
					   NULL);
}

/*----------------------------------------------------------------------*/

struct stderr_handler {
    Widget box;
    XmString str;
    int pipe;
    XtInputId id;
};

static void
stderr_input(XtPointer clientdata, int *src, XtInputId *id)
{
    struct stderr_handler *h = clientdata;
    XmString item;
    Widget label;
    char buf[1024];
    int rc;
    
    rc = read(h->pipe,buf,sizeof(buf)-1);
    if (rc <= 0) {
	/* Oops */
	XtRemoveInput(h->id);
	close(h->pipe);
	XtDestroyWidget(h->box);
	free(h);
    }
    buf[rc] = 0;
    item = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL);
    h->str = XmStringConcatAndFree(h->str,item);
    label = XmMessageBoxGetChild(h->box,XmDIALOG_MESSAGE_LABEL);
    XtVaSetValues(label,XmNlabelString,h->str,NULL);
    XtManageChild(h->box);
};

static void
stderr_ok_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct stderr_handler *h = clientdata;

    XmStringFree(h->str);
    h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
    XtUnmanageChild(h->box);
}

static void
stderr_close_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct stderr_handler *h = clientdata;

    XmStringFree(h->str);
    h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
}

static void
stderr_init(void)
{
    struct stderr_handler *h;
    int p[2];

    if (debug)
	return;
    h = malloc(sizeof(*h));
    memset(h,0,sizeof(*h));
    h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
    h->box = XmCreateErrorDialog(app_shell,"errbox",NULL,0);
    XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_CANCEL_BUTTON));
    XtAddCallback(h->box,XmNokCallback,stderr_ok_cb,h);
    XtAddCallback(XtParent(h->box),XmNpopdownCallback,stderr_close_cb,h);
    pipe(p);
    dup2(p[1],2);
    close(p[1]);
    h->pipe = p[0];
    h->id = XtAppAddInput(app_context,h->pipe,(XtPointer)XtInputReadMask,
			  stderr_input,h);
}

/*----------------------------------------------------------------------*/

int
main(int argc, char *argv[])
{
    int            i;
    unsigned long  freq;
    
    hello_world("motv");
    XtSetLanguageProc(NULL,NULL,NULL);
    app_shell = XtVaAppInitialize(&app_context, "MoTV",
				  opt_desc, opt_count,
				  &argc, argv,
				  fallback_ressources,
				  NULL);
    XmdRegisterEditres(app_shell);
    dpy = XtDisplay(app_shell);
    x11_icons_init(dpy,0);
    init_atoms(dpy);

    /* handle command line args */
    ng_init();
    handle_cmdline_args();
    
    /* device scan */
    if (args.hwscan) {
	fprintf(stderr,"looking for available devices\n");
#ifdef HAVE_LIBXV
	xv_video_init(-1,1);
#endif
	grabber_scan();
    }
    
    /* look for a useful visual */
    visual_init("motv","MoTV");
    
    /* remote display? */
    do_overlay = !args.remote;
    if (do_overlay)
	x11_check_remote();
    v4lconf_init();

    /* x11 stuff */
    XtAppAddActions(app_context,actionTable,
		    sizeof(actionTable)/sizeof(XtActionsRec));
    x11_misc_init(dpy);
    XmAddWMProtocolCallback(app_shell,WM_DELETE_WINDOW,ExitCB,NULL);
    if (debug)
	fprintf(stderr,"main: dga extention...\n");
    xfree_dga_init(dpy);
    if (debug)
	fprintf(stderr,"main: xinerama extention...\n");
    xfree_xinerama_init(dpy);
#ifdef HAVE_LIBXV
    if (debug)
	fprintf(stderr,"main: xvideo extention [video]...\n");
    if (args.xv_video)
	xv_video_init(args.xv_port,0);
    if (debug)
	fprintf(stderr,"main: xvideo extention [image]...\n");
    if (args.xv_image)
	xv_image_init(dpy);
#endif

    /* set hooks (command.c) */
    update_title        = new_title;
    display_message     = new_message;
#if TT
    vtx_message         = display_vtx;
#endif
#ifdef HAVE_ZVBI
    vtx_subtitle        = display_subtitle;
#endif
    set_capture_hook    = do_capture;
    fullscreen_hook     = do_motif_fullscreen;
    attr_notify         = new_attr;
    volume_notify       = new_volume;
    freqtab_notify      = new_freqtab;
    setstation_notify   = new_channel;
    movie_hook          = do_movie_record;
    rec_status          = do_rec_status;
    exit_hook           = do_exit;
    capture_get_hook    = video_gd_suspend;
    capture_rel_hook    = video_gd_restart;
    channel_switch_hook = pixit;

    if (debug)
	fprintf(stderr,"main: init main window...\n");
    tv = video_init(app_shell,&vinfo,xmPrimitiveWidgetClass,
		    args.bpp,args.gl);
    XtAddEventHandler(XtParent(tv),StructureNotifyMask, True,
		      resize_event, NULL);
    if (debug)
	fprintf(stderr,"main: install signal handlers...\n");
    xt_siginit();
    if (NULL == drv) {
	if (debug)
	    fprintf(stderr,"main: open grabber device...\n");
	grabber_init();
    }

    /* create windows */
    XSetIOErrorHandler(x11_ctrl_alt_backspace);
    if (debug)
	fprintf(stderr,"main: checking wm...\n");
    wm_detect(dpy);
    if (debug)
	fprintf(stderr,"main: creating windows ...\n");
    create_onscreen(xmLabelWidgetClass);
    create_vtx();
    create_strwin();
    stderr_init();

    /* read config file + related settings */
    if (debug)
	fprintf(stderr,"main: init frequency tables ...\n");
    freq_init();
    if (args.readconfig) {
	if (debug)
	    fprintf(stderr,"main: read config file ...\n");
	read_config(args.conffile ? args.conffile : NULL, &argc, argv);
    }
    if (0 != strlen(mixerdev)) {
	struct ng_attribute *attr;
	if (debug)
	    fprintf(stderr,"main: open mixer device...\n");
	if (NULL != (attr = ng_mix_init(mixerdev,mixerctl)))
	    add_attrs(attr);
    }

    create_control();
    create_prop();
    create_pref();
    create_levels();
    create_filter_prop();

    init_movie_menus();
    create_scale();
    create_attr_widgets();
    INIT_LIST_HEAD(&ipc_selections);

    xt_vm_randr_input_init(dpy);
    
    if (debug)
	fprintf(stderr,"main: mapping main window ...\n");
    XtRealizeWidget(app_shell);
    create_pointers(app_shell);
    create_bitmaps(app_shell);
    XDefineCursor(dpy, XtWindow(app_shell), left_ptr);
    if (f_drv & CAN_CAPTURE)
	XtAddCallback(tv,XmNconvertCallback,ipc_convert,NULL);

    XtVaSetValues(app_shell,
		  XtNwidthInc,  WIDTH_INC,
		  XtNheightInc, HEIGHT_INC,
		  XtNminWidth,  WIDTH_INC,
		  XtNminHeight, HEIGHT_INC,
		  NULL);

    /* mouse pointer magic */
    XtAddEventHandler(tv, PointerMotionMask, True, mouse_event, NULL);
    mouse_event(tv,NULL,NULL,NULL);

    /* init hw */
    if (debug)
	fprintf(stderr,"main: initialize hardware ...\n");
    attr_init();
    audio_on();
    audio_init();

    /* build channel list */
    if (args.readconfig) {
	if (debug)
	    fprintf(stderr,"main: parse channels from config file ...\n");
	parse_config();
    }
    channel_menu();

    xt_handle_pending(dpy);
    init_overlay();

    set_property(0,NULL,NULL);
    if (optind+1 == argc) {
	do_va_cmd(2,"setstation",argv[optind]);
    } else {
	if ((f_drv & CAN_TUNE) && 0 != (freq = drv->getfreq(h_drv))) {
	    for (i = 0; i < chancount; i++)
		if (chanlist[i].freq == freq*1000/16) {
		    do_va_cmd(2,"setchannel",chanlist[i].name);
		    break;
		}
	}
	if (-1 == cur_channel) {
	    if (count > 0) {
		if (debug)
		    fprintf(stderr,"main: tuning first station\n");
		do_va_cmd(2,"setstation","0");
	    } else {
		if (debug)
		    fprintf(stderr,"main: setting defaults\n");
		set_defaults();
	    }
	} else {
	    if (debug)
		fprintf(stderr,"main: known station tuned, not changing\n");
	}
    }
    XtAddEventHandler(tv,ExposureMask, True, tv_expose_event, NULL);

    if (args.fullscreen) {
	do_motif_fullscreen();
    } else {
	XtAppAddWorkProc(app_context,MyResize,NULL);
    }

    xt_main_loop();
    return 0;
}
