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