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