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