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