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