aboutsummaryrefslogtreecommitdiffstats
path: root/channels/console_gui.c
blob: 617c1f91f3eec1ddfac7d2be022af59a4da4afcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
/*
 * GUI for console video.
 * The routines here are in charge of loading the keypad and handling events.
 * $Revision$
 */

#include "asterisk.h"
#include "console_video.h"
#include "asterisk/lock.h"
#include "asterisk/frame.h"
#include "asterisk/utils.h"	/* ast_calloc and ast_realloc */
#include <math.h>		/* sqrt */

enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE };
struct keypad_entry {
        int c;  /* corresponding character */
        int x0, y0, x1, y1, h;  /* arguments */
        enum kp_type type;
};

/* our representation of a displayed window. SDL can only do one main
 * window so we map everything within that one
 */
enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_MAX };
/* our representation of a displayed window. SDL can only do one main
 * window so we map everything within that one
 */
struct display_window   {   
	SDL_Overlay	*bmp;
	SDL_Rect	rect;	/* location of the window */
};
#define GUI_BUFFER_LEN 256			/* buffer lenght used for input buffers */

struct keypad_entry;	/* defined in console_gui.c */

/*! \brief info related to the gui: button status, mouse coords, etc. */
struct gui_info {
	char			inbuf[GUI_BUFFER_LEN];	/* buffer for to-dial buffer */
	int			inbuf_pos;	/* next free position in inbuf */
	char			msgbuf[GUI_BUFFER_LEN];	/* buffer for text-message buffer */
	int			msgbuf_pos;	/* next free position in msgbuf */
	int			text_mode;	/* switch to-dial and text-message mode */
	int			drag_mode;	/* switch phone and drag-source mode */
	int			x_drag;		/* x coordinate where the drag starts */
	int			y_drag;		/* y coordinate where the drag starts */
#ifdef HAVE_SDL_TTF
	TTF_Font                *font;          /* font to be used */ 
#endif
	/* support for display. */
	SDL_Surface             *screen;	/* the main window */

	int			outfd;		/* fd for output */
	SDL_Surface		*keypad;	/* the pixmap for the keypad */
	int kp_size, kp_used;
	struct keypad_entry *kp;

	struct display_window   win[WIN_MAX];
};

static void cleanup_sdl(struct video_desc *env)  
{
	int i;
	struct gui_info *gui = env->gui;

    if (gui) {
#ifdef HAVE_SDL_TTF
	/* unload font file */ 
	if (gui->font) {
		TTF_CloseFont(gui->font);
		gui->font = NULL; 
	}

	/* uninitialize SDL_ttf library */
	if ( TTF_WasInit() )
		TTF_Quit();
#endif
	if (gui->keypad)
		SDL_FreeSurface(gui->keypad);
	gui->keypad = NULL;

	/* uninitialize the SDL environment */
	for (i = 0; i < WIN_MAX; i++) {
		if (gui->win[i].bmp)
			SDL_FreeYUVOverlay(gui->win[i].bmp);
	}
	bzero(gui->win, sizeof(gui->win));
	/* XXX free the keys entries */
	gui->screen = NULL; /* XXX check reference */
	ast_mutex_destroy(&(env->in.dec_in_lock));
	ast_free(gui);
	env->gui = NULL;
    }
	SDL_Quit();
}

/*
 * Display video frames (from local or remote stream) using the SDL library.
 * - Set the video mode to use the resolution specified by the codec context
 * - Create a YUV Overlay to copy the frame into it;
 * - After the frame is copied into the overlay, display it
 *
 * The size is taken from the configuration.
 *
 * 'out' is 0 for remote video, 1 for the local video
 */
static void show_frame(struct video_desc *env, int out)
{
	AVPicture *p_in, p_out;
	struct fbuf_t *b_in, *b_out;
	SDL_Overlay *bmp;
	struct gui_info *gui = env->gui;

	if (!gui)
		return;

	if (out == WIN_LOCAL) {	/* webcam/x11 to sdl */
		b_in = &env->out.enc_in;
		b_out = &env->out.loc_dpy;
		p_in = NULL;
	} else {
		/* copy input format from the decoding context */
		AVCodecContext *c = env->in.dec_ctx;
		b_in = &env->in.dec_out;
                b_in->pix_fmt = c->pix_fmt;
                b_in->w = c->width;
                b_in->h = c->height;

		b_out = &env->in.rem_dpy;
		p_in = (AVPicture *)env->in.d_frame;
	}
	bmp = gui->win[out].bmp;
	SDL_LockYUVOverlay(bmp);
	/* output picture info - this is sdl, YUV420P */
	bzero(&p_out, sizeof(p_out));
	p_out.data[0] = bmp->pixels[0];
	p_out.data[1] = bmp->pixels[1];
	p_out.data[2] = bmp->pixels[2];
	p_out.linesize[0] = bmp->pitches[0];
	p_out.linesize[1] = bmp->pitches[1];
	p_out.linesize[2] = bmp->pitches[2];

	my_scale(b_in, p_in, b_out, &p_out);

	/* lock to protect access to Xlib by different threads. */
	SDL_DisplayYUVOverlay(bmp, &gui->win[out].rect);
	SDL_UnlockYUVOverlay(bmp);
}

/*
 * GUI layout, structure and management
 *

For the GUI we use SDL to create a large surface (env->screen)
containing tree sections: remote video on the left, local video
on the right, and the keypad with all controls and text windows
in the center.
The central section is built using two images: one is the skin,
the other one is a mask where the sensitive areas of the skin
are colored in different grayscale levels according to their
functions. The mapping between colors and function is defined
in the 'enum pixel_value' below.

Mouse and keyboard events are detected on the whole surface, and
handled differently according to their location, as follows:

- drag on the local video window are used to move the captured
  area (in the case of X11 grabber) or the picture-in-picture
  location (in case of camera included on the X11 grab).
- click on the keypad are mapped to the corresponding key;
- drag on some keypad areas (sliders etc.) are mapped to the
  corresponding functions;
- keystrokes are used as keypad functions, or as text input
  if we are in text-input mode.

To manage these behavior we use two status variables,
that defines if keyboard events should be redirect to dialing functions
or to write message functions, and if mouse events should be used
to implement keypad functionalities or to drag the capture device.

Configuration options control the appeareance of the gui:

    keypad = /tmp/phone.jpg		; the keypad on the screen
    keypad_font = /tmp/font.ttf		; the font to use for output

 *
 */

/* enumerate for the pixel value. 0..127 correspond to ascii chars */
enum pixel_value {
	/* answer/close functions */
	KEY_PICK_UP = 128,
	KEY_HANG_UP = 129,

	/* other functions */
	KEY_MUTE = 130,
	KEY_AUTOANSWER = 131,
	KEY_SENDVIDEO = 132,
	KEY_LOCALVIDEO = 133,
	KEY_REMOTEVIDEO = 134,
	KEY_WRITEMESSAGE = 135,
	KEY_GUI_CLOSE = 136,		/* close gui */

	/* other areas within the keypad */
	KEY_DIGIT_BACKGROUND = 255,

	/* areas outside the keypad - simulated */
	KEY_OUT_OF_KEYPAD = 251,
	KEY_REM_DPY = 252,
	KEY_LOC_DPY = 253,
};

/*
 * Handlers for the various keypad functions
 */

/*! \brief append a character, or reset if '\0' */
static void append_char(char *str, int *str_pos, const char c)
{
	int i = *str_pos;
	if (c == '\0')
		i = 0;
	else if (i < GUI_BUFFER_LEN - 1)
		str[i++] = c;
	else
		i = GUI_BUFFER_LEN - 1; /* unnecessary, i think */
	str = '\0';
	*str_pos = i;
}

/* accumulate digits, possibly call dial if in connected mode */
static void keypad_digit(struct video_desc *env, int digit)
{	
	if (env->owner) {		/* we have a call, send the digit */
		struct ast_frame f = { AST_FRAME_DTMF, 0 };

		f.subclass = digit;
		ast_queue_frame(env->owner, &f);
	} else {		/* no call, accumulate digits */
		append_char(env->gui->inbuf, &env->gui->inbuf_pos, digit);
	}
}

/* this is a wrapper for actions that are available through the cli */
/* TODO append arg to command and send the resulting string as cli command */
static void keypad_send_command(struct video_desc *env, char *command)
{	
	ast_log(LOG_WARNING, "keypad_send_command(%s) called\n", command);
	ast_cli_command(env->gui->outfd, command);
	return;
}

/* function used to toggle on/off the status of some variables */
static char *keypad_toggle(struct video_desc *env, int index)
{
	ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index);

	switch (index) {
	case KEY_SENDVIDEO:
		env->out.sendvideo = !env->out.sendvideo;
		break;
#ifdef notyet
	case KEY_MUTE: {
		struct chan_oss_pvt *o = find_desc(oss_active);
		o->mute = !o->mute;
		}
		break;
	case KEY_AUTOANSWER: {
		struct chan_oss_pvt *o = find_desc(oss_active);
		o->autoanswer = !o->autoanswer;
		}
		break;
#endif
	}
	return NULL;
}

char *console_do_answer(int fd);
/*
 * Function called when the pick up button is pressed
 * perform actions according the channel status:
 *
 *  - if no one is calling us and no digits was pressed,
 *    the operation have no effects,
 *  - if someone is calling us we answer to the call.
 *  - if we have no call in progress and we pressed some
 *    digit, send the digit to the console.
 */
static void keypad_pick_up(struct video_desc *env)
{
	struct gui_info *gui = env->gui;

	ast_log(LOG_WARNING, "keypad_pick_up called\n");

	if (env->owner) { /* someone is calling us, just answer */
		console_do_answer(-1);
	} else if (gui->inbuf_pos) { /* we have someone to call */
		ast_cli_command(gui->outfd, gui->inbuf);
	}

	append_char(gui->inbuf, &gui->inbuf_pos, '\0'); /* clear buffer */
}

#if 0 /* still unused */
/*
 * As an alternative to SDL_TTF, we can simply load the font from
 * an image and blit characters on the background of the GUI.
 *
 * To generate a font we can use the 'fly' command with the
 * following script (3 lines with 32 chars each)
 
size 320,64
name font.png
transparent 0,0,0
string 255,255,255,  0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>?
string 255,255,255,  0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
string 255,255,255,  0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~
end

 */

/* Print given text on the gui */
static int gui_output(struct video_desc *env, const char *text)
{
#ifndef HAVE_SDL_TTF
	return 1;	/* error, not supported */
#else
	int x = 30, y = 20;	/* XXX change */
	SDL_Surface *output = NULL;
	SDL_Color color = {0, 0, 0};	/* text color */
	struct gui_info *gui = env->gui;
	SDL_Rect dest = {gui->win[WIN_KEYPAD].rect.x + x, y};

	/* clean surface each rewrite */
	SDL_BlitSurface(gui->keypad, NULL, gui->screen, &gui->win[WIN_KEYPAD].rect);

	output = TTF_RenderText_Solid(gui->font, text, color);
	if (output == NULL) {
		ast_log(LOG_WARNING, "Cannot render text on gui - %s\n", TTF_GetError());
		return 1;
	}

	SDL_BlitSurface(output, NULL, gui->screen, &dest);
	
	SDL_UpdateRects(gui->keypad, 1, &gui->win[WIN_KEYPAD].rect);
	SDL_FreeSurface(output);
	return 0;	/* success */
#endif
}
#endif 

static int video_geom(struct fbuf_t *b, const char *s);
static void sdl_setup(struct video_desc *env);
static int kp_match_area(const struct keypad_entry *e, int x, int y);

/*
 * Handle SDL_MOUSEBUTTONDOWN type, finding the palette
 * index value and calling the right callback.
 *
 * x, y are referred to the upper left corner of the main SDL window.
 */
static void handle_button_event(struct video_desc *env, SDL_MouseButtonEvent button)
{
	uint8_t index = KEY_OUT_OF_KEYPAD;	/* the key or region of the display we clicked on */

#if 0
	ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n",
		button.x, button.y, env->gui->kp_used, env->gui->kp_size, env->gui->kp);
#endif
	/* for each click we come back in normal mode */
	env->gui->text_mode = 0;

	/* define keypad boundary */
	if (button.x < env->in.rem_dpy.w)
		index = KEY_REM_DPY; /* click on remote video */
	else if (button.x > env->in.rem_dpy.w + env->gui->keypad->w)
		index = KEY_LOC_DPY; /* click on local video */
	else if (button.y > env->gui->keypad->h)
		index = KEY_OUT_OF_KEYPAD; /* click outside the keypad */
	else if (env->gui->kp) {
		int i;
		for (i = 0; i < env->gui->kp_used; i++) {
			if (kp_match_area(&env->gui->kp[i], button.x - env->in.rem_dpy.w, button.y)) {
				index = env->gui->kp[i].c;
				break;
			}
		}
	}

	/* exec the function */
	if (index < 128) {	/* surely clicked on the keypad, don't care which key */
		keypad_digit(env, index);
		return;
	}
	switch (index) {
	/* answer/close function */
	case KEY_PICK_UP:
		keypad_pick_up(env);
		break;
	case KEY_HANG_UP:
		keypad_send_command(env, "console hangup");
		break;

	/* other functions */
	case KEY_MUTE:
	case KEY_AUTOANSWER:
	case KEY_SENDVIDEO:
		keypad_toggle(env, index);
		break;

	case KEY_LOCALVIDEO:
		break;
	case KEY_REMOTEVIDEO:
		break;
	case KEY_WRITEMESSAGE:
		/* goes in text-mode */
		env->gui->text_mode = 1;
		break;


	/* press outside the keypad. right increases size, center decreases, left drags */
	case KEY_LOC_DPY:
	case KEY_REM_DPY:
		if (button.button == SDL_BUTTON_LEFT) {
			if (index == KEY_LOC_DPY) {
				/* store points where the drag start
				* and switch in drag mode */
				env->gui->x_drag = button.x;
				env->gui->y_drag = button.y;
				env->gui->drag_mode = 1;
			}
			break;
		} else {
			char buf[128];
			struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->out.loc_dpy : &env->in.rem_dpy;
			sprintf(buf, "%c%dx%d", button.button == SDL_BUTTON_RIGHT ? '>' : '<',
				fb->w, fb->h);
			video_geom(fb, buf);
			sdl_setup(env);
		}
		break;
	case KEY_OUT_OF_KEYPAD:
		break;

	case KEY_GUI_CLOSE:
		cleanup_sdl(env);
		break;
	case KEY_DIGIT_BACKGROUND:
		break;
	default:
		ast_log(LOG_WARNING, "function not yet defined %i\n", index);
	}
}

/*
 * Handle SDL_KEYDOWN type event, put the key pressed
 * in the dial buffer or in the text-message buffer,
 * depending on the text_mode variable value.
 *
 * key is the SDLKey structure corresponding to the key pressed.
 */
static void handle_keyboard_input(struct video_desc *env, SDLKey key)
{
	if (env->gui->text_mode) {
		/* append in the text-message buffer */
		if (key == SDLK_RETURN) {
			/* send the text message and return in normal mode */
			env->gui->text_mode = 0;
			keypad_send_command(env, "send text");
		} else {
			/* accumulate the key in the message buffer */
			append_char(env->gui->msgbuf, &env->gui->msgbuf_pos, key);
		}
	}
	else {
		/* append in the dial buffer */
		append_char(env->gui->inbuf, &env->gui->inbuf_pos, key);
	}

	return;
}

/*
 * Check if the grab point is inside the X screen.
 *
 * x represent the new grab value
 * limit represent the upper value to use
 */
static int boundary_checks(int x, int limit)
{
	return (x <= 0) ? 0 : (x > limit ? limit : x);
}

/* implement superlinear acceleration on the movement */
static int move_accel(int delta)
{
	int d1 = delta*delta / 100;
	return (delta > 0) ? delta + d1 : delta - d1;
}

/*
 * Move the source of the captured video.
 *
 * x_final_drag and y_final_drag are the coordinates where the drag ends,
 * start coordinares are in the gui_info structure.
 */
static void move_capture_source(struct video_desc *env, int x_final_drag, int y_final_drag)
{
	int new_x, new_y;		/* new coordinates for grabbing local video */
	int x = env->out.loc_src.x;	/* old value */
	int y = env->out.loc_src.y;	/* old value */

	/* move the origin */
#define POLARITY -1		/* +1 or -1 depending on the desired direction */
	new_x = x + POLARITY*move_accel(x_final_drag - env->gui->x_drag) * 3;
	new_y = y + POLARITY*move_accel(y_final_drag - env->gui->y_drag) * 3;
#undef POLARITY
	env->gui->x_drag = x_final_drag;	/* update origin */
	env->gui->y_drag = y_final_drag;

	/* check boundary and let the source to grab from the new points */
	env->out.loc_src.x = boundary_checks(new_x, env->out.screen_width - env->out.loc_src.w);
	env->out.loc_src.y = boundary_checks(new_y, env->out.screen_height - env->out.loc_src.h);
	return;
}

/*
 * I am seeing some kind of deadlock or stall around
 * SDL_PumpEvents() while moving the window on a remote X server
 * (both xfree-4.4.0 and xorg 7.2)
 * and windowmaker. It is unclear what causes it.
 */

/* grab a bunch of events */
static void eventhandler(struct video_desc *env)
{
#define N_EVENTS	32
	int i, n;
	SDL_Event ev[N_EVENTS];

#define MY_EV (SDL_MOUSEBUTTONDOWN|SDL_KEYDOWN)
	while ( (n = SDL_PeepEvents(ev, N_EVENTS, SDL_GETEVENT, SDL_ALLEVENTS)) > 0) {
		for (i = 0; i < n; i++) {
#if 0
			ast_log(LOG_WARNING, "------ event %d at %d %d\n",
				ev[i].type,  ev[i].button.x,  ev[i].button.y);
#endif
			switch (ev[i].type) {
			case SDL_KEYDOWN:
				handle_keyboard_input(env, ev[i].key.keysym.sym);
				break;
			case SDL_MOUSEMOTION:
				if (env->gui->drag_mode != 0)
					move_capture_source(env, ev[i].motion.x, ev[i].motion.y);
				break;
			case SDL_MOUSEBUTTONDOWN:
				handle_button_event(env, ev[i].button);
				break;
			case SDL_MOUSEBUTTONUP:
				if (env->gui->drag_mode != 0) {
					move_capture_source(env, ev[i].button.x, ev[i].button.y);
					env->gui->drag_mode = 0;
				}
				break;
			}

		}
	}
	if (1) {
		struct timeval b, a = ast_tvnow();
		int i;
		//SDL_Lock_EventThread();
		SDL_PumpEvents();
		b = ast_tvnow();
		i = ast_tvdiff_ms(b, a);
		if (i > 3)
			fprintf(stderr, "-------- SDL_PumpEvents took %dms\n", i);
		//SDL_Unlock_EventThread();
	}
}

static SDL_Surface *get_keypad(const char *file)
{
	SDL_Surface *temp;
 
#ifdef HAVE_SDL_IMAGE
	temp = IMG_Load(file);
#else
	temp = SDL_LoadBMP(file);
#endif
	if (temp == NULL)
		fprintf(stderr, "Unable to load image %s: %s\n",
			file, SDL_GetError());
	return temp;
}

/* TODO: consistency checks, check for bpp, widht and height */
/* Init the mask image used to grab the action. */
static struct gui_info *gui_init(struct video_desc *env)
{
	struct gui_info *gui = ast_calloc(1, sizeof(*gui));

	if (gui == NULL)
		return NULL;
	/* initialize keypad status */
	gui->text_mode = 0;
	gui->drag_mode = 0;

	/* initialize grab coordinates */
	env->out.loc_src.x = 0;
	env->out.loc_src.y = 0;

	/* initialize keyboard buffer */
	append_char(gui->inbuf, &gui->inbuf_pos, '\0');
	append_char(gui->msgbuf, &gui->msgbuf_pos, '\0');

#ifdef HAVE_SDL_TTF
	/* Initialize SDL_ttf library and load font */
	if (TTF_Init() == -1) {
		ast_log(LOG_WARNING, "Unable to init SDL_ttf, no output available\n");
		goto error;
	}

#define GUI_FONTSIZE 28
	gui->font = TTF_OpenFont( env->keypad_font, GUI_FONTSIZE);
	if (!gui->font) {
		ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", env->keypad_font);
		goto error;
	}
	ast_log(LOG_WARNING, "Loaded font %s\n", env->keypad_font);
#endif

	gui->outfd = open ("/dev/null", O_WRONLY);	/* discard output, temporary */
	if (gui->outfd < 0) {
		ast_log(LOG_WARNING, "Unable output fd\n");
		goto error;
	}
	return gui;

error:
	ast_free(gui);
	return NULL;
}

/* setup an sdl overlay and associated info, return 0 on success, != 0 on error */
static int set_win(SDL_Surface *screen, struct display_window *win, int fmt,
	int w, int h, int x, int y)
{
	win->bmp = SDL_CreateYUVOverlay(w, h, fmt, screen);
	if (win->bmp == NULL)
		return -1;	/* error */
	win->rect.x = x;
	win->rect.y = y;
	win->rect.w = w;
	win->rect.h = h;
	return 0;
}

static int keypad_cfg_read(struct gui_info *gui, const char *val);

static void keypad_setup(struct video_desc *env)
{
	int fd = -1;
	void *p = NULL;
	off_t l = 0;

	if (env->gui->keypad)
		return;
	env->gui->keypad = get_keypad(env->keypad_file);
	if (!env->gui->keypad)
		return;

	/*
	 * If the keypad image has a comment field, try to read
	 * the button location from there. The block must be
	 *	keypad_entry = token shape x0 y0 x1 y1 h
	 *	...
	 * (basically, lines have the same format as config file entries.
	 * same as the keypad_entry.
	 * You can add it to a jpeg file using wrjpgcom
	 */
	do { /* only once, in fact */
		const char region[] = "region";
		int reg_len = strlen(region);
		const unsigned char *s, *e;

		fd = open(env->keypad_file, O_RDONLY);
		if (fd < 0) {
			ast_log(LOG_WARNING, "fail to open %s\n", env->keypad_file);
			break;
		}
		l = lseek(fd, 0, SEEK_END);
		if (l <= 0) {
			ast_log(LOG_WARNING, "fail to lseek %s\n", env->keypad_file);
			break;
		}
		p = mmap(NULL, l, PROT_READ, 0, fd, 0);
		if (p == NULL) {
			ast_log(LOG_WARNING, "fail to mmap %s size %ld\n", env->keypad_file, (long)l);
			break;
		}
		e = (const unsigned char *)p + l;
		for (s = p; s < e - 20 ; s++) {
			if (!memcmp(s, region, reg_len)) { /* keyword found */
				/* reset previous entries */
				keypad_cfg_read(env->gui, "reset");
				break;
			}
		}
		for ( ;s < e - 20; s++) {
			char buf[256];
			const unsigned char *s1;
			if (index(" \t\r\n", *s))	/* ignore blanks */
				continue;
			if (*s > 127)	/* likely end of comment */
				break;
			if (memcmp(s, region, reg_len)) /* keyword not found */
				break;
			s += reg_len;
			l = MIN(sizeof(buf), e - s);
			ast_copy_string(buf, s, l);
			s1 = ast_skip_blanks(buf);	/* between token and '=' */
			if (*s1++ != '=')	/* missing separator */
				break;
			if (*s1 == '>')	/* skip => */
				s1++;
			keypad_cfg_read(env->gui, ast_skip_blanks(s1));
			/* now wait for a newline */
			s1 = s;
			while (s1 < e - 20 && !index("\r\n", *s1) && *s1 < 128)
				s1++;
			s = s1;
		}
	} while (0);
	if (p)
		munmap(p, l);
	if (fd >= 0)
		close(fd);
}

/* [re]set the main sdl window, useful in case of resize */
static void sdl_setup(struct video_desc *env)
{
	int dpy_fmt = SDL_IYUV_OVERLAY;	/* YV12 causes flicker in SDL */
	int depth, maxw, maxh;
	const SDL_VideoInfo *info = SDL_GetVideoInfo();
	int kp_w = 0, kp_h = 0;	/* keypad width and height */
	int sdl_ok = 0;

	/* We want at least 16bpp to support YUV overlays.
	 * E.g with SDL_VIDEODRIVER = aalib the default is 8
	 */
	depth = info->vfmt->BitsPerPixel;
	if (depth < 16)
		depth = 16;
	/*
	 * initialize the SDL environment. We have one large window
	 * with local and remote video, and a keypad.
	 * At the moment we arrange them statically, as follows:
	 * - on the left, the remote video;
	 * - on the center, the keypad
	 * - on the right, the local video
	 * We need to read in the skin for the keypad before creating the main
	 * SDL window, because the size is only known here.
	 */

	env->gui = gui_init(env);
	ast_log(LOG_WARNING, "gui_init returned %p\n", env->gui);
	if (!env->gui)
		goto no_sdl;
	keypad_setup(env);
	ast_log(LOG_WARNING, "keypad_setup returned %p %d\n",
		env->gui->keypad, env->gui->kp_used);
	if (env->gui->keypad) {
		kp_w = env->gui->keypad->w;
		kp_h = env->gui->keypad->h;
	}
#define BORDER	5	/* border around our windows */
	maxw = env->in.rem_dpy.w + env->out.loc_dpy.w + kp_w;
	maxh = MAX( MAX(env->in.rem_dpy.h, env->out.loc_dpy.h), kp_h);
	maxw += 4 * BORDER;
	maxh += 2 * BORDER;
	env->gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0);
	if (!env->gui->screen) {
		ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n");
		goto no_sdl;
	}

	SDL_WM_SetCaption("Asterisk console Video Output", NULL);
	if (set_win(env->gui->screen, &env->gui->win[WIN_REMOTE], dpy_fmt,
			env->in.rem_dpy.w, env->in.rem_dpy.h, BORDER, BORDER))
		goto no_sdl;
	if (set_win(env->gui->screen, &env->gui->win[WIN_LOCAL], dpy_fmt,
			env->out.loc_dpy.w, env->out.loc_dpy.h,
			3*BORDER+env->in.rem_dpy.w + kp_w, BORDER))
		goto no_sdl;

	/* display the skin, but do not free it as we need it later to
	 * restore text areas and maybe sliders too.
	 */
	if (env->gui && env->gui->keypad) {
		struct SDL_Rect *dest = &env->gui->win[WIN_KEYPAD].rect;
		dest->x = 2*BORDER + env->in.rem_dpy.w;
		dest->y = BORDER;
		dest->w = env->gui->keypad->w;
		dest->h = env->gui->keypad->h;
		SDL_BlitSurface(env->gui->keypad, NULL, env->gui->screen, dest);
		SDL_UpdateRects(env->gui->screen, 1, dest);
	}
	env->in.dec_in_cur = &env->in.dec_in[0];
	env->in.dec_in_dpy = NULL;	/* nothing to display */
	sdl_ok = 1;

no_sdl:
	if (!sdl_ok)	/* free resources in case of errors */
		cleanup_sdl(env);
}

/*
 * Functions to determine if a point is within a region. Return 1 if success.
 * First rotate the point, with
 *	x' =  (x - x0) * cos A + (y - y0) * sin A
 *	y' = -(x - x0) * sin A + (y - y0) * cos A
 * where cos A = (x1-x0)/l, sin A = (y1 - y0)/l, and
 *	l = sqrt( (x1-x0)^2 + (y1-y0)^2
 * Then determine inclusion by simple comparisons i.e.:
 *	rectangle: x >= 0 && x < l && y >= 0 && y < h
 *	ellipse: (x-xc)^2/l^2 + (y-yc)^2/h2 < 1
 */
static int kp_match_area(const struct keypad_entry *e, int x, int y)
{
	double xp, dx = (e->x1 - e->x0);
	double yp, dy = (e->y1 - e->y0);
	double l = sqrt(dx*dx + dy*dy);
	int ret = 0;

	if (l > 1) { /* large enough */
		xp = ((x - e->x0)*dx + (y - e->y0)*dy)/l;
		yp = (-(x - e->x0)*dy + (y - e->y0)*dx)/l;
		if (e->type == KP_RECT) {
			ret = (xp >= 0 && xp < l && yp >=0 && yp < l);
		} else if (e->type == KP_CIRCLE) {
			dx = xp*xp/(l*l) + yp*yp/(e->h*e->h);
			ret = (dx < 1);
		}
	}
#if 0
	ast_log(LOG_WARNING, "result %d [%d] for match %d,%d in type %d p0 %d,%d p1 %d,%d h %d\n",
		ret, e->c, x, y, e->type, e->x0, e->y0, e->x1, e->y1, e->h);
#endif
	return ret;
}

struct _s_k { const char *s; int k; };
static struct _s_k gui_key_map[] = {
	{"PICK_UP",	KEY_PICK_UP },
	{"PICKUP",	KEY_PICK_UP },
        {"HANG_UP",	KEY_HANG_UP },
        {"HANGUP",	KEY_HANG_UP },
        {"MUTE",	KEY_MUTE },
        {"AUTOANSWER",	KEY_AUTOANSWER },
        {"SENDVIDEO",	KEY_SENDVIDEO },
        {"LOCALVIDEO",	KEY_LOCALVIDEO },
        {"REMOTEVIDEO",	KEY_REMOTEVIDEO },
        {"WRITEMESSAGE", KEY_WRITEMESSAGE },
        {"GUI_CLOSE",	KEY_GUI_CLOSE },
        {NULL, 0 } };

/*! \brief read a keypad entry line in the format
 *	reset
 *	token circle xc yc diameter
 *	token circle xc yc x1 y1 h	# ellipse, main diameter and height
 *	token rect x0 y0 x1 y1 h	# rectangle with main side and eight
 * token is the token to be returned, either a character or a symbol
 * as KEY_* above
 * Return 1 on success, 0 on error.
 */
static int keypad_cfg_read(struct gui_info *gui, const char *val)
{
	struct keypad_entry e;
	char s1[16], s2[16];
	int i, ret = 0;

	if (gui == NULL || val == NULL)
		return 0;

	bzero(&e, sizeof(e));
	i = sscanf(val, "%14s %14s %d %d %d %d %d",
                s1, s2, &e.x0, &e.y0, &e.x1, &e.y1, &e.h);

	switch (i) {
	default:
		break;
	case 1:	/* only "reset" is allowed */
		if (strcasecmp(s1, "reset"))	/* invalid */
			break;
		if (gui->kp) {
			gui->kp_used = 0;
		}
		break;
	case 5: /* token circle xc yc diameter */
		if (strcasecmp(s2, "circle"))	/* invalid */
			break;
		e.h = e.x1;
		e.y1 = e.y0;	/* map radius in x1 y1 */
		e.x1 = e.x0 + e.h;	/* map radius in x1 y1 */
		e.x0 = e.x0 - e.h;	/* map radius in x1 y1 */
		/* fallthrough */

	case 7: /* token circle|rect x0 y0 x1 y1 h */
		if (e.x1 < e.x0 || e.h <= 0) {
			ast_log(LOG_WARNING, "error in coordinates\n");
			e.type = 0;
			break;
		}
		if (!strcasecmp(s2, "circle")) {
			/* for a circle we specify the diameter but store center and radii */
			e.type = KP_CIRCLE;
			e.x0 = (e.x1 + e.x0) / 2;
			e.y0 = (e.y1 + e.y0) / 2;
			e.h = e.h / 2;
		} else if (!strcasecmp(s2, "rect")) {
			e.type = KP_RECT;
		} else
			break;
		ret = 1;
	}
	// ast_log(LOG_WARNING, "reading [%s] returns %d %d\n", val, i, ret);
	if (ret == 0)
		return 0;
	/* map the string into token to be returned */
	i = atoi(s1);
	if (i > 0 || s1[1] == '\0')	/* numbers or single characters */
		e.c = (i > 9) ? i : s1[0];
	else {
		struct _s_k *p;
		for (p = gui_key_map; p->s; p++) {
			if (!strcasecmp(p->s, s1)) {
				e.c = p->k;
				break;
			}
		}
	}
	if (e.c == 0) {
		ast_log(LOG_WARNING, "missing token\n");
		return 0;
	}
	if (gui->kp_size == 0) {
		gui->kp = ast_calloc(10, sizeof(e));
		if (gui->kp == NULL) {
			ast_log(LOG_WARNING, "cannot allocate kp");
			return 0;
		}
		gui->kp_size = 10;
	}
	if (gui->kp_size == gui->kp_used) { /* must allocate */
		struct keypad_entry *a = ast_realloc(gui->kp, sizeof(e)*(gui->kp_size+10));
		if (a == NULL) {
			ast_log(LOG_WARNING, "cannot reallocate kp");
			return 0;
		}
		gui->kp = a;
		gui->kp_size += 10;
	}
	if (gui->kp_size == gui->kp_used)
		return 0;
	gui->kp[gui->kp_used++] = e;
	ast_log(LOG_WARNING, "now %d regions\n", gui->kp_used);
	return 1;
}