1 /*
2     channel for Bt848 frame grabber driver
3 
4     Copyright (C) 1996,97 Marcus Metzler (mocm@thp.uni-koeln.de)
5 	      (c) 1998-2003 Gerd Knorr <kraxel@bytesex.org>
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21 
22 #include "config.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <math.h>
30 #include <pthread.h>
31 
32 #ifndef NO_X11
33 # include <X11/Xlib.h>
34 # include <X11/Intrinsic.h>
35 # include <X11/StringDefs.h>
36 # include <X11/Xaw/XawInit.h>
37 # include <X11/Xaw/Command.h>
38 # include <X11/Xaw/Paned.h>
39 #endif
40 
41 #include "grab-ng.h"
42 #include "channel.h"
43 #include "commands.h"
44 #include "frequencies.h"
45 #include "sound.h"
46 #include "parseconfig.h"
47 #include "event.h"
48 
49 /* ----------------------------------------------------------------------- */
50 /* misc common stuff, not only channel related                             */
51 
52 struct CHANNEL defaults = {
53     .name =     "defaults",
54     .group =    "main",
55     .capture =  CAPTURE_ON,
56     .channel =  -1,
57     .audio =    -1,
58     .color =    -1,
59     .bright =   -1,
60     .hue =      -1,
61     .contrast = -1,
62 };
63 
64 struct CHANNEL  **channels  = NULL;
65 int             count       = 0;
66 int             alloc_count = 0;
67 
68 int last_sender = -1, cur_sender = -1, cur_channel = -1, cur_fine = 0;
69 int cur_freq;
70 struct ng_filter *cur_filter;
71 
72 int cur_capture = CAPTURE_OFF;
73 int have_config;
74 int keypad_ntsc = 0;
75 int keypad_partial = 1;
76 int use_wm_fullscreen = 1;
77 int use_osd = 1;
78 int osd_x = 30;
79 int osd_y = 20;
80 int fs_width,fs_height,fs_xoff,fs_yoff;
81 int pix_width=128, pix_height=96, pix_cols=1;
82 
83 char *mov_driver = NULL;
84 char *mov_video  = NULL;
85 char *mov_fps    = NULL;
86 char *mov_audio  = NULL;
87 char *mov_rate   = NULL;
88 
89 #ifndef NO_X11
90 extern Widget chan_box, chan_viewport, tv, opt_paned, launch_paned;
91 #endif
92 
93 static char *mixer = NULL;
94 char mixerdev[32],mixerctl[16];
95 char *midi = NULL;
96 
97 struct LAUNCH *launch = NULL;
98 int nlaunch           = 0;
99 
100 /* ----------------------------------------------------------------------- */
101 
lookup_channel(char * channel)102 int lookup_channel(char *channel)
103 {
104 #if 0
105     /* Hmm, why the heck that used to be that complex?
106      * Any good reason I forgot ? */
107     int    i,nr1,nr2;
108     char   tag1[5],tag2[5];
109 
110     if (NULL == channel)
111 	return -1;
112 
113     if (isdigit(channel[0])) {
114 	tag1[0] = 0;
115 	nr1  = atoi(channel);
116     } else {
117 	sscanf(channel,"%4[A-Za-z]%d",tag1,&nr1);
118     }
119 
120     for (i = 0; i < chancount; i++) {
121 	if (isdigit(chanlist[i].name[0])) {
122 	    tag2[0] = 0;
123 	    nr2  = atoi(chanlist[i].name);
124 	} else {
125 	    sscanf(chanlist[i].name,"%4[A-Za-z]%d",tag2,&nr2);
126 	}
127 	if (tag1[0] && tag2[0])
128 	    if (nr1 == nr2 && 0 == strcmp(tag1,tag2))
129 		break;
130 	if (!tag1[0] && !tag2[0])
131 	    if (nr1 == nr2)
132 		break;
133     }
134     if (i == chancount)
135 	return -1;
136 
137     return i;
138 #else
139     int i;
140 
141     if (NULL == channel)
142 	return -1;
143     for (i = 0; i < chancount; i++)
144 	if (0 == strcasecmp(chanlist[i].name,channel))
145 	    break;
146     if (i == chancount)
147 	return -1;
148     return i;
149 #endif
150 }
151 
get_freq(int i)152 int get_freq(int i)
153 {
154     if (i < 0 || i >= chancount)
155 	return -1;
156     return chanlist[i].freq*16/1000;
157 }
158 
cf2freq(char * name,int fine)159 int  cf2freq(char *name, int fine)
160 {
161     int i;
162 
163     if (-1 == (i = lookup_channel(name)))
164 	return -1;
165     return get_freq(i)+fine;
166 }
167 
168 /* ----------------------------------------------------------------------- */
169 
170 struct STRTAB captab[] = {
171     {  CAPTURE_ON,          "on"          },
172     {  CAPTURE_ON,          "yes"         },
173     {  CAPTURE_ON,          "true"        },
174     {  CAPTURE_OFF,         "off"         },
175     {  CAPTURE_OFF,         "no"          },
176     {  CAPTURE_OFF,         "false"       },
177     {  CAPTURE_OVERLAY,     "over"        },
178     {  CAPTURE_OVERLAY,     "overlay"     },
179     {  CAPTURE_GRABDISPLAY, "grab"        },
180     {  CAPTURE_GRABDISPLAY, "grabdisplay" },
181     {  -1, NULL,     },
182 };
183 
184 /* just malloc memory for a new channel ... */
185 struct CHANNEL*
add_channel(char * name)186 add_channel(char *name)
187 {
188     struct CHANNEL *channel;
189 
190     if (alloc_count == count) {
191 	alloc_count += 16;
192 	if (alloc_count == 16)
193 	    channels = malloc(sizeof(struct CHANNEL*)*alloc_count);
194 	else
195 	    channels = realloc(channels,sizeof(struct CHANNEL*)*alloc_count);
196     }
197     channel = channels[count++] = malloc(sizeof(struct CHANNEL));
198     memcpy(channel,&defaults,sizeof(struct CHANNEL));
199     channel->name = strdup(name);
200     return channel;
201 }
202 
203 #ifndef NO_X11
204 
205 #define PANED_FIX               \
206 	XtNallowResize, False,  \
207 	XtNshowGrip,    False,  \
208 	XtNskipAdjust,  True
209 
hotkey_channel(struct CHANNEL * channel)210 void hotkey_channel(struct CHANNEL *channel)
211 {
212     char str[100],key[32],ctrl[16];
213 
214     if (NULL == channel->key)
215 	return;
216     if (2 == sscanf(channel->key,"%15[A-Za-z0-9_]+%31[A-Za-z0-9_]",
217 		    ctrl,key))
218 	sprintf(str,"%s<Key>%s: Command(setstation,\"%s\")",
219 		ctrl,key,channel->name);
220     else
221 	sprintf(str,"<Key>%s: Command(setstation,\"%s\")",
222 		channel->key,channel->name);
223     XtOverrideTranslations(tv,XtParseTranslationTable(str));
224     XtOverrideTranslations(opt_paned,XtParseTranslationTable(str));
225     XtOverrideTranslations(chan_viewport,XtParseTranslationTable(str));
226 }
227 
228 static void
launch_cb(Widget widget,XtPointer clientdata,XtPointer call_data)229 launch_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
230 {
231     char *argv[2];
232 
233     argv[0] = (char*)clientdata;
234     argv[1] = NULL;
235     XtCallActionProc(widget,"Launch",NULL,argv,1);
236 }
237 
238 static void
hotkey_launch(struct LAUNCH * launch)239 hotkey_launch(struct LAUNCH *launch)
240 {
241     Widget c;
242     char str[100],key[32],ctrl[16],label[64];
243 
244     if (NULL == launch->key)
245 	return;
246     if (2 == sscanf(launch->key,"%15[A-Za-z0-9_]+%31[A-Za-z0-9_]",
247 		    ctrl,key))
248 	sprintf(str,"%s<Key>%s: Launch(\"%s\")",ctrl,key,launch->name);
249     else
250 	sprintf(str,"<Key>%s: Launch(\"%s\")",launch->key,launch->name);
251     XtOverrideTranslations(tv,XtParseTranslationTable(str));
252     XtOverrideTranslations(opt_paned,XtParseTranslationTable(str));
253     XtOverrideTranslations(chan_viewport,XtParseTranslationTable(str));
254 
255     sprintf(label,"%-20s %s",launch->name,launch->key);
256     c = XtVaCreateManagedWidget(launch->name, commandWidgetClass,
257 				launch_paned,
258 				PANED_FIX,
259 				XtNlabel,label,
260 				NULL);
261     XtAddCallback(c,XtNcallback,launch_cb,(XtPointer)(launch->name));
262 }
263 
264 static void
button_cb(Widget widget,XtPointer clientdata,XtPointer call_data)265 button_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
266 {
267     struct CHANNEL *channel = clientdata;
268     do_va_cmd(2,"setstation",channel->name);
269 }
270 
271 /* ... and initalize later */
configure_channel(struct CHANNEL * channel)272 void configure_channel(struct CHANNEL *channel)
273 {
274     channel->button =
275 	XtVaCreateManagedWidget(channel->name,
276 				commandWidgetClass, chan_box,
277 				XtNwidth,pix_width,
278 				XtNheight,pix_height,
279 				NULL);
280     XtAddCallback(channel->button,XtNcallback,button_cb,(XtPointer*)channel);
281     hotkey_channel(channel);
282 }
283 #endif
284 
285 /* delete channel */
286 void
del_channel(int i)287 del_channel(int i)
288 {
289     free(channels[i]->name);
290     if (channels[i]->key)
291 	free(channels[i]->key);
292     free(channels[i]);
293     count--;
294     if (i < count)
295 	memmove(channels+i,channels+i+1,(count-i)*sizeof(struct CHANNEL*));
296 }
297 
298 void
calc_frequencies()299 calc_frequencies()
300 {
301     int i;
302 
303     for (i = 0; i < count; i++) {
304 	if (NULL == channels[i]->cname)
305 	    continue;
306 	channels[i]->channel = lookup_channel(channels[i]->cname);
307 	if (-1 == channels[i]->channel)
308 	    channels[i]->freq = -1;
309 	else
310 	    channels[i]->freq = get_freq(channels[i]->channel)
311 		+ channels[i]->fine;
312     }
313 }
314 
315 /* ----------------------------------------------------------------------- */
316 
317 static void
init_channel(char * name,struct CHANNEL * c)318 init_channel(char *name, struct CHANNEL *c)
319 {
320     struct ng_attribute *attr;
321     char *val; int n,i;
322 
323     if (NULL != (val = cfg_get_str(name,"capture"))) {
324 	if (-1 != (i = str_to_int(val,captab)))
325 	    c->capture = i;
326 	else
327 	    fprintf(stderr,"config: invalid value for capture: %s\n",val);
328     }
329     if (NULL != (attr = ng_attr_byid(attrs,ATTR_ID_INPUT)) &&
330 	(NULL != (val = cfg_get_str(name,"input")) ||
331 	 NULL != (val = cfg_get_str(name,"source")))) { /* obsolete */
332 	if (-1 != (i = ng_attr_getint(attr,val)))
333 	    c->input = i;
334 	else {
335 	    fprintf(stderr,"config: invalid value for input: %s\n",val);
336 	    ng_attr_listchoices(attr);
337 	}
338     }
339     if (NULL != (attr = ng_attr_byid(attrs,ATTR_ID_NORM)) &&
340 	NULL != (val = cfg_get_str(name,"norm"))) {
341 	if (-1 != (i = ng_attr_getint(attr,val)))
342 	    c->norm = i;
343 	else {
344 	    fprintf(stderr,"config: invalid value for norm: %s\n",val);
345 	    ng_attr_listchoices(attr);
346 	}
347     }
348     if (NULL != (attr = ng_attr_byid(attrs,ATTR_ID_AUDIO_MODE)) &&
349 	NULL != (val = cfg_get_str(name,"audio"))) {
350 	if (-1 != (i = ng_attr_getint(attr,val)))
351 	    c->audio = i;
352 	else {
353 	    fprintf(stderr,"config: invalid value for audio: %s\n",val);
354 	    ng_attr_listchoices(attr);
355 	}
356     }
357 
358     if (NULL != (val = cfg_get_str(name,"channel")))
359 	c->cname   = strdup(val);
360     if (NULL != (val = cfg_get_str(name,"freq")))
361 	c->freq   = (int)(atof(val)*16);
362     if (0 != (n = cfg_get_signed_int(name,"fine")))
363 	c->fine = n;
364 
365     if (NULL != (val = cfg_get_str(name,"key")))
366 	c->key  = strdup(val);
367     if (NULL != (val = cfg_get_str(name,"group")))
368 	c->group  = strdup(val);
369     if (NULL != (val = cfg_get_str(name,"midi")))
370 	c->midi = atoi(val);
371 
372     attr = ng_attr_byid(attrs,ATTR_ID_COLOR);
373     if (attr && NULL != (val = cfg_get_str(name,"color")))
374 	c->color = ng_attr_parse_int(attr,val);
375     attr = ng_attr_byid(attrs,ATTR_ID_BRIGHT);
376     if (attr && NULL != (val = cfg_get_str(name,"bright")))
377 	c->bright = ng_attr_parse_int(attr,val);
378     attr = ng_attr_byid(attrs,ATTR_ID_HUE);
379     if (attr && NULL != (val = cfg_get_str(name,"hue")))
380 	c->hue = ng_attr_parse_int(attr,val);
381     attr = ng_attr_byid(attrs,ATTR_ID_CONTRAST);
382     if (attr && NULL != (val = cfg_get_str(name,"contrast")))
383 	c->contrast = ng_attr_parse_int(attr,val);
384 }
385 
386 void
read_config(char * conffile,int * argc,char ** argv)387 read_config(char *conffile, int *argc, char **argv)
388 {
389     struct list_head *item;
390     char filename[100];
391     char *val;
392     int  i;
393 
394     if (conffile) {
395 	if (0 == cfg_parse_file(conffile))
396 	    have_config = 1;
397     } else {
398 	sprintf(filename,"%.*s/%s",(int)sizeof(filename)-8,
399 		getenv("HOME"),".xawtv");
400 	if (0 == cfg_parse_file(CONFIGFILE))
401 	    have_config = 1;
402 	if (0 == cfg_parse_file(filename))
403 	    have_config = 1;
404     }
405     if (argc)
406 	cfg_parse_options(argc,argv);
407 
408     /* misc global settings */
409     if (NULL != (val = cfg_get_str("global","mixer"))) {
410 	mixer = strdup(val);
411 	if (2 != sscanf(mixer,"%31[^:]:%15s",mixerdev,mixerctl)) {
412 	    strcpy(mixerdev,ng_dev.mixer);
413 	    strncpy(mixerctl,val,15);
414 	    mixerctl[15] = 0;
415 	}
416     }
417 
418     if (NULL != (val = cfg_get_str("global","midi")))
419 	midi = strdup(val);
420 
421     if (NULL != (val = cfg_get_str("global","freqtab"))) {
422 	for (i = 0; chanlists[i].name != NULL; i++)
423 	    if (0 == strcasecmp(val,chanlists[i].name))
424 		break;
425 	if (chanlists[i].name != NULL) {
426 	    freq_newtab(i);
427 	} else
428 	    fprintf(stderr,"invalid value for freqtab: %s\n",val);
429     }
430 
431     if (NULL != (val = cfg_get_str("global","fullscreen"))) {
432 	if (2 != sscanf(val,"%d x %d",&fs_width,&fs_height)) {
433 	    fprintf(stderr,"invalid value for fullscreen: %s\n",val);
434 	    fs_width = fs_height = 0;
435 	}
436     }
437 
438     if (NULL != (val = cfg_get_str("global","pixsize"))) {
439 	if (2 != sscanf(val,"%d x %d",&pix_width,&pix_height)) {
440 	    fprintf(stderr,"invalid value for pixsize: %s\n",val);
441 	    pix_width = 128;
442 	    pix_height = 96;
443 	}
444     }
445     if (-1 != (i = cfg_get_int("global","pixcols")))
446 	pix_cols = i;
447 
448     if (NULL != (val = cfg_get_str("global","wm-off-by"))) {
449 	if (2 != sscanf(val,"%d %d",&fs_xoff,&fs_yoff)) {
450 	    fprintf(stderr,"invalid value for wm-off-by: %s\n",val);
451 	    fs_xoff = fs_yoff = 0;
452 	}
453     }
454     if (NULL != (val = cfg_get_str("global","ratio"))) {
455 	if (2 != sscanf(val,"%d:%d",&ng_ratio_x,&ng_ratio_y)) {
456 	    fprintf(stderr,"invalid value for ratio: %s\n",val);
457 	    ng_ratio_x = ng_ratio_y = 0;
458 	}
459     }
460 
461     if (-1 != (i = cfg_get_int("global","jpeg-quality")))
462 	ng_jpeg_quality = i;
463 
464     if (NULL != (val = cfg_get_str("global","keypad-ntsc")))
465 	if (-1 != (i = str_to_int(val,booltab)))
466 	    keypad_ntsc = i;
467     if (NULL != (val = cfg_get_str("global","keypad-partial")))
468 	if (-1 != (i = str_to_int(val,booltab)))
469 	    keypad_partial = i;
470     if (NULL != (val = cfg_get_str("global","osd")))
471 	if (-1 != (i = str_to_int(val,booltab)))
472 	    use_osd = i;
473     if (NULL != (val = cfg_get_str("global","osd-position")))
474 	if (2 != sscanf(val,"%d , %d",&osd_x,&osd_y))
475 	    fprintf(stderr,"invalid values for osd-position: %s\n",val);
476     if (NULL != (val = cfg_get_str("global","use-wm-fullscreen")))
477 	if (-1 != (i = str_to_int(val,booltab)))
478 	    use_wm_fullscreen = i;
479 
480     if (NULL != (val = cfg_get_str("global","mov-driver")))
481 	mov_driver = val;
482     if (NULL != (val = cfg_get_str("global","mov-video")))
483 	mov_video = val;
484     if (NULL != (val = cfg_get_str("global","mov-fps")))
485 	mov_fps = val;
486     if (NULL != (val = cfg_get_str("global","mov-audio")))
487 	mov_audio = val;
488     if (NULL != (val = cfg_get_str("global","mov-rate")))
489 	mov_rate = val;
490 
491     if (NULL != (val = cfg_get_str("global","filter"))) {
492 	list_for_each(item,&ng_filters) {
493 	    struct ng_filter *f = list_entry(item, struct ng_filter, list);
494 	    if (0 == strcasecmp(f->name, val))
495 		cur_filter = f;
496 	}
497     }
498 }
499 
500 void
parse_config(int parse_channels)501 parse_config(int parse_channels)
502 {
503     char key[16], cmdline[128];
504     char **list,*val;
505 #ifndef NO_X11
506     int i;
507 #endif
508 
509     /* launch */
510     list = cfg_list_entries("launch");
511     if (NULL != list) {
512 	for (; *list != NULL; list++) {
513 	    if (NULL != (val = cfg_get_str("launch",*list)) &&
514 		2 == sscanf(val,"%15[^,], %127[^\n]",
515 			    key,cmdline)) {
516 		launch = realloc(launch,sizeof(struct LAUNCH)*(nlaunch+1));
517 		launch[nlaunch].name    = strdup(*list);
518 		launch[nlaunch].key     = strdup(key);
519 		launch[nlaunch].cmdline = strdup(cmdline);
520 #ifndef NO_X11
521 		hotkey_launch(launch+nlaunch);
522 #endif
523 		nlaunch++;
524 	    } else {
525 		fprintf(stderr,"invalid value in section [launch]: %s\n",val);
526 	    }
527 	}
528     }
529 
530     /* events */
531     event_readconfig();
532 
533     if (!parse_channels)
534         return;
535 
536     /* channels */
537     init_channel("defaults",&defaults);
538     for (list = cfg_list_sections(); *list != NULL; list++) {
539 	if (0 == strcmp(*list,"defaults")) continue;
540 	if (0 == strcmp(*list,"global"))   continue;
541 	if (0 == strcmp(*list,"launch"))   continue;
542 	if (0 == strcmp(*list,"eventmap")) continue;
543 	init_channel(*list,add_channel(*list));
544     }
545 
546     /* calculate channel frequencies */
547     defaults.channel = lookup_channel(defaults.cname);
548     defaults.freq    = get_freq(defaults.channel) + defaults.fine;
549     calc_frequencies();
550 #ifndef NO_X11
551     for (i = 0; i < count; i++)
552 	configure_channel(channels[i]);
553 #endif
554 }
555 
556 /* ----------------------------------------------------------------------- */
557 
558 void
save_config()559 save_config()
560 {
561     struct ng_attribute *attr;
562     char filename1[100], filename2[100];
563     FILE *fp;
564     int i;
565 
566     sprintf(filename1,"%s/%s",getenv("HOME"),".xawtv");
567     sprintf(filename2,"%s/%s",getenv("HOME"),".xawtv~");
568 
569     /* delete old backup */
570     unlink(filename2);
571 
572     /* current becomes backup */
573     if (0 == link(filename1,filename2))
574 	unlink(filename1);
575 
576     /* write new one... */
577     fp = fopen(filename1,"w");
578     if (NULL == fp) {
579 	fprintf(stderr,"can't open config file %s\n",filename1);
580 	return;
581     }
582 
583     fprintf(fp,"[global]\n");
584     if (fs_width && fs_height)
585 	fprintf(fp,"fullscreen = %d x %d\n",fs_width,fs_height);
586     if (fs_xoff || fs_yoff)
587 	fprintf(fp,"wm-off-by = %+d%+d\n",fs_xoff,fs_yoff);
588     if (ng_ratio_x || ng_ratio_y)
589 	fprintf(fp,"ratio = %d:%d\n",ng_ratio_x,ng_ratio_y);
590     fprintf(fp,"freqtab = %s\n",chanlists[chantab].name);
591     fprintf(fp,"pixsize = %d x %d\n",pix_width,pix_height);
592     fprintf(fp,"pixcols = %d\n",pix_cols);
593     fprintf(fp,"jpeg-quality = %d\n",ng_jpeg_quality);
594     fprintf(fp,"keypad-ntsc = %s\n",int_to_str(keypad_ntsc,booltab));
595     fprintf(fp,"keypad-partial = %s\n",int_to_str(keypad_partial,booltab));
596     fprintf(fp,"osd = %s\n",int_to_str(use_osd,booltab));
597     fprintf(fp,"osd-position = %d , %d\n",osd_x,osd_y);
598     fprintf(fp,"use-wm-fullscreen = %s\n",
599 	    int_to_str(use_wm_fullscreen,booltab));
600     if (mixer)
601 	fprintf(fp,"mixer = %s\n",mixer);
602     if (midi)
603 	fprintf(fp,"midi = %s\n",midi);
604 
605     if (mov_driver)
606 	fprintf(fp,"mov-driver = %s\n",mov_driver);
607     if (mov_video)
608 	fprintf(fp,"mov-video = %s\n",mov_video);
609     if (mov_fps)
610 	fprintf(fp,"mov-fps = %s\n",mov_fps);
611     if (mov_audio)
612 	fprintf(fp,"mov-audio = %s\n",mov_audio);
613     if (mov_rate)
614 	fprintf(fp,"mov-rate = %s\n",mov_rate);
615 
616     fprintf(fp,"\n");
617 
618     if (nlaunch > 0) {
619 	fprintf(fp,"[launch]\n");
620 	for (i = 0; i < nlaunch; i++) {
621 	    fprintf(fp,"%s = %s, %s\n",
622 		    launch[i].name,launch[i].key,launch[i].cmdline);
623 	}
624 	fprintf(fp,"\n");
625     }
626 
627     /* events */
628     event_writeconfig(fp);
629 
630     /* write help */
631     fprintf(fp,"# [Station name]\n");
632     fprintf(fp,"# capture = overlay | grabdisplay | on | off\n");
633     fprintf(fp,"# input = Television | Composite1 | S-Video | ...\n");
634     fprintf(fp,"# norm = PAL | NTSC | SECAM | ... \n");
635     fprintf(fp,"# channel = #\n");
636     fprintf(fp,"# fine = # (-128..+127)\n");
637     fprintf(fp,"# key = keysym | modifier+keysym\n");
638     fprintf(fp,"# color = #\n");
639     fprintf(fp,"# bright = #\n");
640     fprintf(fp,"# hue = #\n");
641     fprintf(fp,"# contrast = #\n");
642     fprintf(fp,"\n");
643 
644     /* write defaults */
645     fprintf(fp,"[defaults]\n");
646     fprintf(fp,"group = %s\n",defaults.group);
647 
648     fprintf(fp,"norm = %s\n",
649 	    ng_attr_getstr(ng_attr_byid(attrs,ATTR_ID_NORM),
650 			   cur_attrs[ATTR_ID_NORM]));
651     fprintf(fp,"input = %s\n",
652 	    ng_attr_getstr(ng_attr_byid(attrs,ATTR_ID_INPUT),
653 			   cur_attrs[ATTR_ID_INPUT]));
654     fprintf(fp,"capture = %s\n",int_to_str(cur_capture,captab));
655 
656     attr = ng_attr_byid(attrs,ATTR_ID_COLOR);
657     if (attr && attr->defval != cur_attrs[ATTR_ID_COLOR])
658 	fprintf(fp,"color = %d%%\n",
659 		ng_attr_int2percent(attr,cur_attrs[ATTR_ID_COLOR]));
660     attr = ng_attr_byid(attrs,ATTR_ID_BRIGHT);
661     if (attr && attr->defval != cur_attrs[ATTR_ID_BRIGHT])
662 	fprintf(fp,"bright = %d%%\n",
663 		ng_attr_int2percent(attr,cur_attrs[ATTR_ID_BRIGHT]));
664     attr = ng_attr_byid(attrs,ATTR_ID_HUE);
665     if (attr && attr->defval != cur_attrs[ATTR_ID_HUE])
666 	fprintf(fp,"hue = %d%%\n",
667 		ng_attr_int2percent(attr,cur_attrs[ATTR_ID_HUE]));
668     attr = ng_attr_byid(attrs,ATTR_ID_CONTRAST);
669     if (attr && attr->defval != cur_attrs[ATTR_ID_CONTRAST])
670 	fprintf(fp,"contrast = %d%%\n",
671 		ng_attr_int2percent(attr,cur_attrs[ATTR_ID_CONTRAST]));
672     fprintf(fp,"\n");
673 
674     /* write channels */
675     for (i = 0; i < count; i++) {
676 	fprintf(fp,"[%s]\n",channels[i]->name);
677 	if (NULL != channels[i]->cname) {
678 	    fprintf(fp,"channel = %s\n",chanlist[channels[i]->channel].name);
679 	    if (0 != channels[i]->fine)
680 		fprintf(fp,"fine = %+d\n", channels[i]->fine);
681 	} else {
682 	    fprintf(fp,"freq = %.2f\n",(float)(channels[i]->freq)/16);
683 	}
684 
685 	if ( channels[i]->norm != cur_attrs[ATTR_ID_NORM])
686 	    fprintf(fp,"norm = %s\n",
687 		    ng_attr_getstr(ng_attr_byid(attrs,ATTR_ID_NORM),
688 				   channels[i]->norm));
689 	if (channels[i]->input != cur_attrs[ATTR_ID_INPUT])
690 	    fprintf(fp,"input = %s\n",
691 		    ng_attr_getstr(ng_attr_byid(attrs,ATTR_ID_INPUT),
692 				   channels[i]->input));
693 
694 	if (channels[i]->key != NULL)
695 	    fprintf(fp,"key = %s\n",channels[i]->key);
696 	if (0 != strcmp(channels[i]->group,defaults.group))
697 	    fprintf(fp,"group = %s\n",channels[i]->group);
698 	if (channels[i]->midi != 0)
699 	    fprintf(fp,"midi = %d\n",channels[i]->midi);
700 	if (channels[i]->capture != cur_capture)
701 	    fprintf(fp,"capture = %s\n",
702 		    int_to_str(channels[i]->capture,captab));
703 
704 	attr = ng_attr_byid(attrs,ATTR_ID_COLOR);
705 	if (attr && cur_attrs[ATTR_ID_COLOR] != channels[i]->color)
706 	    fprintf(fp,"color = %d%%\n",
707 		    ng_attr_int2percent(attr,channels[i]->color));
708 	attr = ng_attr_byid(attrs,ATTR_ID_BRIGHT);
709 	if (attr && cur_attrs[ATTR_ID_BRIGHT] != channels[i]->bright)
710 	    fprintf(fp,"bright = %d%%\n",
711 		    ng_attr_int2percent(attr,channels[i]->bright));
712 	attr = ng_attr_byid(attrs,ATTR_ID_HUE);
713 	if (attr && cur_attrs[ATTR_ID_HUE] != channels[i]->hue)
714 	    fprintf(fp,"hue = %d%%\n",
715 		    ng_attr_int2percent(attr,channels[i]->hue));
716 	attr = ng_attr_byid(attrs,ATTR_ID_CONTRAST);
717 	if (attr && cur_attrs[ATTR_ID_CONTRAST] != channels[i]->contrast)
718 	    fprintf(fp,"contrast = %d%%\n",
719 		    ng_attr_int2percent(attr,channels[i]->contrast));
720 
721 	fprintf(fp,"\n");
722     }
723     fclose(fp);
724 }
725 
726 /* ----------------------------------------------------------------------- */
727 
728 struct STRTAB booltab[] = {
729     {  0, "no" },
730     {  0, "false" },
731     {  0, "off" },
732     {  1, "yes" },
733     {  1, "true" },
734     {  1, "on" },
735     { -1, NULL }
736 };
737 
738 int
str_to_int(char * str,struct STRTAB * tab)739 str_to_int(char *str, struct STRTAB *tab)
740 {
741     int i;
742 
743     if (str[0] >= '0' && str[0] <= '9')
744 	return atoi(str);
745     for (i = 0; tab[i].str != NULL; i++)
746 	if (0 == strcasecmp(str,tab[i].str))
747 	    return(tab[i].nr);
748     return -1;
749 }
750 
751 const char*
int_to_str(int n,struct STRTAB * tab)752 int_to_str(int n, struct STRTAB *tab)
753 {
754     int i;
755 
756     for (i = 0; tab[i].str != NULL; i++)
757 	if (tab[i].nr == n)
758 	    return tab[i].str;
759     return NULL;
760 }
761