add support for cropping the keypad image while displaying it.
[asterisk/asterisk.git] / channels / console_gui.c
1 /*
2  * GUI for console video.
3  * The routines here are in charge of loading the keypad and handling events.
4  * $Revision$
5  */
6
7 /*
8  * GUI layout, structure and management
9  
10 For the GUI we use SDL to create a large surface (gui->screen)
11 containing tree sections: remote video on the left, local video
12 on the right, and the keypad with all controls and text windows
13 in the center.
14 The central section is built using an image for the skin, fonts and
15 other GUI elements.  Comments embedded in the image to indicate to
16 what function each area is mapped to.
17
18 Mouse and keyboard events are detected on the whole surface, and
19 handled differently according to their location:
20
21 - drag on the local video window are used to move the captured
22   area (in the case of X11 grabber) or the picture-in-picture
23   location (in case of camera included on the X11 grab).
24 - click on the keypad are mapped to the corresponding key;
25 - drag on some keypad areas (sliders etc.) are mapped to the
26   corresponding functions;
27 - keystrokes are used as keypad functions, or as text input
28   if we are in text-input mode.
29
30 Configuration options control the appeareance of the gui:
31
32     keypad = /tmp/phone.jpg     ; the skin
33     keypad_font = /tmp/font.ttf ; the font to use for output (XXX deprecated)
34
35  *
36  */
37
38 #include "asterisk.h"
39 #include "console_video.h"
40 #include "asterisk/lock.h"
41 #include "asterisk/frame.h"
42 #include "asterisk/utils.h"     /* ast_calloc and ast_realloc */
43 #include <math.h>               /* sqrt */
44
45 /* We use 3 'windows' in the GUI */
46 enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_MAX };
47
48 #ifndef HAVE_SDL        /* stubs if we don't have any sdl */
49 static void show_frame(struct video_desc *env, int out) {}
50 static void sdl_setup(struct video_desc *env)           {}
51 static struct gui_info *cleanup_sdl(struct gui_info *gui)       { return NULL; }
52 static void eventhandler(struct video_desc *env, const char *caption)   {}
53 static int keypad_cfg_read(struct gui_info *gui, const char *val)       { return 0; }
54
55 #else /* HAVE_SDL, the real rendering code */
56
57 #include <SDL/SDL.h>
58 #ifdef HAVE_SDL_IMAGE
59 #include <SDL/SDL_image.h>      /* for loading images */
60 #endif
61 #ifdef HAVE_SDL_TTF
62 #include <SDL/SDL_ttf.h>        /* render text on sdl surfaces */
63 #endif
64
65 enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE };
66 struct keypad_entry {
67         int c;  /* corresponding character */
68         int x0, y0, x1, y1, h;  /* arguments */
69         enum kp_type type;
70 };
71
72 /* our representation of a displayed window. SDL can only do one main
73  * window so we map everything within that one
74  */
75 struct display_window   {   
76         SDL_Overlay     *bmp;
77         SDL_Rect        rect;   /* location of the window */
78 };
79 #define GUI_BUFFER_LEN 256                      /* buffer lenght used for input buffers */
80
81 /* Where do we send the keyboard/keypad output */
82 enum kb_output {
83         KO_NONE,
84         KO_INPUT,       /* the local input window */
85         KO_DIALED,      /* the 'dialed number' window */
86 };
87
88 enum drag_window {      /* which window are we dragging */
89         DRAG_NONE,
90         DRAG_LOCAL,     /* local video */
91         DRAG_REMOTE,    /* remote video */
92         DRAG_DIALED,    /* dialed number */
93         DRAG_INPUT,     /* input window */
94         DRAG_MESSAGE,   /* message window */
95 };
96
97 /*! \brief info related to the gui: button status, mouse coords, etc. */
98 struct gui_info {
99         char                    inbuf[GUI_BUFFER_LEN];  /* buffer for to-dial buffer */
100         int                     inbuf_pos;      /* next free position in inbuf */
101         char                    msgbuf[GUI_BUFFER_LEN]; /* buffer for text-message buffer */
102         int                     msgbuf_pos;     /* next free position in msgbuf */
103         enum kb_output          kb_output;      /* where the keyboard output goes */
104         enum drag_window        drag_window;    /* which window are we dragging */
105         int                     x_drag;         /* x coordinate where the drag starts */
106         int                     y_drag;         /* y coordinate where the drag starts */
107 #ifdef HAVE_SDL_TTF
108         TTF_Font                *font;          /* font to be used */ 
109 #endif
110         /* support for display. */
111         SDL_Surface             *screen;        /* the main window */
112
113         int                     outfd;          /* fd for output */
114         SDL_Surface             *keypad;        /* the skin for the keypad */
115
116         SDL_Rect                kp_rect;        /* portion of the skin to display - default all */
117
118         /* variable-size array mapping keypad regions to functions */
119         int kp_size, kp_used;
120         struct keypad_entry *kp;
121
122         struct display_window   win[WIN_MAX];
123 };
124
125 /*! \brief free the resources in struct gui_info and the descriptor itself.
126  *  Return NULL so we can assign the value back to the descriptor in case.
127  */
128 static struct gui_info *cleanup_sdl(struct gui_info *gui)
129 {
130         int i;
131
132         if (gui == NULL)
133                 return NULL;
134
135 #ifdef HAVE_SDL_TTF
136         /* unload font file */ 
137         if (gui->font) {
138                 TTF_CloseFont(gui->font);
139                 gui->font = NULL; 
140         }
141
142         /* uninitialize SDL_ttf library */
143         if ( TTF_WasInit() )
144                 TTF_Quit();
145 #endif
146         if (gui->outfd > -1)
147                 close(gui->outfd);
148         if (gui->keypad)
149                 SDL_FreeSurface(gui->keypad);
150         gui->keypad = NULL;
151         if (gui->kp)
152                 ast_free(gui->kp);
153
154         /* uninitialize the SDL environment */
155         for (i = 0; i < WIN_MAX; i++) {
156                 if (gui->win[i].bmp)
157                         SDL_FreeYUVOverlay(gui->win[i].bmp);
158         }
159         bzero(gui, sizeof(gui));
160         ast_free(gui);
161         SDL_Quit();
162         return NULL;
163 }
164
165 /*
166  * Display video frames (from local or remote stream) using the SDL library.
167  * - Set the video mode to use the resolution specified by the codec context
168  * - Create a YUV Overlay to copy the frame into it;
169  * - After the frame is copied into the overlay, display it
170  *
171  * The size is taken from the configuration.
172  *
173  * 'out' is 0 for remote video, 1 for the local video
174  */
175 static void show_frame(struct video_desc *env, int out)
176 {
177         AVPicture *p_in, p_out;
178         struct fbuf_t *b_in, *b_out;
179         SDL_Overlay *bmp;
180         struct gui_info *gui = env->gui;
181
182         if (!gui)
183                 return;
184
185         if (out == WIN_LOCAL) { /* webcam/x11 to sdl */
186                 b_in = &env->enc_in;
187                 b_out = &env->loc_dpy;
188                 p_in = NULL;
189         } else {
190                 /* copy input format from the decoding context */
191                 AVCodecContext *c;
192                 if (env->in == NULL)    /* XXX should not happen - decoder not ready */
193                         return;
194                 c = env->in->dec_ctx;
195                 b_in = &env->in->dec_out;
196                 b_in->pix_fmt = c->pix_fmt;
197                 b_in->w = c->width;
198                 b_in->h = c->height;
199
200                 b_out = &env->rem_dpy;
201                 p_in = (AVPicture *)env->in->d_frame;
202         }
203         bmp = gui->win[out].bmp;
204         SDL_LockYUVOverlay(bmp);
205         /* output picture info - this is sdl, YUV420P */
206         bzero(&p_out, sizeof(p_out));
207         p_out.data[0] = bmp->pixels[0];
208         p_out.data[1] = bmp->pixels[1];
209         p_out.data[2] = bmp->pixels[2];
210         p_out.linesize[0] = bmp->pitches[0];
211         p_out.linesize[1] = bmp->pitches[1];
212         p_out.linesize[2] = bmp->pitches[2];
213
214         my_scale(b_in, p_in, b_out, &p_out);
215
216         /* lock to protect access to Xlib by different threads. */
217         SDL_DisplayYUVOverlay(bmp, &gui->win[out].rect);
218         SDL_UnlockYUVOverlay(bmp);
219 }
220
221 /*
222  * Identifiers for regions of the main window.
223  * Values between 0 and 127 correspond to ASCII characters.
224  * The corresponding strings to be used in the skin comment section
225  * are defined in gui_key_map.
226  */
227 enum skin_area {
228         /* answer/close functions */
229         KEY_PICK_UP = 128,
230         KEY_HANG_UP = 129,
231
232         KEY_MUTE = 130,
233         KEY_AUTOANSWER = 131,
234         KEY_SENDVIDEO = 132,
235         KEY_LOCALVIDEO = 133,
236         KEY_REMOTEVIDEO = 134,
237         KEY_WRITEMESSAGE = 135,
238         KEY_FLASH = 136,
239         KEY_GUI_CLOSE = 199,            /* close gui */
240
241         /* regions of the skin - active area, fonts, etc. */
242         KEY_KEYPAD = 200,               /* the keypad - default to the whole image */
243         KEY_FONT = 201,         /* the font */
244
245         /* areas outside the keypad - simulated */
246         KEY_OUT_OF_KEYPAD = 241,
247         KEY_REM_DPY = 242,
248         KEY_LOC_DPY = 243,
249         KEY_RESET = 253,                /* the 'reset' keyword */
250         KEY_NONE = 254,                 /* invalid area */
251         KEY_DIGIT_BACKGROUND = 255,     /* other areas within the keypad */
252 };
253
254 /*
255  * Handlers for the various keypad functions
256  */
257
258 /*! \brief append a character, or reset if  c = '\0' */
259 static void append_char(char *str, int *str_pos, const char c)
260 {
261         int i = *str_pos;
262         if (c == '\0')
263                 i = 0;
264         else if (i < GUI_BUFFER_LEN - 1)
265                 str[i++] = c;
266         else
267                 i = GUI_BUFFER_LEN - 1; /* unnecessary, i think */
268         str = '\0';
269         *str_pos = i;
270 }
271
272 /*! \brief append string to a buffer */
273 static void append_string(char *str, int *str_pos, const char *s)
274 {
275         while (*s)
276                 append_char(str, str_pos, *s++);
277 }
278
279 /* accumulate digits, possibly call dial if in connected mode */
280 static void keypad_digit(struct video_desc *env, int digit)
281 {       
282         if (env->owner) {               /* we have a call, send the digit */
283                 struct ast_frame f = { AST_FRAME_DTMF, 0 };
284
285                 f.subclass = digit;
286                 ast_queue_frame(env->owner, &f);
287         } else {                /* no call, accumulate digits */
288                 append_char(env->gui->inbuf, &env->gui->inbuf_pos, digit);
289         }
290 }
291
292 /* function used to toggle on/off the status of some variables */
293 static char *keypad_toggle(struct video_desc *env, int index)
294 {
295         ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index);
296
297         switch (index) {
298         case KEY_SENDVIDEO:
299                 env->out.sendvideo = !env->out.sendvideo;
300                 break;
301 #ifdef notyet
302         case KEY_MUTE: {
303                 struct chan_oss_pvt *o = find_desc(oss_active);
304                 o->mute = !o->mute;
305                 }
306                 break;
307         case KEY_AUTOANSWER: {
308                 struct chan_oss_pvt *o = find_desc(oss_active);
309                 o->autoanswer = !o->autoanswer;
310                 }
311                 break;
312 #endif
313         }
314         return NULL;
315 }
316
317 char *console_do_answer(int fd);
318 /*
319  * Function called when the pick up button is pressed
320  * perform actions according the channel status:
321  *
322  *  - if no one is calling us and no digits was pressed,
323  *    the operation have no effects,
324  *  - if someone is calling us we answer to the call.
325  *  - if we have no call in progress and we pressed some
326  *    digit, send the digit to the console.
327  */
328 static void keypad_pick_up(struct video_desc *env)
329 {
330         struct gui_info *gui = env->gui;
331
332         ast_log(LOG_WARNING, "keypad_pick_up called\n");
333
334         if (env->owner) { /* someone is calling us, just answer */
335                 ast_cli_command(gui->outfd, "console answer");
336         } else if (gui->inbuf_pos) { /* we have someone to call */
337                 ast_cli_command(gui->outfd, gui->inbuf);
338         }
339         append_char(gui->inbuf, &gui->inbuf_pos, '\0'); /* clear buffer */
340         append_string(gui->inbuf, &gui->inbuf_pos, "console dial ");
341 }
342
343 #if 0 /* still unused */
344 /*
345  * As an alternative to SDL_TTF, we can simply load the font from
346  * an image and blit characters on the background of the GUI.
347  *
348  * To generate a font we can use the 'fly' command with the
349  * following script (3 lines with 32 chars each)
350  
351 size 320,64
352 name font.png
353 transparent 0,0,0
354 string 255,255,255,  0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>?
355 string 255,255,255,  0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
356 string 255,255,255,  0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~
357 end
358
359  */
360
361 /* Print given text on the gui */
362 static int gui_output(struct video_desc *env, const char *text)
363 {
364 #ifndef HAVE_SDL_TTF
365         return 1;       /* error, not supported */
366 #else
367         int x = 30, y = 20;     /* XXX change */
368         SDL_Surface *output = NULL;
369         SDL_Color color = {0, 0, 0};    /* text color */
370         struct gui_info *gui = env->gui;
371         SDL_Rect dest = {gui->win[WIN_KEYPAD].rect.x + x, y};
372
373         /* clean surface each rewrite */
374         SDL_BlitSurface(gui->keypad, NULL, gui->screen, &gui->win[WIN_KEYPAD].rect);
375
376         output = TTF_RenderText_Solid(gui->font, text, color);
377         if (output == NULL) {
378                 ast_log(LOG_WARNING, "Cannot render text on gui - %s\n", TTF_GetError());
379                 return 1;
380         }
381
382         SDL_BlitSurface(output, NULL, gui->screen, &dest);
383         
384         SDL_UpdateRects(gui->keypad, 1, &gui->win[WIN_KEYPAD].rect);
385         SDL_FreeSurface(output);
386         return 0;       /* success */
387 #endif
388 }
389 #endif 
390
391 static int video_geom(struct fbuf_t *b, const char *s);
392 static void sdl_setup(struct video_desc *env);
393 static int kp_match_area(const struct keypad_entry *e, int x, int y);
394
395 /*
396  * Handle SDL_MOUSEBUTTONDOWN type, finding the palette
397  * index value and calling the right callback.
398  *
399  * x, y are referred to the upper left corner of the main SDL window.
400  */
401 static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button)
402 {
403         uint8_t index = KEY_OUT_OF_KEYPAD;      /* the key or region of the display we clicked on */
404         struct gui_info *gui = env->gui;
405
406 #if 0
407         ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n",
408                 button.x, button.y, gui->kp_used, gui->kp_size, gui->kp);
409 #endif
410         /* for each mousedown we end previous drag */
411         gui->drag_window = DRAG_NONE;
412
413         /* define keypad boundary */
414         if (button.x < env->rem_dpy.w)
415                 index = KEY_REM_DPY; /* click on remote video */
416         else if (button.x > env->rem_dpy.w + gui->keypad->w)
417                 index = KEY_LOC_DPY; /* click on local video */
418         else if (button.y > gui->keypad->h)
419                 index = KEY_OUT_OF_KEYPAD; /* click outside the keypad */
420         else if (gui->kp) {
421                 int i;
422                 for (i = 0; i < gui->kp_used; i++) {
423                         if (kp_match_area(&gui->kp[i], button.x - env->rem_dpy.w, button.y)) {
424                                 index = gui->kp[i].c;
425                                 break;
426                         }
427                 }
428         }
429
430         /* exec the function */
431         if (index < 128) {      /* surely clicked on the keypad, don't care which key */
432                 keypad_digit(env, index);
433                 return;
434         }
435         switch (index) {
436         /* answer/close function */
437         case KEY_PICK_UP:
438                 keypad_pick_up(env);
439                 break;
440         case KEY_HANG_UP:
441                 ast_cli_command(gui->outfd, "console hangup");
442                 break;
443
444         /* other functions */
445         case KEY_MUTE:
446         case KEY_AUTOANSWER:
447         case KEY_SENDVIDEO:
448                 keypad_toggle(env, index);
449                 break;
450
451         case KEY_LOCALVIDEO:
452                 break;
453         case KEY_REMOTEVIDEO:
454                 break;
455
456         /* press outside the keypad. right increases size, center decreases, left drags */
457         case KEY_LOC_DPY:
458         case KEY_REM_DPY:
459                 if (button.button == SDL_BUTTON_LEFT) {
460                         if (index == KEY_LOC_DPY) {
461                                 /* store points where the drag start
462                                 * and switch in drag mode */
463                                 env->gui->x_drag = button.x;
464                                 env->gui->y_drag = button.y;
465                                 env->gui->drag_window = DRAG_LOCAL;
466                         }
467                         break;
468                 } else {
469                         char buf[128];
470                         struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->loc_dpy : &env->rem_dpy;
471                         sprintf(buf, "%c%dx%d", button.button == SDL_BUTTON_RIGHT ? '>' : '<',
472                                 fb->w, fb->h);
473                         video_geom(fb, buf);
474                         sdl_setup(env);
475                 }
476                 break;
477         case KEY_OUT_OF_KEYPAD:
478                 break;
479
480         case KEY_DIGIT_BACKGROUND:
481                 break;
482         default:
483                 ast_log(LOG_WARNING, "function not yet defined %i\n", index);
484         }
485 }
486
487 /*
488  * Handle SDL_KEYDOWN type event, put the key pressed
489  * in the dial buffer or in the text-message buffer,
490  * depending on the text_mode variable value.
491  *
492  * key is the SDLKey structure corresponding to the key pressed.
493  * XXX needs to be cleaned up when handling returns etc.
494  */
495 static void handle_keyboard_input(struct video_desc *env, SDLKey key)
496 {
497         struct gui_info *gui = env->gui;
498         switch (gui->kb_output) {
499         default:
500                 break;
501         case KO_INPUT:
502                 /* append in the text-message buffer */
503                 if (key == SDLK_RETURN) {
504                         /* send the text message and return in normal mode */
505                         gui->kb_output = KO_NONE;
506                         ast_cli_command(gui->outfd, gui->msgbuf);
507                         append_char(gui->msgbuf, &gui->msgbuf_pos, '\0');
508                 } else {
509                         /* accumulate the key in the message buffer */
510                         append_char(gui->msgbuf, &gui->msgbuf_pos, key);
511                 }
512                 break;
513         case KO_DIALED:
514                 /* XXX actually, enter should be a 'console dial ' */
515                 /* append in the dial buffer */
516                 append_char(gui->inbuf, &gui->inbuf_pos, key);
517                 break;
518         }
519
520         return;
521 }
522
523 /* implement superlinear acceleration on the movement */
524 static int move_accel(int delta)
525 {
526         int d1 = delta*delta / 100;
527         return (delta > 0) ? delta + d1 : delta - d1;
528 }
529
530 static void grabber_move(struct video_out_desc *, int dx, int dy);
531 /*
532  * Move the source of the captured video.
533  *
534  * x_final_drag and y_final_drag are the coordinates where the drag ends,
535  * start coordinares are in the gui_info structure.
536  */
537 static void move_capture_source(struct video_desc *env, int x_final_drag, int y_final_drag)
538 {
539         int dx, dy;
540
541         /* move the origin */
542 #define POLARITY -1             /* +1 or -1 depending on the desired direction */
543         dx = POLARITY*move_accel(x_final_drag - env->gui->x_drag) * 3;
544         dy = POLARITY*move_accel(y_final_drag - env->gui->y_drag) * 3;
545 #undef POLARITY
546         env->gui->x_drag = x_final_drag;        /* update origin */
547         env->gui->y_drag = y_final_drag;
548
549         grabber_move(&env->out, dx, dy);
550         return;
551 }
552
553 /*
554  * I am seeing some kind of deadlock or stall around
555  * SDL_PumpEvents() while moving the window on a remote X server
556  * (both xfree-4.4.0 and xorg 7.2)
557  * and windowmaker. It is unclear what causes it.
558  */
559
560 /*! \brief refresh the screen, and also grab a bunch of events.
561  */
562 static void eventhandler(struct video_desc *env, const char *caption)
563 {
564         struct gui_info *gui = env->gui;
565 #define N_EVENTS        32
566         int i, n;
567         SDL_Event ev[N_EVENTS];
568
569         if (!gui)
570                 return;
571         if (caption)
572                 SDL_WM_SetCaption(caption, NULL);
573
574 #define MY_EV (SDL_MOUSEBUTTONDOWN|SDL_KEYDOWN)
575         while ( (n = SDL_PeepEvents(ev, N_EVENTS, SDL_GETEVENT, SDL_ALLEVENTS)) > 0) {
576                 for (i = 0; i < n; i++) {
577 #if 0
578                         ast_log(LOG_WARNING, "------ event %d at %d %d\n",
579                                 ev[i].type,  ev[i].button.x,  ev[i].button.y);
580 #endif
581                         switch (ev[i].type) {
582                         case SDL_KEYDOWN:
583                                 handle_keyboard_input(env, ev[i].key.keysym.sym);
584                                 break;
585                         case SDL_MOUSEMOTION:
586                                 if (gui->drag_window == DRAG_LOCAL)
587                                         move_capture_source(env, ev[i].motion.x, ev[i].motion.y);
588                                 break;
589                         case SDL_MOUSEBUTTONDOWN:
590                                 handle_mousedown(env, ev[i].button);
591                                 break;
592                         case SDL_MOUSEBUTTONUP:
593                                 if (gui->drag_window == DRAG_LOCAL) {
594                                         move_capture_source(env, ev[i].button.x, ev[i].button.y);
595                                         gui->drag_window = DRAG_NONE;
596                                 }
597                                 break;
598                         }
599
600                 }
601         }
602         if (1) {
603                 struct timeval b, a = ast_tvnow();
604                 int i;
605                 //SDL_Lock_EventThread();
606                 SDL_PumpEvents();
607                 b = ast_tvnow();
608                 i = ast_tvdiff_ms(b, a);
609                 if (i > 3)
610                         fprintf(stderr, "-------- SDL_PumpEvents took %dms\n", i);
611                 //SDL_Unlock_EventThread();
612         }
613 }
614
615 static SDL_Surface *load_image(const char *file)
616 {
617         SDL_Surface *temp;
618  
619 #ifdef HAVE_SDL_IMAGE
620         temp = IMG_Load(file);
621 #else
622         temp = SDL_LoadBMP(file);
623 #endif
624         if (temp == NULL)
625                 fprintf(stderr, "Unable to load image %s: %s\n",
626                         file, SDL_GetError());
627         return temp;
628 }
629
630 static void keypad_setup(struct gui_info *gui, const char *kp_file);
631
632 /* TODO: consistency checks, check for bpp, widht and height */
633 /* Init the mask image used to grab the action. */
634 static struct gui_info *gui_init(const char *keypad_file)
635 {
636         struct gui_info *gui = ast_calloc(1, sizeof(*gui));
637
638         if (gui == NULL)
639                 return NULL;
640         /* initialize keypad status */
641         gui->kb_output = KO_DIALED;
642         gui->drag_window = DRAG_NONE;
643         gui->outfd = -1;
644
645         /* initialize keyboard buffer */
646         append_char(gui->inbuf, &gui->inbuf_pos, '\0');
647         append_string(gui->inbuf, &gui->inbuf_pos, "console dial ");
648         append_char(gui->msgbuf, &gui->msgbuf_pos, '\0');
649
650         keypad_setup(gui, keypad_file);
651         if (gui->keypad == NULL)        /* no keypad, we are done */
652                 return gui;
653 #ifdef HAVE_SDL_TTF
654         /* Initialize SDL_ttf library and load font */
655         if (TTF_Init() == -1) {
656                 ast_log(LOG_WARNING, "Unable to init SDL_ttf, no output available\n");
657                 goto error;
658         }
659
660 #define GUI_FONTSIZE 28
661         gui->font = TTF_OpenFont( env->keypad_font, GUI_FONTSIZE);
662         if (!gui->font) {
663                 ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", env->keypad_font);
664                 goto error;
665         }
666         ast_log(LOG_WARNING, "Loaded font %s\n", env->keypad_font);
667 #endif
668
669         gui->outfd = open ("/dev/null", O_WRONLY);      /* discard output, temporary */
670         if (gui->outfd < 0) {
671                 ast_log(LOG_WARNING, "Unable output fd\n");
672                 goto error;
673         }
674         return gui;
675
676 error:
677         ast_free(gui);
678         return NULL;
679 }
680
681 /* setup an sdl overlay and associated info, return 0 on success, != 0 on error */
682 static int set_win(SDL_Surface *screen, struct display_window *win, int fmt,
683         int w, int h, int x, int y)
684 {
685         win->bmp = SDL_CreateYUVOverlay(w, h, fmt, screen);
686         if (win->bmp == NULL)
687                 return -1;      /* error */
688         win->rect.x = x;
689         win->rect.y = y;
690         win->rect.w = w;
691         win->rect.h = h;
692         return 0;
693 }
694
695 static int keypad_cfg_read(struct gui_info *gui, const char *val);
696
697 static void keypad_setup(struct gui_info *gui, const char *kp_file)
698 {
699         FILE *fd;
700         char buf[1024];
701         const char region[] = "region";
702         int reg_len = strlen(region);
703         int in_comment = 0;
704
705         if (gui->keypad)
706                 return;
707         gui->keypad = load_image(kp_file);
708         if (!gui->keypad)
709                 return;
710         /* now try to read the keymap from the file. */
711         fd = fopen(kp_file, "r");
712         if (fd == NULL) {
713                 ast_log(LOG_WARNING, "fail to open %s\n", kp_file);
714                 return;
715         }
716         /*
717          * If the keypad image has a comment field, try to read
718          * the button location from there. The block must start with
719          * a comment (or empty) line, and continue with entries like:
720          *      region = token shape x0 y0 x1 y1 h
721          *      ...
722          * (basically, lines have the same format as config file entries).
723          * You can add it to a jpeg file using wrjpgcom
724          */
725         while (fgets(buf, sizeof(buf), fd)) {
726                 char *s;
727
728                 if (!strstr(buf, region)) { /* no keyword yet */
729                         if (!in_comment)        /* still waiting for initial comment block */
730                                 continue;
731                         else
732                                 break;
733                 }
734                 if (!in_comment) {      /* first keyword, reset previous entries */
735                         keypad_cfg_read(gui, "reset");
736                         in_comment = 1;
737                 }
738                 s = ast_skip_blanks(buf);
739                 ast_trim_blanks(s);
740                 if (memcmp(s, region, reg_len))
741                         break;  /* keyword not found */
742                 s = ast_skip_blanks(s + reg_len); /* space between token and '=' */
743                 if (*s++ != '=')        /* missing separator */
744                         break;
745                 if (*s == '>')  /* skip '>' if present */
746                         s++;
747                 keypad_cfg_read(gui, ast_skip_blanks(s));
748         }
749         fclose(fd);
750 }
751
752 /*! \brief [re]set the main sdl window, useful in case of resize.
753  * We can tell the first from subsequent calls from the value of
754  * env->gui, which is NULL the first time.
755  */
756 static void sdl_setup(struct video_desc *env)
757 {
758         int dpy_fmt = SDL_IYUV_OVERLAY; /* YV12 causes flicker in SDL */
759         int depth, maxw, maxh;
760         const SDL_VideoInfo *info;
761         int kp_w = 0, kp_h = 0; /* keypad width and height */
762         struct gui_info *gui = env->gui;
763
764         /*
765          * initialize the SDL environment. We have one large window
766          * with local and remote video, and a keypad.
767          * At the moment we arrange them statically, as follows:
768          * - on the left, the remote video;
769          * - on the center, the keypad
770          * - on the right, the local video
771          * We need to read in the skin for the keypad before creating the main
772          * SDL window, because the size is only known here.
773          */
774
775         if (gui == NULL && SDL_Init(SDL_INIT_VIDEO)) {
776                 ast_log(LOG_WARNING, "Could not initialize SDL - %s\n",
777                         SDL_GetError());
778                 /* again not fatal, just we won't display anything */
779                 return;
780         }
781         info = SDL_GetVideoInfo();
782         /* We want at least 16bpp to support YUV overlays.
783          * E.g with SDL_VIDEODRIVER = aalib the default is 8
784          */
785         depth = info->vfmt->BitsPerPixel;
786         if (depth < 16)
787                 depth = 16;
788         if (!gui)
789                 env->gui = gui = gui_init(env->keypad_file);
790         if (!gui)
791                 goto no_sdl;
792
793         if (gui->keypad) {
794                 if (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) {
795                         kp_w = gui->kp_rect.w;
796                         kp_h = gui->kp_rect.h;
797                 } else {
798                         kp_w = gui->keypad->w;
799                         kp_h = gui->keypad->h;
800                 }
801         }
802 #define BORDER  5       /* border around our windows */
803         maxw = env->rem_dpy.w + env->loc_dpy.w + kp_w;
804         maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h);
805         maxw += 4 * BORDER;
806         maxh += 2 * BORDER;
807         gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0);
808         if (!gui->screen) {
809                 ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n");
810                 goto no_sdl;
811         }
812
813         SDL_WM_SetCaption("Asterisk console Video Output", NULL);
814         if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt,
815                         env->rem_dpy.w, env->rem_dpy.h, BORDER, BORDER))
816                 goto no_sdl;
817         if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt,
818                         env->loc_dpy.w, env->loc_dpy.h,
819                         3*BORDER+env->rem_dpy.w + kp_w, BORDER))
820                 goto no_sdl;
821
822         /* display the skin, but do not free it as we need it later to
823          * restore text areas and maybe sliders too.
824          */
825         if (gui->keypad) {
826                 struct SDL_Rect *dest = &gui->win[WIN_KEYPAD].rect;
827                 struct SDL_Rect *src = (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) ? & gui->kp_rect : NULL;
828                 dest->x = 2*BORDER + env->rem_dpy.w;
829                 dest->y = BORDER;
830                 dest->w = kp_w;
831                 dest->h = kp_h;
832                 SDL_BlitSurface(gui->keypad, src, gui->screen, dest);
833                 SDL_UpdateRects(gui->screen, 1, dest);
834         }
835         return;
836
837 no_sdl:
838         /* free resources in case of errors */
839         env->gui = cleanup_sdl(gui);
840 }
841
842 /*
843  * Functions to determine if a point is within a region. Return 1 if success.
844  * First rotate the point, with
845  *      x' =  (x - x0) * cos A + (y - y0) * sin A
846  *      y' = -(x - x0) * sin A + (y - y0) * cos A
847  * where cos A = (x1-x0)/l, sin A = (y1 - y0)/l, and
848  *      l = sqrt( (x1-x0)^2 + (y1-y0)^2
849  * Then determine inclusion by simple comparisons i.e.:
850  *      rectangle: x >= 0 && x < l && y >= 0 && y < h
851  *      ellipse: (x-xc)^2/l^2 + (y-yc)^2/h2 < 1
852  */
853 static int kp_match_area(const struct keypad_entry *e, int x, int y)
854 {
855         double xp, dx = (e->x1 - e->x0);
856         double yp, dy = (e->y1 - e->y0);
857         double l = sqrt(dx*dx + dy*dy);
858         int ret = 0;
859
860         if (l > 1) { /* large enough */
861                 xp = ((x - e->x0)*dx + (y - e->y0)*dy)/l;
862                 yp = (-(x - e->x0)*dy + (y - e->y0)*dx)/l;
863                 if (e->type == KP_RECT) {
864                         ret = (xp >= 0 && xp < l && yp >=0 && yp < l);
865                 } else if (e->type == KP_CIRCLE) {
866                         dx = xp*xp/(l*l) + yp*yp/(e->h*e->h);
867                         ret = (dx < 1);
868                 }
869         }
870 #if 0
871         ast_log(LOG_WARNING, "result %d [%d] for match %d,%d in type %d p0 %d,%d p1 %d,%d h %d\n",
872                 ret, e->c, x, y, e->type, e->x0, e->y0, e->x1, e->y1, e->h);
873 #endif
874         return ret;
875 }
876
877 struct _s_k { const char *s; int k; };
878 static struct _s_k gui_key_map[] = {
879         {"PICK_UP",     KEY_PICK_UP },
880         {"PICKUP",      KEY_PICK_UP },
881         {"HANG_UP",     KEY_HANG_UP },
882         {"HANGUP",      KEY_HANG_UP },
883         {"MUTE",        KEY_MUTE },
884         {"FLASH",       KEY_FLASH },
885         {"AUTOANSWER",  KEY_AUTOANSWER },
886         {"SENDVIDEO",   KEY_SENDVIDEO },
887         {"LOCALVIDEO",  KEY_LOCALVIDEO },
888         {"REMOTEVIDEO", KEY_REMOTEVIDEO },
889         {"WRITEMESSAGE", KEY_WRITEMESSAGE },
890         {"GUI_CLOSE",   KEY_GUI_CLOSE },
891         {"KEYPAD",      KEY_KEYPAD },   /* x0 y0 w h - active area of the keypad */
892         {"FONT",        KEY_FONT },     /* x0 yo w h rows cols - location and format of the font */
893         {NULL, 0 } };
894
895 static int gui_map_token(const char *s)
896 {
897         /* map the string into token to be returned */
898         int i = atoi(s);
899         struct _s_k *p;
900         if (i > 0 || s[1] == '\0')      /* numbers or single characters */
901                 return (i > 9) ? i : s[0];
902         for (p = gui_key_map; p->s; p++) {
903                 if (!strcasecmp(p->s, s))
904                         return p->k;
905         }
906         return KEY_NONE;        /* not found */
907 }
908
909 /*! \brief read a keypad entry line in the format
910  *      reset
911  *      token circle xc yc diameter
912  *      token circle xc yc x1 y1 h      # ellipse, main diameter and height
913  *      token rect x0 y0 x1 y1 h        # rectangle with main side and eight
914  * token is the token to be returned, either a character or a symbol
915  * as KEY_* above
916  * Return 1 on success, 0 on error.
917  */
918 static int keypad_cfg_read(struct gui_info *gui, const char *val)
919 {
920         struct keypad_entry e;
921         char s1[16], s2[16];
922         int i, ret = 0; /* default, error */
923
924         if (gui == NULL || val == NULL)
925                 return 0;
926
927         s1[0] = s2[0] = '\0';
928         bzero(&e, sizeof(e));
929         i = sscanf(val, "%14s %14s %d %d %d %d %d",
930                 s1, s2, &e.x0, &e.y0, &e.x1, &e.y1, &e.h);
931
932         e.c = gui_map_token(s1);
933         if (e.c == KEY_NONE)
934                 return 0;       /* nothing found */
935         switch (i) {
936         default:
937                 break;
938         case 1: /* only "reset" is allowed */
939                 if (e.c != KEY_RESET)
940                         break;
941                 if (gui->kp)
942                         gui->kp_used = 0;
943                 break;
944         case 5:
945                 if (e.c == KEY_KEYPAD) {        /* active keypad area */
946                         gui->kp_rect.x = atoi(s2);
947                         gui->kp_rect.y = e.x0;
948                         gui->kp_rect.w = e.y0;
949                         gui->kp_rect.h = e.x1;
950                         break;
951                 }
952                 if (strcasecmp(s2, "circle"))   /* invalid */
953                         break;
954                 /* token circle xc yc diameter */
955                 e.h = e.x1;
956                 e.y1 = e.y0;    /* map radius in x1 y1 */
957                 e.x1 = e.x0 + e.h;      /* map radius in x1 y1 */
958                 e.x0 = e.x0 - e.h;      /* map radius in x1 y1 */
959                 /* fallthrough */
960
961         case 7:
962                 if (e.c == KEY_FONT) {  /* font - x0 y0 w h rows cols */
963                         ast_log(LOG_WARNING, "font not supported yet\n");
964                         break;
965                 }
966                 /* token circle|rect x0 y0 x1 y1 h */
967                 if (e.x1 < e.x0 || e.h <= 0) {
968                         ast_log(LOG_WARNING, "error in coordinates\n");
969                         e.type = 0;
970                         break;
971                 }
972                 if (!strcasecmp(s2, "circle")) {
973                         /* for a circle we specify the diameter but store center and radii */
974                         e.type = KP_CIRCLE;
975                         e.x0 = (e.x1 + e.x0) / 2;
976                         e.y0 = (e.y1 + e.y0) / 2;
977                         e.h = e.h / 2;
978                 } else if (!strcasecmp(s2, "rect")) {
979                         e.type = KP_RECT;
980                 } else
981                         break;
982                 ret = 1;
983         }
984         // ast_log(LOG_WARNING, "reading [%s] returns %d %d\n", val, i, ret);
985         if (ret == 0)
986                 return 0;
987         if (gui->kp_size == 0) {
988                 gui->kp = ast_calloc(10, sizeof(e));
989                 if (gui->kp == NULL) {
990                         ast_log(LOG_WARNING, "cannot allocate kp");
991                         return 0;
992                 }
993                 gui->kp_size = 10;
994         }
995         if (gui->kp_size == gui->kp_used) { /* must allocate */
996                 struct keypad_entry *a = ast_realloc(gui->kp, sizeof(e)*(gui->kp_size+10));
997                 if (a == NULL) {
998                         ast_log(LOG_WARNING, "cannot reallocate kp");
999                         return 0;
1000                 }
1001                 gui->kp = a;
1002                 gui->kp_size += 10;
1003         }
1004         if (gui->kp_size == gui->kp_used)
1005                 return 0;
1006         gui->kp[gui->kp_used++] = e;
1007         // ast_log(LOG_WARNING, "now %d regions\n", gui->kp_used);
1008         return 1;
1009 }
1010 #endif  /* HAVE_SDL */