Asterisk - The Open Source Telephony Project GIT-master-a358458
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
10For the GUI we use SDL to create a large surface (gui->screen) with 4 areas:
11remote video on the left, local video on the right, keypad with all controls
12and text windows in the center, and source device thumbnails on the top.
13The 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
36The central section is built using an image (jpg, png, maybe gif too)
37for the skin, and other GUI elements. Comments embedded in the image
38indicate to what function each area is mapped to.
39Another image (png with transparency) is used for the font.
40
41Mouse and keyboard events are detected on the whole surface, and
42handled 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
57Configuration options control the appearance of the gui:
58
59 keypad = /tmp/kpad2.jpg ; the skin
60 keypad_font = /tmp/font.png ; the font to use for output
61
62For future implementation, interesting features can be the following:
63- save of the whole SDL window as a picture
64- audio output device switching
65
66The audio switching feature should allow changing the device
67or switching to a recorded message for audio sent to remote party.
68The selection of the device should happen clicking on a marker in the layout.
69For this reason above the thumbnails row in the layout we would like a new row,
70the elements composing the row could be message boards, reporting the name of the
71device or the path of the message to be played.
72
73For video input freeze and entire window capture, we define 2 new key types,
74those should be activated pressing the buttons on the keypad, associated with
75new 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 */
98static void show_frame(struct video_desc *env, int out) {}
99static void sdl_setup(struct video_desc *env) {}
100static struct gui_info *cleanup_sdl(struct gui_info* g, int n) { return NULL; }
101static void eventhandler(struct video_desc *env, const char *caption) {}
102static 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
120enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE };
121struct 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 */
130struct 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 */
137struct thumb_bd {
138 SDL_Rect rect; /* the rect for geometry and background */
139 struct board *board; /* the board */
140};
141
142struct 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 */
185static 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/* constants defined to describe status of devices */
235#define IS_PRIMARY 1
236#define IS_SECONDARY 2
237#define IS_ON 4
238
239char* 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 */
259static 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 */
318enum 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 */
374static 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 */
389static 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
421char *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 */
432static 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
461size 320,64
462name font.png
463transparent 0,0,0
464string 255,255,255, 0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>?
465string 255,255,255, 0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
466string 255,255,255, 0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~
467end
468
469 */
470
471/* Print given text on the gui */
472static int gui_output(struct video_desc *env, const char *text)
473{
474 return 1; /* error, not supported */
475}
476#endif
477
478static int video_geom(struct fbuf_t *b, const char *s);
479static void sdl_setup(struct video_desc *env);
480static int kp_match_area(const struct keypad_entry *e, int x, int y);
481
482static 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
489static 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 */
516static 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*/
570static 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 */
619static 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 coordinate 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 coordinate 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 */
839static 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
847static 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
875static 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
902static void grabber_move(struct video_device *, int dx, int dy);
903
904int compute_drag(int *start, int end, int magnifier);
905int 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*/
925static 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 */
953static 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
1043static 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
1058static void keypad_setup(struct gui_info *gui, const char *kp_file);
1059
1060/* TODO: consistency checks, check for bpp, width and height */
1061/* Init the mask image used to grab the action. */
1062static 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
1106error:
1107 ast_free(gui);
1108 return NULL;
1109}
1110
1111/* setup an sdl overlay and associated info, return 0 on success, != 0 on error */
1112static 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
1125static int keypad_cfg_read(struct gui_info *gui, const char *val);
1126
1127static 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
1182struct 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 */
1186static 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 */
1208static 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 */
1219static 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
1471no_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 */
1487static 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
1511struct _s_k { const char *s; int k; };
1512static 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
1536static 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 */
1561static 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 */
char digit
char * text
Definition: app_queue.c:1639
if(!yyg->yy_init)
Definition: ast_expr2f.c:854
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_realloc(p, len)
A wrapper for realloc()
Definition: astmm.h:226
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
static const char type[]
Definition: chan_ooh323.c:109
@ KEY_MUTE
Definition: chan_unistim.c:320
int ast_queue_frame(struct ast_channel *chan, struct ast_frame *f)
Queue one or more frames to a channel's frame queue.
Definition: channel.c:1139
ast_cli_command
calling arguments for new-style handlers.
Definition: cli.h:151
static void sdl_setup(struct video_desc *env)
Definition: console_gui.c:99
static void eventhandler(struct video_desc *env, const char *caption)
Definition: console_gui.c:101
static struct gui_info * cleanup_sdl(struct gui_info *g, int n)
Definition: console_gui.c:100
static int keypad_cfg_read(struct gui_info *gui, const char *val)
Definition: console_gui.c:102
static void show_frame(struct video_desc *env, int out)
Definition: console_gui.c:98
@ WIN_SRC1
Definition: console_gui.c:93
@ WIN_KEYPAD
Definition: console_gui.c:93
@ WIN_SRC6
Definition: console_gui.c:95
@ WIN_SRC7
Definition: console_gui.c:95
@ WIN_SRC8
Definition: console_gui.c:95
@ WIN_REMOTE
Definition: console_gui.c:93
@ WIN_SRC3
Definition: console_gui.c:94
@ WIN_SRC9
Definition: console_gui.c:95
@ WIN_LOCAL
Definition: console_gui.c:93
@ WIN_SRC5
Definition: console_gui.c:94
@ WIN_SRC2
Definition: console_gui.c:94
@ WIN_MAX
Definition: console_gui.c:95
@ WIN_SRC4
Definition: console_gui.c:94
void delete_board(struct board *b)
deallocates memory space for a board
struct grab_desc * console_grabbers[]
drag_window
@ DRAG_LOCAL
@ DRAG_PIP
@ DRAG_NONE
@ DRAG_MESSAGE
kb_output
@ KO_INPUT
@ KO_MESSAGE
@ KO_DIALED
void move_message_board(struct board *b, int dy)
const char * read_message(const struct board *b)
return the whole text from a board
#define SRC_WIN_W
Definition: console_video.h:46
int reset_board(struct board *b)
reset the board to blank
#define SRC_WIN_H
Definition: console_video.h:47
#define MAX_VIDEO_SOURCES
Definition: console_video.h:51
int print_message(struct board *b, const char *s)
char * end
Definition: eagi_proxy.c:73
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Asterisk internal frame definitions.
#define AST_FRAME_DTMF
#define LOG_ERROR
#define LOG_WARNING
Asterisk locking-related definitions:
Definition: cdr/env.py:1
def info(msg)
#define NULL
Definition: resample.c:96
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:186
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
Definition: strings.h:161
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
support for drag actions
enum drag_window drag_window
int pix_fmt
Definition: console_video.h:69
void *(* open)(const char *name, struct fbuf_t *geom, int fps)
Definition: console_video.h:82
Definition: ast_expr2.c:325
static struct test_val b
static struct test_val a
static struct test_val d
static struct test_val c
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
FILE * out
Definition: utils/frame.c:33
int error(const char *format,...)
Definition: utils/frame.c:999
Utility functions.
#define MAX(a, b)
Definition: utils.h:233