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