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