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