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