(closes issue #13557)
[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) with 4 areas:
11 remote video on the left, local video on the right, keypad with all controls
12 and text windows in the center, and source device thumbnails on the top.
13 The top row is not displayed if no devices are specified in the config file.
14
15      ________________________________________________________________
16     |  ______   ______   ______   ______   ______   ______   ______  |
17     | | tn.1 | | tn.2 | | tn.3 | | tn.4 | | tn.5 | | tn.6 | | tn.7 | |
18     | |______| |______| |______| |______| |______| |______| |______| |
19     |  ______   ______   ______   ______   ______   ______   ______  |
20     | |______| |______| |______| |______| |______| |______| |______| |
21     |  _________________    __________________    _________________  |
22     | |                 |  |                  |  |                 | |
23     | |                 |  |                  |  |                 | |
24     | |                 |  |                  |  |                 | |
25     | |   remote video  |  |                  |  |   local video   | |
26     | |                 |  |                  |  |          ______ | |
27     | |                 |  |      keypad      |  |         |  PIP || |
28     | |                 |  |                  |  |         |______|| |
29     | |_________________|  |                  |  |_________________| |
30     |                      |                  |                      |
31     |                      |                  |                      |
32     |                      |__________________|                      |
33     |________________________________________________________________|
34
35
36 The central section is built using an image (jpg, png, maybe gif too)
37 for the skin, and other GUI elements.  Comments embedded in the image
38 indicate to what function each area is mapped to.
39 Another image (png with transparency) is used for the font.
40
41 Mouse and keyboard events are detected on the whole surface, and
42 handled differently according to their location:
43 - center/right click on the local/remote window are used to resize
44   the corresponding window;
45 - clicks on the thumbnail start/stop sources and select them as
46   primary or secondary video sources;
47 - drag on the local video window are used to move the captured
48   area (in the case of X11 grabber) or the picture-in-picture position;
49 - keystrokes on the keypad are mapped to the corresponding key;
50   keystrokes are used as keypad functions, or as text input
51   if we are in text-input mode.
52 - drag on some keypad areas (sliders etc.) are mapped to the
53   corresponding functions (mute/unmute audio and video,
54   enable/disable Picture-in-Picture, freeze the incoming video,
55   dial numbers, pick up or hang up a call, ...)
56
57 Configuration options control the appeareance of the gui:
58
59     keypad = /tmp/kpad2.jpg     ; the skin
60     keypad_font = /tmp/font.png ; the font to use for output
61
62 For future implementation, intresting features can be the following:
63 - save of the whole SDL window as a picture
64 - audio output device switching
65
66 The audio switching feature should allow changing the device
67 or switching to a recorded message for audio sent to remote party.
68 The selection of the device should happen clicking on a marker in the layout.
69 For this reason above the thumbnails row in the layout we would like a new row,
70 the elements composing the row could be message boards, reporting the name of the
71 device or the path of the message to be played.
72
73 For video input freeze and entire window capture, we define 2 new key types,
74 those should be activated pressing the buttons on the keypad, associated with
75 new regions inside the keypad pictureas comments
76
77
78  *
79  */
80
81 #include "asterisk.h"
82 #include "console_video.h"
83 #include "asterisk/lock.h"
84 #include "asterisk/frame.h"
85 #include "asterisk/utils.h"     /* ast_calloc and ast_realloc */
86 #include <math.h>               /* sqrt */
87
88 /* We use a maximum of 12 'windows' in the GUI */
89 enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_SRC1,
90         WIN_SRC2, WIN_SRC3, WIN_SRC4, WIN_SRC5,
91         WIN_SRC6, WIN_SRC7, WIN_SRC8, WIN_SRC9, WIN_MAX };
92
93 #ifndef HAVE_SDL        /* stubs if we don't have any sdl */
94 static void show_frame(struct video_desc *env, int out) {}
95 static void sdl_setup(struct video_desc *env)           {}
96 static struct gui_info *cleanup_sdl(struct gui_info* g, int n)  { return NULL; }
97 static void eventhandler(struct video_desc *env, const char *caption)   {}
98 static int keypad_cfg_read(struct gui_info *gui, const char *val)       { return 0; }
99
100 #else /* HAVE_SDL, the real rendering code */
101
102 #include <SDL/SDL.h>
103 #include <SDL/SDL_syswm.h>
104 #ifdef HAVE_SDL_IMAGE
105 #include <SDL/SDL_image.h>      /* for loading images */
106 #endif
107
108 #ifdef HAVE_X11
109 /* Need to hook into X for SDL_WINDOWID handling */
110 #include <X11/Xlib.h>
111 #endif
112
113 #define BORDER  5               /* border around our windows */
114 #define SRC_MSG_BD_H 20         /* height of the message board below those windows */
115
116 enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE };
117 struct keypad_entry {
118         int c;  /* corresponding character */
119         int x0, y0, x1, y1, h;  /* arguments */
120         enum kp_type type;
121 };
122
123 /* our representation of a displayed window. SDL can only do one main
124  * window so we map everything within that one
125  */
126 struct display_window {
127         SDL_Overlay     *bmp;
128         SDL_Rect        rect;   /* location of the window */
129 };
130
131 /* each thumbnail message board has a rectangle associated for the geometry,
132  * and a board structure, we include these two elements in a singole structure */
133 struct thumb_bd {
134         SDL_Rect                rect;           /* the rect for geometry and background */
135         struct board            *board;         /* the board */
136 };
137
138 struct gui_info {
139         enum kb_output          kb_output;      /* where the keyboard output goes */
140         struct drag_info        drag;           /* info on the window are we dragging */
141         /* support for display. */
142         SDL_Surface             *screen;        /* the main window */
143
144         int                     outfd;          /* fd for output */
145         SDL_Surface             *keypad;        /* the skin for the keypad */
146         SDL_Rect                kp_rect;        /* portion of the skin to display - default all */
147         SDL_Surface             *font;          /* font to be used */ 
148         SDL_Rect                font_rects[96]; /* only printable chars */
149
150         /* each of the following board has two rectangles,
151          * [0] is the geometry relative to the keypad,
152          * [1] is the geometry relative to the whole screen
153          * we do not use the thumb_bd for these boards because here we need
154          * 2 rectangles for geometry
155          */
156         SDL_Rect                kp_msg[2];              /* incoming msg, relative to kpad */
157         struct board            *bd_msg;
158
159         SDL_Rect                kp_edit[2];     /* edit user input */
160         struct board            *bd_edit;
161
162         SDL_Rect                kp_dialed[2];   /* dialed number */
163         struct board            *bd_dialed;
164
165         /* other boards are one associated with the source windows
166          * above the keypad in the layout, we only have the geometry
167          * relative to the whole screen
168          */
169         struct thumb_bd         thumb_bd_array[MAX_VIDEO_SOURCES];
170
171         /* variable-size array mapping keypad regions to functions */
172         int kp_size, kp_used;
173         struct keypad_entry *kp;
174
175         struct display_window   win[WIN_MAX];
176 };
177
178 /*! \brief free the resources in struct gui_info and the descriptor itself.
179  *  Return NULL so we can assign the value back to the descriptor in case.
180  */
181 static struct gui_info *cleanup_sdl(struct gui_info *gui, int device_num)
182 {
183         int i;
184
185         if (gui == NULL)
186                 return NULL;
187
188         /* unload font file */ 
189         if (gui->font) {
190                 SDL_FreeSurface(gui->font);
191                 gui->font = NULL; 
192         }
193
194         if (gui->outfd > -1)
195                 close(gui->outfd);
196         if (gui->keypad)
197                 SDL_FreeSurface(gui->keypad);
198         gui->keypad = NULL;
199         if (gui->kp)
200                 ast_free(gui->kp);
201
202         /* uninitialize the SDL environment */
203         for (i = 0; i < WIN_MAX; i++) {
204                 if (gui->win[i].bmp)
205                         SDL_FreeYUVOverlay(gui->win[i].bmp);
206         }
207         memset(gui, '\0', sizeof(gui));
208
209         /* deallocates the space allocated for the keypad message boards */
210         if (gui->bd_dialed)
211                 delete_board(gui->bd_dialed);
212         if (gui->bd_msg)
213                 delete_board(gui->bd_msg);
214
215         /* deallocates the space allocated for the thumbnail message boards */
216         for (i = 0; i < device_num; i++) {
217                 if (gui->thumb_bd_array[i].board) /* may be useless */
218                         delete_board(gui->thumb_bd_array[i].board);
219         }
220         
221         ast_free(gui);
222         SDL_Quit();
223         return NULL;
224 }
225
226 /* messages to be displayed in the sources message boards
227  * below the source windows
228  */
229
230 /* costants defined to describe status of devices */
231 #define IS_PRIMARY 1
232 #define IS_SECONDARY 2
233 #define IS_ON 4
234
235 char* src_msgs[] = {
236         "    OFF",
237         "1   OFF",
238         "  2 OFF",
239         "1+2 OFF",
240         "    ON",
241         "1   ON",
242         "  2 ON",
243         "1+2 ON",
244 };
245 /*
246  * Display video frames (from local or remote stream) using the SDL library.
247  * - Set the video mode to use the resolution specified by the codec context
248  * - Create a YUV Overlay to copy the frame into it;
249  * - After the frame is copied into the overlay, display it
250  *
251  * The size is taken from the configuration.
252  *
253  * 'out' is 0 for remote video, 1 for the local video
254  */
255 static void show_frame(struct video_desc *env, int out)
256 {
257         AVPicture *p_in, p_out;
258         struct fbuf_t *b_in, *b_out;
259         SDL_Overlay *bmp;
260         struct gui_info *gui = env->gui;
261
262         if (!gui)
263                 return;
264
265         if (out == WIN_LOCAL) { /* webcam/x11 to sdl */
266                 b_in = &env->enc_in;
267                 b_out = &env->loc_dpy;
268                 p_in = NULL;
269         } else if (out == WIN_REMOTE) {
270                 /* copy input format from the decoding context */
271                 AVCodecContext *c;
272                 if (env->in == NULL)    /* XXX should not happen - decoder not ready */
273                         return;
274                 c = env->in->dec_ctx;
275                 b_in = &env->in->dec_out;
276                 b_in->pix_fmt = c->pix_fmt;
277                 b_in->w = c->width;
278                 b_in->h = c->height;
279
280                 b_out = &env->rem_dpy;
281                 p_in = (AVPicture *)env->in->d_frame;
282         } else {
283                 int i = out-WIN_SRC1;
284                 b_in = env->out.devices[i].dev_buf;
285                 if (b_in == NULL)
286                         return;
287                 p_in = NULL;
288                 b_out = &env->src_dpy[i];
289         }               
290         bmp = gui->win[out].bmp;
291         SDL_LockYUVOverlay(bmp);
292         /* output picture info - this is sdl, YUV420P */
293         memset(&p_out, '\0', sizeof(p_out));
294         p_out.data[0] = bmp->pixels[0];
295         p_out.data[1] = bmp->pixels[1];
296         p_out.data[2] = bmp->pixels[2];
297         p_out.linesize[0] = bmp->pitches[0];
298         p_out.linesize[1] = bmp->pitches[1];
299         p_out.linesize[2] = bmp->pitches[2];
300
301         my_scale(b_in, p_in, b_out, &p_out);
302
303         /* lock to protect access to Xlib by different threads. */
304         SDL_DisplayYUVOverlay(bmp, &gui->win[out].rect);
305         SDL_UnlockYUVOverlay(bmp);
306 }
307
308 /*
309  * Identifiers for regions of the main window.
310  * Values between 0 and 127 correspond to ASCII characters.
311  * The corresponding strings to be used in the skin comment section
312  * are defined in gui_key_map.
313  */
314 enum skin_area {
315         /* answer/close functions */
316         KEY_PICK_UP = 128,
317         KEY_HANG_UP = 129,
318
319         KEY_MUTE = 130,
320         KEY_AUTOANSWER = 131,
321         KEY_SENDVIDEO = 132,
322         KEY_LOCALVIDEO = 133,
323         KEY_REMOTEVIDEO = 134,
324         KEY_FLASH = 136,
325
326         /* sensitive areas for the various text windows */
327         KEY_MESSAGEBOARD = 140,
328         KEY_DIALEDBOARD = 141,
329         KEY_EDITBOARD = 142,
330
331         KEY_GUI_CLOSE = 199,            /* close gui */
332         /* regions of the skin - displayed area, fonts, etc.
333          * XXX NOTE these are not sensitive areas.
334          */
335         KEY_KEYPAD = 200,               /* the keypad - default to the whole image */
336         KEY_FONT = 201,         /* the font. Maybe not really useful */
337         KEY_MESSAGE = 202,      /* area for incoming messages */
338         KEY_DIALED = 203,       /* area for dialed numbers */
339         KEY_EDIT = 204,         /* area for editing user input */
340
341 #ifdef notyet /* XXX for future implementation */
342         KEY_AUDIO_SRCS = 210,
343         /*indexes between 210 and 219 (or more) have been reserved for the "keys"
344         associated with the audio device markers, clicking on these markers
345         will change the source device for audio output */
346
347 #endif
348         /* Keys related to video sources */
349         KEY_FREEZE = 220,       /* freeze the incoming video */
350         KEY_CAPTURE = 221,      /* capture the whole SDL window as a picture */
351         KEY_PIP = 230,
352         /*indexes between 231 and 239 have been reserved for the "keys"
353         associated with the device thumbnails, clicking on these pictures
354         will change the source device for primary or secondary (PiP) video output*/
355         KEY_SRCS_WIN = 231, /* till 239 */
356         /* areas outside the keypad - simulated */
357         KEY_OUT_OF_KEYPAD = 241,
358         KEY_REM_DPY = 242,
359         KEY_LOC_DPY = 243,
360         KEY_RESET = 253,                /* the 'reset' keyword */
361         KEY_NONE = 254,                 /* invalid area */
362         KEY_DIGIT_BACKGROUND = 255,     /* other areas within the keypad */
363 };
364
365 /*
366  * Handlers for the various keypad functions
367  */
368
369 /* accumulate digits, possibly call dial if in connected mode */
370 static void keypad_digit(struct video_desc *env, int digit)
371 {       
372         if (env->owner) {               /* we have a call, send the digit */
373                 struct ast_frame f = { AST_FRAME_DTMF, 0 };
374
375                 f.subclass = digit;
376                 ast_queue_frame(env->owner, &f);
377         } else {                /* no call, accumulate digits */
378                 char buf[2] = { digit, '\0' };
379                 if (env->gui->bd_msg) /* XXX not strictly necessary ... */
380                         print_message(env->gui->bd_msg, buf);
381         }
382 }
383
384 /* function used to toggle on/off the status of some variables */
385 static char *keypad_toggle(struct video_desc *env, int index)
386 {
387         ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index);
388
389         switch (index) {
390         case KEY_SENDVIDEO: /* send or do not send video */
391                 env->out.sendvideo = !env->out.sendvideo;
392                 break;
393
394         case KEY_PIP: /* enable or disable Picture in Picture */
395                 env->out.picture_in_picture = !env->out.picture_in_picture;
396                 break;
397
398         case KEY_MUTE: /* send or do not send audio */
399                 ast_cli_command(env->gui->outfd, "console mute toggle");
400                 break;
401
402         case KEY_FREEZE: /* freeze/unfreeze the incoming frames */
403                 env->frame_freeze = !env->frame_freeze;
404                 break;
405
406 #ifdef notyet
407         case KEY_AUTOANSWER: {
408                 struct chan_oss_pvt *o = find_desc(oss_active);
409                 o->autoanswer = !o->autoanswer;
410                 }
411                 break;
412 #endif
413         }
414         return NULL;
415 }
416
417 char *console_do_answer(int fd);
418 /*
419  * Function called when the pick up button is pressed
420  * perform actions according the channel status:
421  *
422  *  - if no one is calling us and no digits was pressed,
423  *    the operation have no effects,
424  *  - if someone is calling us we answer to the call.
425  *  - if we have no call in progress and we pressed some
426  *    digit, send the digit to the console.
427  */
428 static void keypad_pick_up(struct video_desc *env)
429 {
430         struct gui_info *gui = env->gui;
431
432         ast_log(LOG_WARNING, "keypad_pick_up called\n");
433
434         if (env->owner) { /* someone is calling us, just answer */
435                 ast_cli_command(gui->outfd, "console answer");
436         } else { /* we have someone to call */
437                 char buf[160];
438                 const char *who = ast_skip_blanks(read_message(gui->bd_msg));
439                 buf[sizeof(buf) - 1] = '\0';
440                 snprintf(buf, sizeof(buf), "console dial %s", who);
441                 ast_log(LOG_WARNING, "doing <%s>\n", buf);
442                 print_message(gui->bd_dialed, "\n");
443                 print_message(gui->bd_dialed, who);
444                 reset_board(gui->bd_msg);
445                 ast_cli_command(gui->outfd, buf);
446         }
447 }
448
449 #if 0 /* still unused */
450 /*
451  * As an alternative to SDL_TTF, we can simply load the font from
452  * an image and blit characters on the background of the GUI.
453  *
454  * To generate a font we can use the 'fly' command with the
455  * following script (3 lines with 32 chars each)
456  
457 size 320,64
458 name font.png
459 transparent 0,0,0
460 string 255,255,255,  0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>?
461 string 255,255,255,  0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
462 string 255,255,255,  0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~
463 end
464
465  */
466
467 /* Print given text on the gui */
468 static int gui_output(struct video_desc *env, const char *text)
469 {
470         return 1;       /* error, not supported */
471 }
472 #endif 
473
474 static int video_geom(struct fbuf_t *b, const char *s);
475 static void sdl_setup(struct video_desc *env);
476 static int kp_match_area(const struct keypad_entry *e, int x, int y);
477
478 static void set_drag(struct drag_info *drag, int x, int y, enum drag_window win)
479 {
480         drag->x_start = x;
481         drag->y_start = y;
482         drag->drag_window = win;
483 }
484
485 static int update_device_info(struct video_desc *env, int i)
486 {
487         reset_board(env->gui->thumb_bd_array[i].board);
488         print_message(env->gui->thumb_bd_array[i].board,
489                 src_msgs[env->out.devices[i].status_index]);
490         return 0;
491 }
492
493 /*! \brief Changes the video output (local video) source, controlling if
494  * it is already using that video device, 
495  * and switching the correct fields of env->out.
496  * grabbers are always open and saved in the device table.
497  * The secondary or the primary device can be changed,
498  * according to the "button" parameter:
499  * the primary device is changed if button = SDL_BUTTON_LEFT;
500  * the secondary device is changed if button = not SDL_BUTTON_LEFT;
501  * 
502  * the correct message boards of the sources are also updated
503  * with the new status
504  * 
505  * \param env = pointer to the video environment descriptor
506  * \param index = index of the device the caller wants to use are primary or secondary device
507  * \param button = button clicked on the mouse
508  *
509  * returns 0 on success,
510  * returns 1 on error 
511  */
512 static int switch_video_out(struct video_desc *env, int index, Uint8 button)
513 {
514         int *p; /* pointer to the index of the device to select */
515
516         if (index >= env->out.device_num) {
517                 ast_log(LOG_WARNING, "no devices\n");
518                 return 1;
519         }
520         /* select primary or secondary */
521         p = (button == SDL_BUTTON_LEFT) ? &env->out.device_primary :
522                 &env->out.device_secondary;
523         /* controls if the device is already selected */
524         if (index == *p) {
525                 ast_log(LOG_WARNING, "device %s already selected\n", env->out.devices[index].name);
526                 return 0;
527         }
528         ast_log(LOG_WARNING, "switching to %s...\n", env->out.devices[index].name);
529         /* already open */
530         if (env->out.devices[index].grabber) {
531                 /* we also have to update the messages in the source 
532                 message boards below the source windows */
533                 /* first we update the board of the previous source */
534                 if (p == &env->out.device_primary)
535                         env->out.devices[*p].status_index &= ~IS_PRIMARY;
536                 else
537                         env->out.devices[*p].status_index &= ~IS_SECONDARY;
538                 update_device_info(env, *p);
539                 /* update the index used as primary or secondary */
540                 *p = index;
541                 ast_log(LOG_WARNING, "done\n");
542                 /* then we update the board of the new primary or secondary source */
543                 if (p == &env->out.device_primary)
544                         env->out.devices[*p].status_index |= IS_PRIMARY;
545                 else
546                         env->out.devices[*p].status_index |= IS_SECONDARY;
547                 update_device_info(env, *p);
548                 return 0;
549         }
550         /* device is off, just do nothing */
551         ast_log(LOG_WARNING, "device is down\n");
552         return 1;
553 }
554
555 /*! \brief tries to switch the state of a device from on to off or off to on
556  * we also have to update the status of the device and the correct message board
557  *
558  * \param index = the device that must be turned on or off
559  * \param env = pointer to the video environment descriptor
560  *
561  * returns:
562  * - 0 on falure switching from off to on
563  * - 1 on success in switching from off to on
564  * - 2 on success in switching from on to off
565 */
566 static int turn_on_off(int index, struct video_desc *env)
567 {
568         struct video_device *p = &env->out.devices[index];
569
570         if (index >= env->out.device_num) {
571                 ast_log(LOG_WARNING, "no devices\n");
572                 return 0;
573         }
574
575         if (!p->grabber) { /* device off */
576                 void *g_data; /* result of grabber_open() */
577                 struct grab_desc *g;
578                 int i;
579
580                 /* see if the device can be used by one of the existing drivers */
581                 for (i = 0; (g = console_grabbers[i]); i++) {
582                         /* try open the device */
583                         g_data = g->open(p->name, &env->out.loc_src_geometry, env->out.fps);
584                         if (!g_data)    /* no luck, try the next driver */
585                                 continue;
586                         p->grabber = g;
587                         p->grabber_data = g_data;
588                         /* update the status of the source */
589                         p->status_index |= IS_ON;
590                         /* print the new message in the message board */
591                         update_device_info(env, index);
592                         return 1; /* open succeded */
593                 }
594                 return 0; /* failure */
595         } else {
596                 /* the grabber must be closed */
597                 p->grabber_data = p->grabber->close(p->grabber_data);
598                 p->grabber = NULL;
599                 /* dev_buf is already freed by grabber->close() */
600                 p->dev_buf = NULL;
601                 /* update the status of the source */
602                 p->status_index &= ~IS_ON;
603                 /* print the new message in the message board */
604                 update_device_info(env, index);
605                 return 2; /* closed */
606         }       
607 }
608
609 /*
610  * Handle SDL_MOUSEBUTTONDOWN type, finding the palette
611  * index value and calling the right callback.
612  *
613  * x, y are referred to the upper left corner of the main SDL window.
614  */
615 static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button)
616 {
617         uint8_t index = KEY_OUT_OF_KEYPAD;      /* the key or region of the display we clicked on */
618         struct gui_info *gui = env->gui;
619                 
620         int i; /* integer variable used as iterator */
621
622         int x; /* integer variable usable as a container */
623         
624         /* total width of source device thumbnails */
625         int src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER;
626
627         /* x coordinate of the center of the keypad */
628         int x0 = MAX(env->rem_dpy.w+gui->keypad->w/2+2*BORDER, src_wins_tot_w/2);
629         
630 #if 0
631         ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n",
632                 button.x, button.y, gui->kp_used, gui->kp_size, gui->kp);
633 #endif
634         /* for each mousedown we end previous drag */
635         gui->drag.drag_window = DRAG_NONE;
636         
637         /* define keypad boundary */
638         /* XXX this should be extended for clicks on different audio device markers */
639         if (button.y >= (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0)) {
640                 /* if control reaches this point this means that the clicked point is
641                 below the row of the additional sources windows*/
642                 /* adjust the y coordinate as if additional devices windows were not present */
643                 button.y -= (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0);
644                 if (button.y < BORDER)
645                         index = KEY_OUT_OF_KEYPAD;
646                 else if (button.y >= MAX(MAX(env->rem_dpy.h, env->loc_dpy.h), gui->keypad->h))
647                         index = KEY_OUT_OF_KEYPAD;
648                 else if (button.x < x0 - gui->keypad->w/2 - BORDER - env->rem_dpy.w)
649                         index = KEY_OUT_OF_KEYPAD;
650                 else if (button.x < x0 - gui->keypad->w/2 - BORDER)
651                         index = KEY_REM_DPY;
652                 else if (button.x < x0 - gui->keypad->w/2)
653                         index = KEY_OUT_OF_KEYPAD;
654                 else if (button.x >= x0 + gui->keypad->w/2 + BORDER + env->loc_dpy.w)
655                         index = KEY_OUT_OF_KEYPAD;
656                 else if (button.x >= x0 + gui->keypad->w/2 + BORDER)
657                         index = KEY_LOC_DPY;
658                 else if (button.x >= x0 + gui->keypad->w/2)
659                         index = KEY_OUT_OF_KEYPAD;
660                 else if (gui->kp) {
661                         /* we have to calculate the first coordinate 
662                         inside the keypad before calling the kp_match_area*/
663                         int x_keypad = button.x - (x0 - gui->keypad->w/2);
664                         /* find the key clicked (if one was clicked) */
665                         for (i = 0; i < gui->kp_used; i++) {
666                                 if (kp_match_area(&gui->kp[i],x_keypad, button.y - BORDER)) {
667                                         index = gui->kp[i].c;
668                                         break;
669                                 }
670                         }
671                 }
672         } else if (button.y < BORDER) {
673                 index = KEY_OUT_OF_KEYPAD;
674         } else {  /* we are in the thumbnail area */
675                 x = x0 - src_wins_tot_w/2 + BORDER;
676                 if (button.y >= BORDER + SRC_WIN_H)
677                         index = KEY_OUT_OF_KEYPAD;
678                 else if (button.x < x)
679                         index = KEY_OUT_OF_KEYPAD;
680                 else if (button.x < x + src_wins_tot_w - BORDER) {
681                         /* note that the additional device windows 
682                         are numbered from left to right
683                         starting from 0, with a maximum of 8, the index associated on a click is:
684                         KEY_SRCS_WIN + number_of_the_window */
685                         for (i = 1; i <= env->out.device_num; i++) {
686                                 if (button.x < x+i*(SRC_WIN_W+BORDER)-BORDER) {
687                                         index = KEY_SRCS_WIN+i-1;
688                                         break;
689                                 } else if (button.x < x+i*(SRC_WIN_W+BORDER)) {
690                                         index = KEY_OUT_OF_KEYPAD;
691                                         break;
692                                 }
693                         }
694                 } else
695                         index = KEY_OUT_OF_KEYPAD;
696         }
697
698         /* exec the function */
699         if (index < 128) {      /* surely clicked on the keypad, don't care which key */
700                 keypad_digit(env, index);
701                 return;
702         }
703
704         else if (index >= KEY_SRCS_WIN && index < KEY_SRCS_WIN+env->out.device_num) {
705                 index -= KEY_SRCS_WIN; /* index of the window, equal to the device index in the table */
706                 /* if one of the additional device windows is clicked with
707                 left or right mouse button, we have to switch to that device */
708                 if (button.button == SDL_BUTTON_RIGHT || button.button == SDL_BUTTON_LEFT) {
709                         switch_video_out(env, index, button.button);
710                         return;
711                 }
712                 /* turn on or off the devices selectively with other mouse buttons */
713                 else {
714                         int ret = turn_on_off(index, env);
715                         /* print a message according to what happened */
716                         if (!ret)
717                                 ast_log(LOG_WARNING, "unable to turn on device %s\n",
718                                         env->out.devices[index].name);
719                         else if (ret == 1)
720                                 ast_log(LOG_WARNING, "device %s changed state to on\n",
721                                         env->out.devices[index].name);
722                         else if (ret == 2)
723                                 ast_log(LOG_WARNING, "device %s changed state to off\n",
724                                         env->out.devices[index].name);
725                         return;
726                 }
727         }
728
729         /* XXX for future implementation
730         else if (click on audio source marker)
731                 change audio source device
732         */
733
734         switch (index) {
735         /* answer/close function */
736         case KEY_PICK_UP:
737                 keypad_pick_up(env);
738                 break;
739         case KEY_HANG_UP:
740                 ast_cli_command(gui->outfd, "console hangup");
741                 break;
742
743         /* other functions */
744         case KEY_MUTE: /* send or not send the audio */
745         case KEY_AUTOANSWER:
746         case KEY_SENDVIDEO: /* send or not send the video */
747         case KEY_PIP: /* activate/deactivate picture in picture mode */
748         case KEY_FREEZE: /* freeze/unfreeze the incoming video */
749                 keypad_toggle(env, index);
750                 break;
751
752         case KEY_LOCALVIDEO:
753                 break;
754         case KEY_REMOTEVIDEO:
755                 break;
756
757 #ifdef notyet /* XXX for future implementations */
758         case KEY_CAPTURE:
759                 break;
760 #endif
761
762         case KEY_MESSAGEBOARD:
763                 if (button.button == SDL_BUTTON_LEFT)
764                         set_drag(&gui->drag, button.x, button.y, DRAG_MESSAGE);
765                 break;
766
767         /* press outside the keypad. right increases size, center decreases, left drags */
768         case KEY_LOC_DPY:
769         case KEY_REM_DPY:
770                 if (button.button == SDL_BUTTON_LEFT) {
771                         /* values used to find the position of the picture in picture (if present) */
772                         int pip_loc_x = (double)env->out.pip_x/env->enc_in.w * env->loc_dpy.w;
773                         int pip_loc_y = (double)env->out.pip_y/env->enc_in.h * env->loc_dpy.h;
774                         /* check if picture in picture is active and the click was on it */
775                         if (index == KEY_LOC_DPY && env->out.picture_in_picture &&
776                           button.x >= x0+gui->keypad->w/2+BORDER+pip_loc_x &&
777                           button.x < x0+gui->keypad->w/2+BORDER+pip_loc_x+env->loc_dpy.w/3 &&
778                           button.y >= BORDER+pip_loc_y && 
779                           button.y < BORDER+pip_loc_y+env->loc_dpy.h/3) {
780                                 /* set the y cordinate to his previous value */
781                                 button.y += (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0);
782                                 /* starts dragging the picture inside the picture */
783                                 set_drag(&gui->drag, button.x, button.y, DRAG_PIP);
784                         }
785                         else if (index == KEY_LOC_DPY) {
786                                 /* set the y cordinate to his previous value */
787                                 button.y += (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0);
788                                 /* click in the local display, but not on the PiP */
789                                 set_drag(&gui->drag, button.x, button.y, DRAG_LOCAL);
790                         }
791                         break;
792                 } else {
793                         char buf[128];
794                         struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->loc_dpy : &env->rem_dpy;
795                         sprintf(buf, "%c%dx%d", button.button == SDL_BUTTON_RIGHT ? '>' : '<',
796                                 fb->w, fb->h);
797                         video_geom(fb, buf);
798                         sdl_setup(env);
799                         /* writes messages in the source boards, those can be 
800                         modified during the execution, because of the events 
801                         this must be done here, otherwise the status of sources will not be
802                         shown after sdl_setup */
803                         for (i = 0; i < env->out.device_num; i++) {
804                                 update_device_info(env, i);
805                         }
806                         /* we also have to refresh other boards, 
807                         to avoid messages to disappear after video resize */
808                         print_message(gui->bd_msg, " \b");
809                         print_message(gui->bd_dialed, " \b");
810                 }
811                 break;
812         case KEY_OUT_OF_KEYPAD:
813                 ast_log(LOG_WARNING, "nothing clicked, coordinates: %d, %d\n", button.x, button.y);
814                 break;
815
816         case KEY_DIGIT_BACKGROUND:
817                 break;
818
819         default:
820                 ast_log(LOG_WARNING, "function not yet defined %i\n", index);
821         }
822 }
823
824 /*
825  * Handle SDL_KEYDOWN type event, put the key pressed
826  * in the dial buffer or in the text-message buffer,
827  * depending on the text_mode variable value.
828  *
829  * key is the SDLKey structure corresponding to the key pressed.
830  * Note that SDL returns modifiers (ctrl, shift, alt) as independent
831  * information so the key itself is not enough and we need to
832  * use a translation table, below - one line per entry,
833  * plain, shift, ctrl, ... using the first char as key.
834  */
835 static const char *us_kbd_map[] = {
836         "`~", "1!", "2@", "3#", "4$", "5%", "6^",
837         "7&", "8*", "9(", "0)", "-_", "=+", "[{",
838         "]}", "\\|", ";:", "'\"", ",<", ".>", "/?",
839         "jJ\n",
840         NULL
841 };
842
843 static char map_key(SDL_keysym *ks)
844 {
845         const char *s, **p = us_kbd_map;
846         int c = ks->sym;
847
848         if (c == '\r')  /* map cr into lf */
849                 c = '\n';
850         if (c >= SDLK_NUMLOCK && c <= SDLK_COMPOSE)
851                 return 0;       /* only a modifier */
852         if (ks->mod == 0)
853                 return c;
854         while ((s = *p) && s[0] != c)
855                 p++;
856         if (s) { /* see if we have a modifier and a chance to use it */
857                 int l = strlen(s), mod = 0;
858                 if (l > 1)
859                         mod |= (ks->mod & KMOD_SHIFT) ? 1 : 0;
860                 if (l > 2 + mod)
861                         mod |= (ks->mod & KMOD_CTRL) ? 2 : 0;
862                 if (l > 4 + mod)
863                         mod |= (ks->mod & KMOD_ALT) ? 4 : 0;
864                 c = s[mod];
865         }
866         if (ks->mod & (KMOD_CAPS|KMOD_SHIFT) && c >= 'a' && c <='z')
867                 c += 'A' - 'a';
868         return c;
869 }
870
871 static void handle_keyboard_input(struct video_desc *env, SDL_keysym *ks)
872 {
873         char buf[2] = { map_key(ks), '\0' };
874         struct gui_info *gui = env->gui;
875         if (buf[0] == 0)        /* modifier ? */
876                 return;
877         switch (gui->kb_output) {
878         default:
879                 break;
880         case KO_INPUT:  /* to be completed */
881                 break;
882         case KO_MESSAGE:
883                 if (gui->bd_msg) {
884                         print_message(gui->bd_msg, buf);
885                         if (buf[0] == '\r' || buf[0] == '\n') {
886                                 keypad_pick_up(env);
887                         }
888                 }
889                 break;
890
891         case KO_DIALED: /* to be completed */
892                 break;
893         }
894
895         return;
896 }
897
898 static void grabber_move(struct video_device *, int dx, int dy);
899
900 int compute_drag(int *start, int end, int magnifier);
901 int compute_drag(int *start, int end, int magnifier)
902 {
903         int delta = end - *start;
904 #define POLARITY -1
905         /* add a small quadratic term */
906         delta += delta * delta * (delta > 0 ? 1 : -1 )/100;
907         delta *= POLARITY * magnifier;
908 #undef POLARITY
909         *start = end;
910         return delta;
911 }
912
913 /*! \brief This function moves the picture in picture,
914  * controlling the limits of the containing buffer
915  * to avoid problems deriving from going through the limits.
916  *
917  * \param env = pointer to the descriptor of the video environment
918  * \param dx = the variation of the x position
919  * \param dy = the variation of the y position
920 */
921 static void pip_move(struct video_desc* env, int dx, int dy) {
922         int new_pip_x = env->out.pip_x+dx;
923         int new_pip_y = env->out.pip_y+dy;
924         /* going beyond the left borders */
925         if (new_pip_x < 0)
926                 new_pip_x = 0;
927         /* going beyond the right borders */
928         else if (new_pip_x > env->enc_in.w - env->enc_in.w/3)
929                 new_pip_x = env->enc_in.w - env->enc_in.w/3;
930         /* going beyond the top borders */
931         if (new_pip_y < 0)
932                 new_pip_y = 0;
933         /* going beyond the bottom borders */
934         else if (new_pip_y > env->enc_in.h - env->enc_in.h/3)
935                 new_pip_y = env->enc_in.h - env->enc_in.h/3;
936         env->out.pip_x = new_pip_x;
937         env->out.pip_y = new_pip_y;
938 }
939
940 /*
941  * I am seeing some kind of deadlock or stall around
942  * SDL_PumpEvents() while moving the window on a remote X server
943  * (both xfree-4.4.0 and xorg 7.2)
944  * and windowmaker. It is unclear what causes it.
945  */
946
947 /*! \brief refresh the screen, and also grab a bunch of events.
948  */
949 static void eventhandler(struct video_desc *env, const char *caption)
950 {
951         struct gui_info *gui = env->gui;
952         struct drag_info *drag;
953 #define N_EVENTS        32
954         int i, n;
955         SDL_Event ev[N_EVENTS];
956
957         if (!gui)
958                 return;
959         drag = &gui->drag;
960         if (caption)
961                 SDL_WM_SetCaption(caption, NULL);
962
963 #define MY_EV (SDL_MOUSEBUTTONDOWN|SDL_KEYDOWN)
964         while ( (n = SDL_PeepEvents(ev, N_EVENTS, SDL_GETEVENT, SDL_ALLEVENTS)) > 0) {
965                 for (i = 0; i < n; i++) {
966 #if 0
967                         ast_log(LOG_WARNING, "------ event %d at %d %d\n",
968                                 ev[i].type,  ev[i].button.x,  ev[i].button.y);
969 #endif
970                         switch (ev[i].type) {
971                         default:
972                                 ast_log(LOG_WARNING, "------ event %d at %d %d\n",
973                                         ev[i].type,  ev[i].button.x,  ev[i].button.y);
974                                 break;
975
976                         case SDL_ACTIVEEVENT:
977 #if 0 /* do not react, we don't want to die because the window is minimized */
978                                 if (ev[i].active.gain == 0 && ev[i].active.state & SDL_APPACTIVE) {
979                                         ast_log(LOG_WARNING, "/* somebody has killed us ? */");
980                                         ast_cli_command(gui->outfd, "stop now");
981                                 }
982 #endif
983                                 break;
984
985                         case SDL_KEYUP: /* ignore, for the time being */
986                                 break;
987
988                         case SDL_KEYDOWN:
989                                 handle_keyboard_input(env, &ev[i].key.keysym);
990                                 break;
991
992                         case SDL_MOUSEMOTION:
993                         case SDL_MOUSEBUTTONUP:
994                                 if (drag->drag_window == DRAG_LOCAL && env->out.device_num) {
995                                         /* move the capture source */
996                                         int dx = compute_drag(&drag->x_start, ev[i].motion.x, 3);
997                                         int dy = compute_drag(&drag->y_start, ev[i].motion.y, 3);
998                                         grabber_move(&env->out.devices[env->out.device_primary], dx, dy);
999                                 } else if (drag->drag_window == DRAG_PIP) {
1000                                         /* move the PiP image inside the frames of the enc_in buffers */
1001                                         int dx = ev[i].motion.x - drag->x_start;
1002                                         int dy = ev[i].motion.y - drag->y_start;
1003                                         /* dx and dy value are directly applied to env->out.pip_x and
1004                                         env->out.pip_y, so they must work as if the format was cif */
1005                                         dx = (double)dx*env->enc_in.w/env->loc_dpy.w;
1006                                         dy = (double)dy*env->enc_in.h/env->loc_dpy.h;
1007                                         /* sets starts to a new value */
1008                                         drag->x_start = ev[i].motion.x;
1009                                         drag->y_start = ev[i].motion.y;
1010                                         /* ast_log(LOG_WARNING, "moving: %d, %d\n", dx, dy); */
1011                                         pip_move(env, dx, dy);
1012                                 } else if (drag->drag_window == DRAG_MESSAGE) {
1013                                         /* scroll up/down the window */
1014                                         int dy = compute_drag(&drag->y_start, ev[i].motion.y, 1);
1015                                         move_message_board(gui->bd_msg, dy);
1016                                 }
1017                                 if (ev[i].type == SDL_MOUSEBUTTONUP)
1018                                         drag->drag_window = DRAG_NONE;
1019                                 break;
1020                         case SDL_MOUSEBUTTONDOWN:
1021                                 handle_mousedown(env, ev[i].button);
1022                                 break;
1023                         }
1024                 }
1025         }
1026         if (1) {
1027                 struct timeval b, a = ast_tvnow();
1028                 int i;
1029                 //SDL_Lock_EventThread();
1030                 SDL_PumpEvents();
1031                 b = ast_tvnow();
1032                 i = ast_tvdiff_ms(b, a);
1033                 if (i > 3)
1034                         fprintf(stderr, "-------- SDL_PumpEvents took %dms\n", i);
1035                 //SDL_Unlock_EventThread();
1036         }
1037 }
1038
1039 static SDL_Surface *load_image(const char *file)
1040 {
1041         SDL_Surface *temp;
1042  
1043 #ifdef HAVE_SDL_IMAGE
1044         temp = IMG_Load(file);
1045 #else
1046         temp = SDL_LoadBMP(file);
1047 #endif
1048         if (temp == NULL)
1049                 fprintf(stderr, "Unable to load image %s: %s\n",
1050                         file, SDL_GetError());
1051         return temp;
1052 }
1053
1054 static void keypad_setup(struct gui_info *gui, const char *kp_file);
1055
1056 /* TODO: consistency checks, check for bpp, widht and height */
1057 /* Init the mask image used to grab the action. */
1058 static struct gui_info *gui_init(const char *keypad_file, const char *font)
1059 {
1060         struct gui_info *gui = ast_calloc(1, sizeof(*gui));
1061
1062         if (gui == NULL)
1063                 return NULL;
1064         /* initialize keypad status */
1065         gui->kb_output = KO_MESSAGE;    /* XXX temp */
1066         gui->drag.drag_window = DRAG_NONE;
1067         gui->outfd = -1;
1068
1069         keypad_setup(gui, keypad_file);
1070         if (gui->keypad == NULL)        /* no keypad, we are done */
1071                 return gui;
1072         /* XXX load image */
1073         if (!ast_strlen_zero(font)) {
1074                 int i;
1075                 SDL_Rect *r;
1076
1077                 gui->font = load_image(font);
1078                 if (!gui->font) {
1079                         ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", font);
1080                         goto error;
1081                 }
1082                 ast_log(LOG_WARNING, "Loaded font %s\n", font);
1083                 /* XXX hardwired constants - 3 rows of 32 chars */
1084                 r = gui->font_rects;
1085 #define FONT_H 20
1086 #define FONT_W 9
1087                 for (i = 0; i < 96; r++, i++) {
1088                         r->x = (i % 32 ) * FONT_W;
1089                         r->y = (i / 32 ) * FONT_H;
1090                         r->w = FONT_W;
1091                         r->h = FONT_H;
1092                 }
1093         }
1094
1095         gui->outfd = open ("/dev/null", O_WRONLY);      /* discard output, temporary */
1096         if (gui->outfd < 0) {
1097                 ast_log(LOG_WARNING, "Unable output fd\n");
1098                 goto error;
1099         }
1100         return gui;
1101
1102 error:
1103         ast_free(gui);
1104         return NULL;
1105 }
1106
1107 /* setup an sdl overlay and associated info, return 0 on success, != 0 on error */
1108 static int set_win(SDL_Surface *screen, struct display_window *win, int fmt,
1109         int w, int h, int x, int y)
1110 {
1111         win->bmp = SDL_CreateYUVOverlay(w, h, fmt, screen);
1112         if (win->bmp == NULL)
1113                 return -1;      /* error */
1114         win->rect.x = x;
1115         win->rect.y = y;
1116         win->rect.w = w;
1117         win->rect.h = h;
1118         return 0;
1119 }
1120
1121 static int keypad_cfg_read(struct gui_info *gui, const char *val);
1122
1123 static void keypad_setup(struct gui_info *gui, const char *kp_file)
1124 {
1125         FILE *fd;
1126         char buf[1024];
1127         const char region[] = "region";
1128         int reg_len = strlen(region);
1129         int in_comment = 0;
1130
1131         if (gui->keypad)
1132                 return;
1133         gui->keypad = load_image(kp_file);
1134         if (!gui->keypad)
1135                 return;
1136         /* now try to read the keymap from the file. */
1137         fd = fopen(kp_file, "r");
1138         if (fd == NULL) {
1139                 ast_log(LOG_WARNING, "fail to open %s\n", kp_file);
1140                 return;
1141         }
1142         /*
1143          * If the keypad image has a comment field, try to read
1144          * the button location from there. The block must start with
1145          * a comment (or empty) line, and continue with entries like:
1146          *      region = token shape x0 y0 x1 y1 h
1147          *      ...
1148          * (basically, lines have the same format as config file entries).
1149          * You can add it to a jpeg file using wrjpgcom
1150          */
1151         while (fgets(buf, sizeof(buf), fd)) {
1152                 char *s;
1153
1154                 if (!strstr(buf, region)) { /* no keyword yet */
1155                         if (!in_comment)        /* still waiting for initial comment block */
1156                                 continue;
1157                         else
1158                                 break;
1159                 }
1160                 if (!in_comment) {      /* first keyword, reset previous entries */
1161                         keypad_cfg_read(gui, "reset");
1162                         in_comment = 1;
1163                 }
1164                 s = ast_skip_blanks(buf);
1165                 ast_trim_blanks(s);
1166                 if (memcmp(s, region, reg_len))
1167                         break;  /* keyword not found */
1168                 s = ast_skip_blanks(s + reg_len); /* space between token and '=' */
1169                 if (*s++ != '=')        /* missing separator */
1170                         break;
1171                 if (*s == '>')  /* skip '>' if present */
1172                         s++;
1173                 keypad_cfg_read(gui, ast_skip_blanks(s));
1174         }
1175         fclose(fd);
1176 }
1177
1178 struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest,
1179         SDL_Surface *font, SDL_Rect *font_rects);
1180
1181 /*! \brief initialize the boards we have in the keypad */
1182 static void init_board(struct gui_info *gui, struct board **dst, SDL_Rect *r, int dx, int dy)
1183 {
1184         if (r[0].w == 0 || r[0].h == 0)
1185                 return; /* not available */
1186         r[1] = r[0];    /* copy geometry */
1187         r[1].x += dx;   /* add offset of main window */
1188         r[1].y += dy;
1189         if (*dst == NULL) {     /* initial call */
1190                 *dst = board_setup(gui->screen, &r[1], gui->font, gui->font_rects);
1191         } else {
1192                 /* call a refresh */
1193         }
1194 }
1195
1196 #ifdef HAVE_X11
1197 /*
1198  * SDL is not very robust on error handling, so we need to trap ourselves
1199  * at least the most obvious failure conditions, e.g. a bad SDL_WINDOWID.
1200  * As of sdl-1.2.13, SDL_SetVideoMode crashes with bad parameters, so
1201  * we need to do the explicit X calls to make sure the window is correct.
1202  * And around these calls, we must trap X errors.
1203  */
1204 static int my_x_handler(Display *d, XErrorEvent *e)
1205 {
1206         ast_log(LOG_WARNING, "%s error_code %d\n", __FUNCTION__, e->error_code);
1207         return 0;
1208 }
1209 #endif /* HAVE_X11 */
1210
1211 /*! \brief [re]set the main sdl window, useful in case of resize.
1212  * We can tell the first from subsequent calls from the value of
1213  * env->gui, which is NULL the first time.
1214  */
1215 static void sdl_setup(struct video_desc *env)
1216 {
1217         int dpy_fmt = SDL_IYUV_OVERLAY; /* YV12 causes flicker in SDL */
1218         int depth, maxw, maxh;
1219         const SDL_VideoInfo *info;
1220         int kp_w = 0, kp_h = 0; /* keypad width and height */
1221         struct gui_info *gui = env->gui;
1222         
1223         /* Some helper variables used for filling the SDL window */
1224         int x0; /* the x coordinate of the center of the keypad */
1225         int x1; /* userful for calculating of the size of the parent window */
1226         int y0; /* y coordinate of the keypad, the remote window and the local window */
1227         int src_wins_tot_w; /* total width of the source windows */
1228         int i;
1229         int x; /* useful for the creation of the source windows; */
1230         
1231 #ifdef HAVE_X11
1232         const char *e = getenv("SDL_WINDOWID");
1233
1234         if (!ast_strlen_zero(e)) {
1235                 XWindowAttributes a;
1236                 int (*old_x_handler)(Display *d, XErrorEvent *e) = XSetErrorHandler(my_x_handler);
1237                 Display *d = XOpenDisplay(getenv("DISPLAY"));
1238                 long w = atol(e);
1239                 int success = w ? XGetWindowAttributes(d, w, &a) : 0;
1240
1241                 XSetErrorHandler(old_x_handler);
1242                 if (!success) {
1243                         ast_log(LOG_WARNING, "%s error in window\n", __FUNCTION__);
1244                         return;
1245                 }
1246         }       
1247 #endif
1248         /*
1249          * initialize the SDL environment. We have one large window
1250          * with local and remote video, and a keypad.
1251          * At the moment we arrange them statically, as follows:
1252          * - top row: thumbnails for local video sources;
1253          * - next row: message boards for local video sources
1254          * - on the left, the remote video;
1255          * - on the center, the keypad
1256          * - on the right, the local video
1257          * We need to read in the skin for the keypad before creating the main
1258          * SDL window, because the size is only known here.
1259          */
1260
1261         if (gui == NULL && SDL_Init(SDL_INIT_VIDEO)) {
1262                 ast_log(LOG_WARNING, "Could not initialize SDL - %s\n",
1263                         SDL_GetError());
1264                 /* again not fatal, just we won't display anything */
1265                 return;
1266         }
1267         info = SDL_GetVideoInfo();
1268         /* We want at least 16bpp to support YUV overlays.
1269          * E.g with SDL_VIDEODRIVER = aalib the default is 8
1270          */
1271         if (!info || !info->vfmt) {
1272                 ast_log(LOG_WARNING, "Bad SDL_GetVideoInfo - %s\n",
1273                         SDL_GetError());
1274                 return;
1275         }
1276         depth = info->vfmt->BitsPerPixel;
1277         if (depth < 16)
1278                 depth = 16;
1279         if (!gui)
1280                 env->gui = gui = gui_init(env->keypad_file, env->keypad_font);
1281         if (!gui)
1282                 goto no_sdl;
1283
1284         if (gui->keypad) {
1285                 if (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) {
1286                         kp_w = gui->kp_rect.w;
1287                         kp_h = gui->kp_rect.h;
1288                 } else {
1289                         kp_w = gui->keypad->w;
1290                         kp_h = gui->keypad->h;
1291                 }
1292         }
1293         
1294         /* total width of the thumbnails */
1295         src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER;
1296         
1297         /* x coordinate of the center of the keypad */
1298         x0 = MAX(env->rem_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2);
1299         
1300         /* from center of the keypad to right border */
1301         x1 = MAX(env->loc_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2);
1302         
1303         /* total width of the SDL window to create */
1304         maxw = x0+x1;
1305         
1306         /* total height of the mother window to create */
1307         maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h)+2*BORDER;
1308         maxh += env->out.device_num ? (2*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : 0;
1309         
1310         gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0);
1311         if (!gui->screen) {
1312                 ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n");
1313                 goto no_sdl;
1314         }
1315
1316 #ifdef HAVE_X11
1317         /*
1318          * Annoying as it may be, if SDL_WINDOWID is set, SDL does
1319          * not grab keyboard/mouse events or expose or other stuff,
1320          * and it does not handle resize either.
1321          * So we need to implement workarounds here.
1322          */
1323     do {
1324         /* First, handle the event mask */
1325         XWindowAttributes attr;
1326         long want;
1327         SDL_SysWMinfo info;
1328         Display *SDL_Display;
1329         Window win;
1330
1331         const char *e = getenv("SDL_WINDOWID");
1332         if (ast_strlen_zero(e))  /* no external window, don't bother doing this */
1333                 break;
1334         SDL_VERSION(&info.version); /* it is important to set the version */
1335         if (SDL_GetWMInfo(&info) != 1) {
1336                 fprintf(stderr, "no wm info\n");
1337                 break;
1338         }
1339         SDL_Display = info.info.x11.display;
1340         if (SDL_Display == NULL)
1341                 break;
1342         win = info.info.x11.window;
1343
1344         /*
1345          * A list of events we want.
1346          * Leave ResizeRedirectMask to the parent.
1347          */
1348         want = KeyPressMask | KeyReleaseMask | ButtonPressMask |
1349                            ButtonReleaseMask | EnterWindowMask |
1350                            LeaveWindowMask | PointerMotionMask |
1351                            Button1MotionMask |
1352                            Button2MotionMask | Button3MotionMask |
1353                            Button4MotionMask | Button5MotionMask |
1354                            ButtonMotionMask | KeymapStateMask |
1355                            ExposureMask | VisibilityChangeMask |
1356                            StructureNotifyMask | /* ResizeRedirectMask | */
1357                            SubstructureNotifyMask | SubstructureRedirectMask |
1358                            FocusChangeMask | PropertyChangeMask |
1359                            ColormapChangeMask | OwnerGrabButtonMask;
1360
1361         memset(&attr, '\0', sizeof(attr));
1362         XGetWindowAttributes(SDL_Display, win, &attr);
1363
1364         /* the following events can be delivered only to one client.
1365          * So check which ones are going to someone else, and drop
1366          * them from our request.
1367          */
1368         {
1369         /* ev are the events for a single recipient */
1370         long ev = ButtonPressMask | ResizeRedirectMask |
1371                         SubstructureRedirectMask;
1372         ev &= (attr.all_event_masks & ~attr.your_event_mask);
1373         /* now ev contains 1 for single-recipient events owned by others.
1374          * We must clear those bits in 'want'
1375          * and then add the bits in 'attr.your_event_mask' to 'want'
1376          */
1377         want &= ~ev;
1378         want |= attr.your_event_mask;
1379         }
1380         XSelectInput(SDL_Display, win, want);
1381
1382         /* Second, handle resize.
1383          * We do part of the things that X11Resize does,
1384          * but also generate a ConfigureNotify event so
1385          * the owner of the window has a chance to do something
1386          * with it.
1387          */
1388         XResizeWindow(SDL_Display, win, maxw, maxh);
1389         {
1390         XConfigureEvent ce = {
1391                 .type = ConfigureNotify,
1392                 .serial = 0,
1393                 .send_event = 1,        /* TRUE */
1394                 .display = SDL_Display,
1395                 .event = win,
1396                 .window = win,
1397                 .x = 0,
1398                 .y = 0,
1399                 .width = maxw,
1400                 .height = maxh,
1401                 .border_width = 0,
1402                 .above = 0,
1403                 .override_redirect = 0 };
1404         XSendEvent(SDL_Display, win, 1 /* TRUE */, StructureNotifyMask, (XEvent *)&ce);
1405         }
1406     } while (0);
1407 #endif /* HAVE_X11 */
1408
1409         y0 = env->out.device_num ? (3*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : BORDER;
1410         
1411         SDL_WM_SetCaption("Asterisk console Video Output", NULL);
1412         
1413         /* intialize the windows for local and remote video */
1414         if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt,
1415                         env->rem_dpy.w, env->rem_dpy.h, x0-kp_w/2-BORDER-env->rem_dpy.w, y0))
1416                 goto no_sdl;
1417         /* unfreeze incoming frames if set (to avoid showing nothing) */
1418         env->frame_freeze = 0;
1419
1420         if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt,
1421                         env->loc_dpy.w, env->loc_dpy.h,
1422                         x0+kp_w/2+BORDER, y0))
1423                 goto no_sdl;
1424         
1425         /* initialize device_num source windows (thumbnails) and boards
1426         (for a maximum of 9 additional windows and boards) */
1427         x = x0 - src_wins_tot_w/2 + BORDER;
1428         for (i = 0; i < env->out.device_num; i++){
1429                 struct thumb_bd *p = &gui->thumb_bd_array[i];
1430                 if (set_win(gui->screen, &gui->win[i+WIN_SRC1], dpy_fmt,
1431                         SRC_WIN_W, SRC_WIN_H, x+i*(BORDER+SRC_WIN_W), BORDER))
1432                         goto no_sdl;
1433                 /* set geometry for the rect for the message board of the device */
1434                 p->rect.w = SRC_WIN_W;
1435                 p->rect.h = SRC_MSG_BD_H;
1436                 p->rect.x = x+i*(BORDER+SRC_WIN_W);
1437                 p->rect.y = 2*BORDER+SRC_WIN_H;
1438                 /* the white color is used as background */
1439                 SDL_FillRect(gui->screen, &p->rect,
1440                         SDL_MapRGB(gui->screen->format, 255, 255, 255));
1441                 /* if necessary, initialize boards for the sources */
1442                 if (!p->board)
1443                         p->board =
1444                                 board_setup(gui->screen, &p->rect,
1445                                 gui->font, gui->font_rects);
1446                 /* update board rect */
1447                 SDL_UpdateRect(gui->screen, p->rect.x, p->rect.y, p->rect.w, p->rect.h);
1448         }
1449
1450         /* display the skin, but do not free it as we need it later to
1451         restore text areas and maybe sliders too */
1452         if (gui->keypad) {
1453                 struct SDL_Rect *dest = &gui->win[WIN_KEYPAD].rect;
1454                 struct SDL_Rect *src = (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) ? & gui->kp_rect : NULL;
1455                 /* set the coordinates of the keypad relative to the main screen */
1456                 dest->x = x0-kp_w/2;
1457                 dest->y = y0;
1458                 dest->w = kp_w;
1459                 dest->h = kp_h;
1460                 SDL_BlitSurface(gui->keypad, src, gui->screen, dest);
1461                 init_board(gui, &gui->bd_msg, gui->kp_msg, dest->x, dest->y);
1462                 init_board(gui, &gui->bd_dialed, gui->kp_dialed, dest->x, dest->y);
1463                 SDL_UpdateRects(gui->screen, 1, dest);
1464         }
1465         return;
1466
1467 no_sdl:
1468         /* free resources in case of errors */
1469         env->gui = cleanup_sdl(gui, env->out.device_num);
1470 }
1471
1472 /*
1473  * Functions to determine if a point is within a region. Return 1 if success.
1474  * First rotate the point, with
1475  *      x' =  (x - x0) * cos A + (y - y0) * sin A
1476  *      y' = -(x - x0) * sin A + (y - y0) * cos A
1477  * where cos A = (x1-x0)/l, sin A = (y1 - y0)/l, and
1478  *      l = sqrt( (x1-x0)^2 + (y1-y0)^2
1479  * Then determine inclusion by simple comparisons i.e.:
1480  *      rectangle: x >= 0 && x < l && y >= 0 && y < h
1481  *      ellipse: (x-xc)^2/l^2 + (y-yc)^2/h2 < 1
1482  */
1483 static int kp_match_area(const struct keypad_entry *e, int x, int y)
1484 {
1485         double xp, dx = (e->x1 - e->x0);
1486         double yp, dy = (e->y1 - e->y0);
1487         double l = sqrt(dx*dx + dy*dy);
1488         int ret = 0;
1489
1490         if (l > 1) { /* large enough */
1491                 xp = ((x - e->x0)*dx + (y - e->y0)*dy)/l;
1492                 yp = (-(x - e->x0)*dy + (y - e->y0)*dx)/l;
1493                 if (e->type == KP_RECT) {
1494                         ret = (xp >= 0 && xp < l && yp >=0 && yp < e->h);
1495                 } else if (e->type == KP_CIRCLE) {
1496                         dx = xp*xp/(l*l) + yp*yp/(e->h*e->h);
1497                         ret = (dx < 1);
1498                 }
1499         }
1500 #if 0
1501         ast_log(LOG_WARNING, "result %d [%d] for match %d,%d in type %d p0 %d,%d p1 %d,%d h %d\n",
1502                 ret, e->c, x, y, e->type, e->x0, e->y0, e->x1, e->y1, e->h);
1503 #endif
1504         return ret;
1505 }
1506
1507 struct _s_k { const char *s; int k; };
1508 static struct _s_k gui_key_map[] = {
1509         {"FREEZE",      KEY_FREEZE},
1510         {"PIP",         KEY_PIP},
1511         {"PICK_UP",     KEY_PICK_UP },
1512         {"PICKUP",      KEY_PICK_UP },
1513         {"HANG_UP",     KEY_HANG_UP },
1514         {"HANGUP",      KEY_HANG_UP },
1515         {"MUTE",        KEY_MUTE },
1516         {"FLASH",       KEY_FLASH },
1517         {"AUTOANSWER",  KEY_AUTOANSWER },
1518         {"SENDVIDEO",   KEY_SENDVIDEO },
1519         {"LOCALVIDEO",  KEY_LOCALVIDEO },
1520         {"REMOTEVIDEO", KEY_REMOTEVIDEO },
1521         {"GUI_CLOSE",   KEY_GUI_CLOSE },
1522         {"MESSAGEBOARD",        KEY_MESSAGEBOARD },
1523         {"DIALEDBOARD", KEY_DIALEDBOARD },
1524         {"EDITBOARD",   KEY_EDITBOARD },
1525         {"KEYPAD",      KEY_KEYPAD },   /* x0 y0 w h - active area of the keypad */
1526         {"MESSAGE",     KEY_MESSAGE },  /* x0 y0 w h - incoming messages */
1527         {"DIALED",      KEY_DIALED },   /* x0 y0 w h - dialed number */
1528         {"EDIT",        KEY_EDIT },     /* x0 y0 w h - edit user input */
1529         {"FONT",        KEY_FONT },     /* x0 yo w h rows cols - location and format of the font */
1530         {NULL, 0 } };
1531
1532 static int gui_map_token(const char *s)
1533 {
1534         /* map the string into token to be returned */
1535         int i = atoi(s);
1536         struct _s_k *p;
1537         if (i > 0 || s[1] == '\0')      /* numbers or single characters */
1538                 return (i > 9) ? i : s[0];
1539         for (p = gui_key_map; p->s; p++) {
1540                 if (!strcasecmp(p->s, s))
1541                         return p->k;
1542         }
1543         return KEY_NONE;        /* not found */
1544 }
1545
1546 /*! \brief read a keypad entry line in the format
1547  *      reset
1548  *      token circle xc yc diameter
1549  *      token circle xc yc x1 y1 h      # ellipse, main diameter and height
1550  *      token rect x0 y0 x1 y1 h        # rectangle with main side and eight
1551  *      token x0 y0 w h                 # horizontal rectangle (short format)
1552  *                                      # this is used e.g. for message boards
1553  * token is the token to be returned, either a character or a symbol
1554  * as KEY_* above
1555  * Return 1 on success, 0 on error.
1556  */
1557 static int keypad_cfg_read(struct gui_info *gui, const char *val)
1558 {
1559         struct keypad_entry e;
1560         SDL_Rect *r = NULL;
1561         char s1[16], s2[16];
1562         int i, ret = 0; /* default, error */
1563
1564         if (gui == NULL || val == NULL)
1565                 return 0;
1566
1567         s1[0] = s2[0] = '\0';
1568         memset(&e, '\0', sizeof(e));
1569         i = sscanf(val, "%14s %14s %d %d %d %d %d",
1570                 s1, s2, &e.x0, &e.y0, &e.x1, &e.y1, &e.h);
1571
1572         e.c = gui_map_token(s1);
1573         if (e.c == KEY_NONE)
1574                 return 0;       /* nothing found */
1575         switch (i) {
1576         default:
1577                 break;
1578         case 1: /* only "reset" is allowed */
1579                 if (e.c != KEY_RESET)
1580                         break;
1581                 if (gui->kp)
1582                         gui->kp_used = 0;
1583                 break;
1584         case 5:
1585                 if (e.c == KEY_KEYPAD)  /* active keypad area */
1586                         r = &gui->kp_rect;
1587                 else if (e.c == KEY_MESSAGE)
1588                         r = gui->kp_msg;
1589                 else if (e.c == KEY_DIALED)
1590                         r = gui->kp_dialed;
1591                 else if (e.c == KEY_EDIT)
1592                         r = gui->kp_edit;
1593                 if (r) {
1594                         r->x = atoi(s2);        /* this becomes x0 */
1595                         r->y = e.x0;            /* this becomes y0 */
1596                         r->w = e.y0;            /* this becomes w  */
1597                         r->h = e.x1;            /* this becomes h  */
1598                         break;
1599                 }
1600                 if (strcasecmp(s2, "circle"))   /* invalid */
1601                         break;
1602                 /* token circle xc yc diameter */
1603                 e.h = e.x1;
1604                 e.y1 = e.y0;    /* map radius in x1 y1 */
1605                 e.x1 = e.x0 + e.h;      /* map radius in x1 y1 */
1606                 e.x0 = e.x0 - e.h;      /* map radius in x1 y1 */
1607                 /* fallthrough */
1608
1609         case 7:
1610                 if (e.c == KEY_FONT) {  /* font - x0 y0 w h rows cols */
1611                         ast_log(LOG_WARNING, "font not supported yet\n");
1612                         break;
1613                 }
1614                 /* token circle|rect x0 y0 x1 y1 h */
1615                 if (e.x1 < e.x0 || e.h <= 0) {
1616                         ast_log(LOG_WARNING, "error in coordinates\n");
1617                         e.type = 0;
1618                         break;
1619                 }
1620                 if (!strcasecmp(s2, "circle")) {
1621                         /* for a circle we specify the diameter but store center and radii */
1622                         e.type = KP_CIRCLE;
1623                         e.x0 = (e.x1 + e.x0) / 2;
1624                         e.y0 = (e.y1 + e.y0) / 2;
1625                         e.h = e.h / 2;
1626                 } else if (!strcasecmp(s2, "rect")) {
1627                         e.type = KP_RECT;
1628                 } else
1629                         break;
1630                 ret = 1;
1631         }
1632         // ast_log(LOG_WARNING, "reading [%s] returns %d %d\n", val, i, ret);
1633         if (ret == 0)
1634                 return 0;
1635         if (gui->kp_size == 0) {
1636                 gui->kp = ast_calloc(10, sizeof(e));
1637                 if (gui->kp == NULL) {
1638                         ast_log(LOG_WARNING, "cannot allocate kp");
1639                         return 0;
1640                 }
1641                 gui->kp_size = 10;
1642         }
1643         if (gui->kp_size == gui->kp_used) { /* must allocate */
1644                 struct keypad_entry *a = ast_realloc(gui->kp, sizeof(e)*(gui->kp_size+10));
1645                 if (a == NULL) {
1646                         ast_log(LOG_WARNING, "cannot reallocate kp");
1647                         return 0;
1648                 }
1649                 gui->kp = a;
1650                 gui->kp_size += 10;
1651         }
1652         if (gui->kp_size == gui->kp_used)
1653                 return 0;
1654         gui->kp[gui->kp_used++] = e;
1655         // ast_log(LOG_WARNING, "now %d regions\n", gui->kp_used);
1656         return 1;
1657 }
1658 #endif  /* HAVE_SDL */