From f8247040e6231c4b3b5099ea3a526348b7941566 Mon Sep 17 00:00:00 2001 From: russell Date: Sat, 19 Jan 2008 00:19:29 +0000 Subject: Creating tag for the release of asterisk-1.6.0-beta1 git-svn-id: http://svn.digium.com/svn/asterisk/tags/1.6.0-beta1@99163 f38db490-d61c-443f-a65b-d21fe96a405b --- trunk/channels/console_gui.c | 1036 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1036 insertions(+) create mode 100644 trunk/channels/console_gui.c (limited to 'trunk/channels/console_gui.c') diff --git a/trunk/channels/console_gui.c b/trunk/channels/console_gui.c new file mode 100644 index 000000000..aa2f2cf95 --- /dev/null +++ b/trunk/channels/console_gui.c @@ -0,0 +1,1036 @@ +/* + * GUI for console video. + * The routines here are in charge of loading the keypad and handling events. + * $Revision$ + */ + +/* + * GUI layout, structure and management + +For the GUI we use SDL to create a large surface (gui->screen) +containing tree sections: remote video on the left, local video +on the right, and the keypad with all controls and text windows +in the center. +The central section is built using an image for the skin, fonts and +other GUI elements. Comments embedded in the image to indicate to +what function each area is mapped to. + +Mouse and keyboard events are detected on the whole surface, and +handled differently according to their location: + +- drag on the local video window are used to move the captured + area (in the case of X11 grabber) or the picture-in-picture + location (in case of camera included on the X11 grab). +- click on the keypad are mapped to the corresponding key; +- drag on some keypad areas (sliders etc.) are mapped to the + corresponding functions; +- keystrokes are used as keypad functions, or as text input + if we are in text-input mode. + +Configuration options control the appeareance of the gui: + + keypad = /tmp/phone.jpg ; the skin + keypad_font = /tmp/font.ttf ; the font to use for output (XXX deprecated) + + * + */ + +#include "asterisk.h" +#include "console_video.h" +#include "asterisk/lock.h" +#include "asterisk/frame.h" +#include "asterisk/utils.h" /* ast_calloc and ast_realloc */ +#include /* sqrt */ + +/* We use 3 'windows' in the GUI */ +enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_MAX }; + +#ifndef HAVE_SDL /* stubs if we don't have any sdl */ +static void show_frame(struct video_desc *env, int out) {} +static void sdl_setup(struct video_desc *env) {} +static struct gui_info *cleanup_sdl(struct gui_info *gui) { return NULL; } +static void eventhandler(struct video_desc *env, const char *caption) {} +static int keypad_cfg_read(struct gui_info *gui, const char *val) { return 0; } + +#else /* HAVE_SDL, the real rendering code */ + +#include +#ifdef HAVE_SDL_IMAGE +#include /* for loading images */ +#endif + +enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE }; +struct keypad_entry { + int c; /* corresponding character */ + int x0, y0, x1, y1, h; /* arguments */ + enum kp_type type; +}; + +/* our representation of a displayed window. SDL can only do one main + * window so we map everything within that one + */ +struct display_window { + SDL_Overlay *bmp; + SDL_Rect rect; /* location of the window */ +}; + +struct gui_info { + enum kb_output kb_output; /* where the keyboard output goes */ + struct drag_info drag; /* info on the window are we dragging */ + /* support for display. */ + SDL_Surface *screen; /* the main window */ + + int outfd; /* fd for output */ + SDL_Surface *keypad; /* the skin for the keypad */ + SDL_Rect kp_rect; /* portion of the skin to display - default all */ + SDL_Surface *font; /* font to be used */ + SDL_Rect font_rects[96]; /* only printable chars */ + + /* each board has two rectangles, + * [0] is the geometry relative to the keypad, + * [1] is the geometry relative to the whole screen + */ + SDL_Rect kp_msg[2]; /* incoming msg, relative to kpad */ + struct board *bd_msg; + + SDL_Rect kp_edit[2]; /* edit user input */ + struct board *bd_edit; + + SDL_Rect kp_dialed[2]; /* dialed number */ + struct board *bd_dialed; + + /* variable-size array mapping keypad regions to functions */ + int kp_size, kp_used; + struct keypad_entry *kp; + + struct display_window win[WIN_MAX]; +}; + +/*! \brief free the resources in struct gui_info and the descriptor itself. + * Return NULL so we can assign the value back to the descriptor in case. + */ +static struct gui_info *cleanup_sdl(struct gui_info *gui) +{ + int i; + + if (gui == NULL) + return NULL; + + /* unload font file */ + if (gui->font) { + SDL_FreeSurface(gui->font); + gui->font = NULL; + } + + if (gui->outfd > -1) + close(gui->outfd); + if (gui->keypad) + SDL_FreeSurface(gui->keypad); + gui->keypad = NULL; + if (gui->kp) + ast_free(gui->kp); + + /* uninitialize the SDL environment */ + for (i = 0; i < WIN_MAX; i++) { + if (gui->win[i].bmp) + SDL_FreeYUVOverlay(gui->win[i].bmp); + } + bzero(gui, sizeof(gui)); + ast_free(gui); + SDL_Quit(); + return NULL; +} + +/* + * Display video frames (from local or remote stream) using the SDL library. + * - Set the video mode to use the resolution specified by the codec context + * - Create a YUV Overlay to copy the frame into it; + * - After the frame is copied into the overlay, display it + * + * The size is taken from the configuration. + * + * 'out' is 0 for remote video, 1 for the local video + */ +static void show_frame(struct video_desc *env, int out) +{ + AVPicture *p_in, p_out; + struct fbuf_t *b_in, *b_out; + SDL_Overlay *bmp; + struct gui_info *gui = env->gui; + + if (!gui) + return; + + if (out == WIN_LOCAL) { /* webcam/x11 to sdl */ + b_in = &env->enc_in; + b_out = &env->loc_dpy; + p_in = NULL; + } else { + /* copy input format from the decoding context */ + AVCodecContext *c; + if (env->in == NULL) /* XXX should not happen - decoder not ready */ + return; + c = env->in->dec_ctx; + b_in = &env->in->dec_out; + b_in->pix_fmt = c->pix_fmt; + b_in->w = c->width; + b_in->h = c->height; + + b_out = &env->rem_dpy; + p_in = (AVPicture *)env->in->d_frame; + } + bmp = gui->win[out].bmp; + SDL_LockYUVOverlay(bmp); + /* output picture info - this is sdl, YUV420P */ + bzero(&p_out, sizeof(p_out)); + p_out.data[0] = bmp->pixels[0]; + p_out.data[1] = bmp->pixels[1]; + p_out.data[2] = bmp->pixels[2]; + p_out.linesize[0] = bmp->pitches[0]; + p_out.linesize[1] = bmp->pitches[1]; + p_out.linesize[2] = bmp->pitches[2]; + + my_scale(b_in, p_in, b_out, &p_out); + + /* lock to protect access to Xlib by different threads. */ + SDL_DisplayYUVOverlay(bmp, &gui->win[out].rect); + SDL_UnlockYUVOverlay(bmp); +} + +/* + * Identifiers for regions of the main window. + * Values between 0 and 127 correspond to ASCII characters. + * The corresponding strings to be used in the skin comment section + * are defined in gui_key_map. + */ +enum skin_area { + /* answer/close functions */ + KEY_PICK_UP = 128, + KEY_HANG_UP = 129, + + KEY_MUTE = 130, + KEY_AUTOANSWER = 131, + KEY_SENDVIDEO = 132, + KEY_LOCALVIDEO = 133, + KEY_REMOTEVIDEO = 134, + KEY_FLASH = 136, + + /* sensitive areas for the various text windows */ + KEY_MESSAGEBOARD = 140, + KEY_DIALEDBOARD = 141, + KEY_EDITBOARD = 142, + + KEY_GUI_CLOSE = 199, /* close gui */ + /* regions of the skin - displayed area, fonts, etc. + * XXX NOTE these are not sensitive areas. + */ + KEY_KEYPAD = 200, /* the keypad - default to the whole image */ + KEY_FONT = 201, /* the font. Maybe not really useful */ + KEY_MESSAGE = 202, /* area for incoming messages */ + KEY_DIALED = 203, /* area for dialed numbers */ + KEY_EDIT = 204, /* area for editing user input */ + + /* areas outside the keypad - simulated */ + KEY_OUT_OF_KEYPAD = 241, + KEY_REM_DPY = 242, + KEY_LOC_DPY = 243, + KEY_RESET = 253, /* the 'reset' keyword */ + KEY_NONE = 254, /* invalid area */ + KEY_DIGIT_BACKGROUND = 255, /* other areas within the keypad */ +}; + +/* + * Handlers for the various keypad functions + */ + +/* accumulate digits, possibly call dial if in connected mode */ +static void keypad_digit(struct video_desc *env, int digit) +{ + if (env->owner) { /* we have a call, send the digit */ + struct ast_frame f = { AST_FRAME_DTMF, 0 }; + + f.subclass = digit; + ast_queue_frame(env->owner, &f); + } else { /* no call, accumulate digits */ + char buf[2] = { digit, '\0' }; + if (env->gui->bd_msg) /* XXX not strictly necessary ... */ + print_message(env->gui->bd_msg, buf); + } +} + +/* function used to toggle on/off the status of some variables */ +static char *keypad_toggle(struct video_desc *env, int index) +{ + ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index); + + switch (index) { + case KEY_SENDVIDEO: + env->out.sendvideo = !env->out.sendvideo; + break; +#ifdef notyet + case KEY_MUTE: { + struct chan_oss_pvt *o = find_desc(oss_active); + o->mute = !o->mute; + } + break; + case KEY_AUTOANSWER: { + struct chan_oss_pvt *o = find_desc(oss_active); + o->autoanswer = !o->autoanswer; + } + break; +#endif + } + return NULL; +} + +char *console_do_answer(int fd); +/* + * Function called when the pick up button is pressed + * perform actions according the channel status: + * + * - if no one is calling us and no digits was pressed, + * the operation have no effects, + * - if someone is calling us we answer to the call. + * - if we have no call in progress and we pressed some + * digit, send the digit to the console. + */ +static void keypad_pick_up(struct video_desc *env) +{ + struct gui_info *gui = env->gui; + + ast_log(LOG_WARNING, "keypad_pick_up called\n"); + + if (env->owner) { /* someone is calling us, just answer */ + ast_cli_command(gui->outfd, "console answer"); + } else { /* we have someone to call */ + char buf[160]; + const char *who = ast_skip_blanks(read_message(gui->bd_msg)); + buf[sizeof(buf) - 1] = '\0'; + snprintf(buf, sizeof(buf) - 1, "console dial %s", who); + ast_log(LOG_WARNING, "doing <%s>\n", buf); + print_message(gui->bd_dialed, "\n"); + print_message(gui->bd_dialed, who); + reset_board(gui->bd_msg); + ast_cli_command(gui->outfd, buf); + } +} + +#if 0 /* still unused */ +/* + * As an alternative to SDL_TTF, we can simply load the font from + * an image and blit characters on the background of the GUI. + * + * To generate a font we can use the 'fly' command with the + * following script (3 lines with 32 chars each) + +size 320,64 +name font.png +transparent 0,0,0 +string 255,255,255, 0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>? +string 255,255,255, 0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ +string 255,255,255, 0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~ +end + + */ + +/* Print given text on the gui */ +static int gui_output(struct video_desc *env, const char *text) +{ + return 1; /* error, not supported */ +} +#endif + +static int video_geom(struct fbuf_t *b, const char *s); +static void sdl_setup(struct video_desc *env); +static int kp_match_area(const struct keypad_entry *e, int x, int y); + +static void set_drag(struct drag_info *drag, int x, int y, enum drag_window win) +{ + drag->x_start = x; + drag->y_start = y; + drag->drag_window = win; +} + +/* + * Handle SDL_MOUSEBUTTONDOWN type, finding the palette + * index value and calling the right callback. + * + * x, y are referred to the upper left corner of the main SDL window. + */ +static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button) +{ + uint8_t index = KEY_OUT_OF_KEYPAD; /* the key or region of the display we clicked on */ + struct gui_info *gui = env->gui; + +#if 0 + ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n", + button.x, button.y, gui->kp_used, gui->kp_size, gui->kp); +#endif + /* for each mousedown we end previous drag */ + gui->drag.drag_window = DRAG_NONE; + + /* define keypad boundary */ + if (button.x < env->rem_dpy.w) + index = KEY_REM_DPY; /* click on remote video */ + else if (button.x > env->rem_dpy.w + gui->keypad->w) + index = KEY_LOC_DPY; /* click on local video */ + else if (button.y > gui->keypad->h) + index = KEY_OUT_OF_KEYPAD; /* click outside the keypad */ + else if (gui->kp) { + int i; + for (i = 0; i < gui->kp_used; i++) { + if (kp_match_area(&gui->kp[i], button.x - env->rem_dpy.w, button.y)) { + index = gui->kp[i].c; + break; + } + } + } + + /* exec the function */ + if (index < 128) { /* surely clicked on the keypad, don't care which key */ + keypad_digit(env, index); + return; + } + switch (index) { + /* answer/close function */ + case KEY_PICK_UP: + keypad_pick_up(env); + break; + case KEY_HANG_UP: + ast_cli_command(gui->outfd, "console hangup"); + break; + + /* other functions */ + case KEY_MUTE: + case KEY_AUTOANSWER: + case KEY_SENDVIDEO: + keypad_toggle(env, index); + break; + + case KEY_LOCALVIDEO: + break; + case KEY_REMOTEVIDEO: + break; + + case KEY_MESSAGEBOARD: + if (button.button == SDL_BUTTON_LEFT) + set_drag(&gui->drag, button.x, button.y, DRAG_MESSAGE); + break; + + /* press outside the keypad. right increases size, center decreases, left drags */ + case KEY_LOC_DPY: + case KEY_REM_DPY: + if (button.button == SDL_BUTTON_LEFT) { + if (index == KEY_LOC_DPY) + set_drag(&gui->drag, button.x, button.y, DRAG_LOCAL); + break; + } else { + char buf[128]; + struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->loc_dpy : &env->rem_dpy; + sprintf(buf, "%c%dx%d", button.button == SDL_BUTTON_RIGHT ? '>' : '<', + fb->w, fb->h); + video_geom(fb, buf); + sdl_setup(env); + } + break; + case KEY_OUT_OF_KEYPAD: + break; + + case KEY_DIGIT_BACKGROUND: + break; + default: + ast_log(LOG_WARNING, "function not yet defined %i\n", index); + } +} + +/* + * Handle SDL_KEYDOWN type event, put the key pressed + * in the dial buffer or in the text-message buffer, + * depending on the text_mode variable value. + * + * key is the SDLKey structure corresponding to the key pressed. + * Note that SDL returns modifiers (ctrl, shift, alt) as independent + * information so the key itself is not enough and we need to + * use a translation table, below - one line per entry, + * plain, shift, ctrl, ... using the first char as key. + */ +static const char *us_kbd_map[] = { + "`~", "1!", "2@", "3#", "4$", "5%", "6^", + "7&", "8*", "9(", "0)", "-_", "=+", "[{", + "]}", "\\|", ";:", "'\"", ",<", ".>", "/?", + "jJ\n", + NULL +}; + +static char map_key(SDL_keysym *ks) +{ + const char *s, **p = us_kbd_map; + int c = ks->sym; + + if (c == '\r') /* map cr into lf */ + c = '\n'; + if (c >= SDLK_NUMLOCK && c <= SDLK_COMPOSE) + return 0; /* only a modifier */ + if (ks->mod == 0) + return c; + while ((s = *p) && s[0] != c) + p++; + if (s) { /* see if we have a modifier and a chance to use it */ + int l = strlen(s), mod = 0; + if (l > 1) + mod |= (ks->mod & KMOD_SHIFT) ? 1 : 0; + if (l > 2 + mod) + mod |= (ks->mod & KMOD_CTRL) ? 2 : 0; + if (l > 4 + mod) + mod |= (ks->mod & KMOD_ALT) ? 4 : 0; + c = s[mod]; + } + if (ks->mod & (KMOD_CAPS|KMOD_SHIFT) && c >= 'a' && c <='z') + c += 'A' - 'a'; + return c; +} + +static void handle_keyboard_input(struct video_desc *env, SDL_keysym *ks) +{ + char buf[2] = { map_key(ks), '\0' }; + struct gui_info *gui = env->gui; + if (buf[0] == 0) /* modifier ? */ + return; + switch (gui->kb_output) { + default: + break; + case KO_INPUT: /* to be completed */ + break; + case KO_MESSAGE: + if (gui->bd_msg) { + print_message(gui->bd_msg, buf); + if (buf[0] == '\r' || buf[0] == '\n') { + keypad_pick_up(env); + } + } + break; + + case KO_DIALED: /* to be completed */ + break; + } + + return; +} + +static void grabber_move(struct video_out_desc *, int dx, int dy); + +int compute_drag(int *start, int end, int magnifier); +int compute_drag(int *start, int end, int magnifier) +{ + int delta = end - *start; +#define POLARITY -1 + /* add a small quadratic term */ + delta += delta * delta * (delta > 0 ? 1 : -1 )/100; + delta *= POLARITY * magnifier; +#undef POLARITY + *start = end; + return delta; +} + +/* + * I am seeing some kind of deadlock or stall around + * SDL_PumpEvents() while moving the window on a remote X server + * (both xfree-4.4.0 and xorg 7.2) + * and windowmaker. It is unclear what causes it. + */ + +/*! \brief refresh the screen, and also grab a bunch of events. + */ +static void eventhandler(struct video_desc *env, const char *caption) +{ + struct gui_info *gui = env->gui; + struct drag_info *drag; +#define N_EVENTS 32 + int i, n; + SDL_Event ev[N_EVENTS]; + + if (!gui) + return; + drag = &gui->drag; + if (caption) + SDL_WM_SetCaption(caption, NULL); + +#define MY_EV (SDL_MOUSEBUTTONDOWN|SDL_KEYDOWN) + while ( (n = SDL_PeepEvents(ev, N_EVENTS, SDL_GETEVENT, SDL_ALLEVENTS)) > 0) { + for (i = 0; i < n; i++) { +#if 0 + ast_log(LOG_WARNING, "------ event %d at %d %d\n", + ev[i].type, ev[i].button.x, ev[i].button.y); +#endif + switch (ev[i].type) { + case SDL_KEYDOWN: + handle_keyboard_input(env, &ev[i].key.keysym); + break; + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONUP: + if (drag->drag_window == DRAG_LOCAL) { + /* move the capture source */ + int dx = compute_drag(&drag->x_start, ev[i].motion.x, 3); + int dy = compute_drag(&drag->y_start, ev[i].motion.y, 3); + grabber_move(&env->out, dx, dy); + } else if (drag->drag_window == DRAG_MESSAGE) { + /* scroll up/down the window */ + int dy = compute_drag(&drag->y_start, ev[i].motion.y, 1); + move_message_board(gui->bd_msg, dy); + } + if (ev[i].type == SDL_MOUSEBUTTONUP) + drag->drag_window = DRAG_NONE; + break; + case SDL_MOUSEBUTTONDOWN: + handle_mousedown(env, ev[i].button); + break; + } + } + } + if (1) { + struct timeval b, a = ast_tvnow(); + int i; + //SDL_Lock_EventThread(); + SDL_PumpEvents(); + b = ast_tvnow(); + i = ast_tvdiff_ms(b, a); + if (i > 3) + fprintf(stderr, "-------- SDL_PumpEvents took %dms\n", i); + //SDL_Unlock_EventThread(); + } +} + +static SDL_Surface *load_image(const char *file) +{ + SDL_Surface *temp; + +#ifdef HAVE_SDL_IMAGE + temp = IMG_Load(file); +#else + temp = SDL_LoadBMP(file); +#endif + if (temp == NULL) + fprintf(stderr, "Unable to load image %s: %s\n", + file, SDL_GetError()); + return temp; +} + +static void keypad_setup(struct gui_info *gui, const char *kp_file); + +/* TODO: consistency checks, check for bpp, widht and height */ +/* Init the mask image used to grab the action. */ +static struct gui_info *gui_init(const char *keypad_file, const char *font) +{ + struct gui_info *gui = ast_calloc(1, sizeof(*gui)); + + if (gui == NULL) + return NULL; + /* initialize keypad status */ + gui->kb_output = KO_MESSAGE; /* XXX temp */ + gui->drag.drag_window = DRAG_NONE; + gui->outfd = -1; + + keypad_setup(gui, keypad_file); + if (gui->keypad == NULL) /* no keypad, we are done */ + return gui; + /* XXX load image */ + if (!ast_strlen_zero(font)) { + int i; + SDL_Rect *r; + + gui->font = load_image(font); + if (!gui->font) { + ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", font); + goto error; + } + ast_log(LOG_WARNING, "Loaded font %s\n", font); + /* XXX hardwired constants - 3 rows of 32 chars */ + r = gui->font_rects; +#define FONT_H 20 +#define FONT_W 9 + for (i = 0; i < 96; r++, i++) { + r->x = (i % 32 ) * FONT_W; + r->y = (i / 32 ) * FONT_H; + r->w = FONT_W; + r->h = FONT_H; + } + } + + gui->outfd = open ("/dev/null", O_WRONLY); /* discard output, temporary */ + if (gui->outfd < 0) { + ast_log(LOG_WARNING, "Unable output fd\n"); + goto error; + } + return gui; + +error: + ast_free(gui); + return NULL; +} + +/* setup an sdl overlay and associated info, return 0 on success, != 0 on error */ +static int set_win(SDL_Surface *screen, struct display_window *win, int fmt, + int w, int h, int x, int y) +{ + win->bmp = SDL_CreateYUVOverlay(w, h, fmt, screen); + if (win->bmp == NULL) + return -1; /* error */ + win->rect.x = x; + win->rect.y = y; + win->rect.w = w; + win->rect.h = h; + return 0; +} + +static int keypad_cfg_read(struct gui_info *gui, const char *val); + +static void keypad_setup(struct gui_info *gui, const char *kp_file) +{ + FILE *fd; + char buf[1024]; + const char region[] = "region"; + int reg_len = strlen(region); + int in_comment = 0; + + if (gui->keypad) + return; + gui->keypad = load_image(kp_file); + if (!gui->keypad) + return; + /* now try to read the keymap from the file. */ + fd = fopen(kp_file, "r"); + if (fd == NULL) { + ast_log(LOG_WARNING, "fail to open %s\n", kp_file); + return; + } + /* + * If the keypad image has a comment field, try to read + * the button location from there. The block must start with + * a comment (or empty) line, and continue with entries like: + * region = token shape x0 y0 x1 y1 h + * ... + * (basically, lines have the same format as config file entries). + * You can add it to a jpeg file using wrjpgcom + */ + while (fgets(buf, sizeof(buf), fd)) { + char *s; + + if (!strstr(buf, region)) { /* no keyword yet */ + if (!in_comment) /* still waiting for initial comment block */ + continue; + else + break; + } + if (!in_comment) { /* first keyword, reset previous entries */ + keypad_cfg_read(gui, "reset"); + in_comment = 1; + } + s = ast_skip_blanks(buf); + ast_trim_blanks(s); + if (memcmp(s, region, reg_len)) + break; /* keyword not found */ + s = ast_skip_blanks(s + reg_len); /* space between token and '=' */ + if (*s++ != '=') /* missing separator */ + break; + if (*s == '>') /* skip '>' if present */ + s++; + keypad_cfg_read(gui, ast_skip_blanks(s)); + } + fclose(fd); +} + +struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest, + SDL_Surface *font, SDL_Rect *font_rects); + +/*! \brief initialize the boards we have in the keypad */ +static void init_board(struct gui_info *gui, struct board **dst, SDL_Rect *r, int dx, int dy) +{ + if (r[0].w == 0 || r[0].h == 0) + return; /* not available */ + r[1] = r[0]; /* copy geometry */ + r[1].x += dx; /* add offset of main window */ + r[1].y += dy; + if (*dst == NULL) { /* initial call */ + *dst = board_setup(gui->screen, &r[1], gui->font, gui->font_rects); + } else { + /* call a refresh */ + } +} + +/*! \brief [re]set the main sdl window, useful in case of resize. + * We can tell the first from subsequent calls from the value of + * env->gui, which is NULL the first time. + */ +static void sdl_setup(struct video_desc *env) +{ + int dpy_fmt = SDL_IYUV_OVERLAY; /* YV12 causes flicker in SDL */ + int depth, maxw, maxh; + const SDL_VideoInfo *info; + int kp_w = 0, kp_h = 0; /* keypad width and height */ + struct gui_info *gui = env->gui; + + /* + * initialize the SDL environment. We have one large window + * with local and remote video, and a keypad. + * At the moment we arrange them statically, as follows: + * - on the left, the remote video; + * - on the center, the keypad + * - on the right, the local video + * We need to read in the skin for the keypad before creating the main + * SDL window, because the size is only known here. + */ + + if (gui == NULL && SDL_Init(SDL_INIT_VIDEO)) { + ast_log(LOG_WARNING, "Could not initialize SDL - %s\n", + SDL_GetError()); + /* again not fatal, just we won't display anything */ + return; + } + info = SDL_GetVideoInfo(); + /* We want at least 16bpp to support YUV overlays. + * E.g with SDL_VIDEODRIVER = aalib the default is 8 + */ + depth = info->vfmt->BitsPerPixel; + if (depth < 16) + depth = 16; + if (!gui) + env->gui = gui = gui_init(env->keypad_file, env->keypad_font); + if (!gui) + goto no_sdl; + + if (gui->keypad) { + if (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) { + kp_w = gui->kp_rect.w; + kp_h = gui->kp_rect.h; + } else { + kp_w = gui->keypad->w; + kp_h = gui->keypad->h; + } + } + /* XXX same for other boards */ +#define BORDER 5 /* border around our windows */ + maxw = env->rem_dpy.w + env->loc_dpy.w + kp_w; + maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h); + maxw += 4 * BORDER; + maxh += 2 * BORDER; + gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0); + if (!gui->screen) { + ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n"); + goto no_sdl; + } + + SDL_WM_SetCaption("Asterisk console Video Output", NULL); + if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt, + env->rem_dpy.w, env->rem_dpy.h, BORDER, BORDER)) + goto no_sdl; + if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt, + env->loc_dpy.w, env->loc_dpy.h, + 3*BORDER+env->rem_dpy.w + kp_w, BORDER)) + goto no_sdl; + + /* display the skin, but do not free it as we need it later to + * restore text areas and maybe sliders too. + */ + if (gui->keypad) { + struct SDL_Rect *dest = &gui->win[WIN_KEYPAD].rect; + struct SDL_Rect *src = (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) ? & gui->kp_rect : NULL; + /* set the coordinates of the keypad relative to the main screen */ + dest->x = 2*BORDER + env->rem_dpy.w; + dest->y = BORDER; + dest->w = kp_w; + dest->h = kp_h; + SDL_BlitSurface(gui->keypad, src, gui->screen, dest); + init_board(gui, &gui->bd_msg, gui->kp_msg, dest->x, dest->y); + init_board(gui, &gui->bd_dialed, gui->kp_dialed, dest->x, dest->y); + SDL_UpdateRects(gui->screen, 1, dest); + } + return; + +no_sdl: + /* free resources in case of errors */ + env->gui = cleanup_sdl(gui); +} + +/* + * Functions to determine if a point is within a region. Return 1 if success. + * First rotate the point, with + * x' = (x - x0) * cos A + (y - y0) * sin A + * y' = -(x - x0) * sin A + (y - y0) * cos A + * where cos A = (x1-x0)/l, sin A = (y1 - y0)/l, and + * l = sqrt( (x1-x0)^2 + (y1-y0)^2 + * Then determine inclusion by simple comparisons i.e.: + * rectangle: x >= 0 && x < l && y >= 0 && y < h + * ellipse: (x-xc)^2/l^2 + (y-yc)^2/h2 < 1 + */ +static int kp_match_area(const struct keypad_entry *e, int x, int y) +{ + double xp, dx = (e->x1 - e->x0); + double yp, dy = (e->y1 - e->y0); + double l = sqrt(dx*dx + dy*dy); + int ret = 0; + + if (l > 1) { /* large enough */ + xp = ((x - e->x0)*dx + (y - e->y0)*dy)/l; + yp = (-(x - e->x0)*dy + (y - e->y0)*dx)/l; + if (e->type == KP_RECT) { + ret = (xp >= 0 && xp < l && yp >=0 && yp < l); + } else if (e->type == KP_CIRCLE) { + dx = xp*xp/(l*l) + yp*yp/(e->h*e->h); + ret = (dx < 1); + } + } +#if 0 + ast_log(LOG_WARNING, "result %d [%d] for match %d,%d in type %d p0 %d,%d p1 %d,%d h %d\n", + ret, e->c, x, y, e->type, e->x0, e->y0, e->x1, e->y1, e->h); +#endif + return ret; +} + +struct _s_k { const char *s; int k; }; +static struct _s_k gui_key_map[] = { + {"PICK_UP", KEY_PICK_UP }, + {"PICKUP", KEY_PICK_UP }, + {"HANG_UP", KEY_HANG_UP }, + {"HANGUP", KEY_HANG_UP }, + {"MUTE", KEY_MUTE }, + {"FLASH", KEY_FLASH }, + {"AUTOANSWER", KEY_AUTOANSWER }, + {"SENDVIDEO", KEY_SENDVIDEO }, + {"LOCALVIDEO", KEY_LOCALVIDEO }, + {"REMOTEVIDEO", KEY_REMOTEVIDEO }, + {"GUI_CLOSE", KEY_GUI_CLOSE }, + {"MESSAGEBOARD", KEY_MESSAGEBOARD }, + {"DIALEDBOARD", KEY_DIALEDBOARD }, + {"EDITBOARD", KEY_EDITBOARD }, + {"KEYPAD", KEY_KEYPAD }, /* x0 y0 w h - active area of the keypad */ + {"MESSAGE", KEY_MESSAGE }, /* x0 y0 w h - incoming messages */ + {"DIALED", KEY_DIALED }, /* x0 y0 w h - dialed number */ + {"EDIT", KEY_EDIT }, /* x0 y0 w h - edit user input */ + {"FONT", KEY_FONT }, /* x0 yo w h rows cols - location and format of the font */ + {NULL, 0 } }; + +static int gui_map_token(const char *s) +{ + /* map the string into token to be returned */ + int i = atoi(s); + struct _s_k *p; + if (i > 0 || s[1] == '\0') /* numbers or single characters */ + return (i > 9) ? i : s[0]; + for (p = gui_key_map; p->s; p++) { + if (!strcasecmp(p->s, s)) + return p->k; + } + return KEY_NONE; /* not found */ +} + +/*! \brief read a keypad entry line in the format + * reset + * token circle xc yc diameter + * token circle xc yc x1 y1 h # ellipse, main diameter and height + * token rect x0 y0 x1 y1 h # rectangle with main side and eight + * token is the token to be returned, either a character or a symbol + * as KEY_* above + * Return 1 on success, 0 on error. + */ +static int keypad_cfg_read(struct gui_info *gui, const char *val) +{ + struct keypad_entry e; + SDL_Rect *r = NULL; + char s1[16], s2[16]; + int i, ret = 0; /* default, error */ + + if (gui == NULL || val == NULL) + return 0; + + s1[0] = s2[0] = '\0'; + bzero(&e, sizeof(e)); + i = sscanf(val, "%14s %14s %d %d %d %d %d", + s1, s2, &e.x0, &e.y0, &e.x1, &e.y1, &e.h); + + e.c = gui_map_token(s1); + if (e.c == KEY_NONE) + return 0; /* nothing found */ + switch (i) { + default: + break; + case 1: /* only "reset" is allowed */ + if (e.c != KEY_RESET) + break; + if (gui->kp) + gui->kp_used = 0; + break; + case 5: + if (e.c == KEY_KEYPAD) /* active keypad area */ + r = &gui->kp_rect; + else if (e.c == KEY_MESSAGE) + r = gui->kp_msg; + else if (e.c == KEY_DIALED) + r = gui->kp_dialed; + else if (e.c == KEY_EDIT) + r = gui->kp_edit; + if (r) { + r->x = atoi(s2); + r->y = e.x0; + r->w = e.y0; + r->h = e.x1; + break; + } + if (strcasecmp(s2, "circle")) /* invalid */ + break; + /* token circle xc yc diameter */ + e.h = e.x1; + e.y1 = e.y0; /* map radius in x1 y1 */ + e.x1 = e.x0 + e.h; /* map radius in x1 y1 */ + e.x0 = e.x0 - e.h; /* map radius in x1 y1 */ + /* fallthrough */ + + case 7: + if (e.c == KEY_FONT) { /* font - x0 y0 w h rows cols */ + ast_log(LOG_WARNING, "font not supported yet\n"); + break; + } + /* token circle|rect x0 y0 x1 y1 h */ + if (e.x1 < e.x0 || e.h <= 0) { + ast_log(LOG_WARNING, "error in coordinates\n"); + e.type = 0; + break; + } + if (!strcasecmp(s2, "circle")) { + /* for a circle we specify the diameter but store center and radii */ + e.type = KP_CIRCLE; + e.x0 = (e.x1 + e.x0) / 2; + e.y0 = (e.y1 + e.y0) / 2; + e.h = e.h / 2; + } else if (!strcasecmp(s2, "rect")) { + e.type = KP_RECT; + } else + break; + ret = 1; + } + // ast_log(LOG_WARNING, "reading [%s] returns %d %d\n", val, i, ret); + if (ret == 0) + return 0; + if (gui->kp_size == 0) { + gui->kp = ast_calloc(10, sizeof(e)); + if (gui->kp == NULL) { + ast_log(LOG_WARNING, "cannot allocate kp"); + return 0; + } + gui->kp_size = 10; + } + if (gui->kp_size == gui->kp_used) { /* must allocate */ + struct keypad_entry *a = ast_realloc(gui->kp, sizeof(e)*(gui->kp_size+10)); + if (a == NULL) { + ast_log(LOG_WARNING, "cannot reallocate kp"); + return 0; + } + gui->kp = a; + gui->kp_size += 10; + } + if (gui->kp_size == gui->kp_used) + return 0; + gui->kp[gui->kp_used++] = e; + // ast_log(LOG_WARNING, "now %d regions\n", gui->kp_used); + return 1; +} +#endif /* HAVE_SDL */ -- cgit v1.2.3