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