From add967dccf437089ce86a8427e67c070c62dd7e8 Mon Sep 17 00:00:00 2001 From: Luigi Rizzo Date: Wed, 26 Dec 2007 10:14:11 +0000 Subject: Split console_video.c so that video codecs and gui functions are in separate files (still #include'd because of tangling in the data structures, but this is going to be cleaned up). The video grabbing functions still need to be moved to a separate file. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@94774 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- channels/console_gui.c | 880 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 880 insertions(+) create mode 100644 channels/console_gui.c (limited to 'channels/console_gui.c') diff --git a/channels/console_gui.c b/channels/console_gui.c new file mode 100644 index 000000000..4405cc709 --- /dev/null +++ b/channels/console_gui.c @@ -0,0 +1,880 @@ +/* + * GUI for console video. + * The routines here are in charge of loading the keypad and handling events. + * $Revision$ + */ + +static void cleanup_sdl(struct video_desc *env) +{ + int i; + +#ifdef HAVE_SDL_TTF + /* unload font file */ + if (env->gui.font) { + TTF_CloseFont(env->gui.font); + env->gui.font = NULL; + } + + /* uninitialize SDL_ttf library */ + if ( TTF_WasInit() ) + TTF_Quit(); +#endif + + /* uninitialize the SDL environment */ + for (i = 0; i < WIN_MAX; i++) { + if (env->win[i].bmp) + SDL_FreeYUVOverlay(env->win[i].bmp); + } + if (env->gui.keypad) + SDL_FreeSurface(env->gui.keypad); + env->gui.keypad = NULL; + SDL_Quit(); + env->screen = NULL; /* XXX check reference */ + bzero(env->win, sizeof(env->win)); + if (env->sdl_ok) + ast_mutex_destroy(&(env->in.dec_in_lock)); +} + +/* + * 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; + + if (!env->sdl_ok) + return; + + if (out == WIN_LOCAL) { /* webcam/x11 to sdl */ + b_in = &env->out.enc_in; + b_out = &env->out.loc_dpy; + p_in = NULL; + } else { + /* copy input format from the decoding context */ + AVCodecContext *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->in.rem_dpy; + p_in = (AVPicture *)env->in.d_frame; + } + bmp = env->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, &env->win[out].rect); + SDL_UnlockYUVOverlay(bmp); +} + +/* + * GUI layout, structure and management + * + +For the GUI we use SDL to create a large surface (env->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 two images: one is the skin, +the other one is a mask where the sensitive areas of the skin +are colored in different grayscale levels according to their +functions. The mapping between colors and function is defined +in the 'enum pixel_value' below. + +Mouse and keyboard events are detected on the whole surface, and +handled differently according to their location, as follows: + +- 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. + +To manage these behavior we use two status variables, +that defines if keyboard events should be redirect to dialing functions +or to write message functions, and if mouse events should be used +to implement keypad functionalities or to drag the capture device. + +Configuration options control the appeareance of the gui: + + keypad = /tmp/phone.jpg ; the keypad on the screen + keypad_font = /tmp/font.ttf ; the font to use for output + + * + */ + +/* enumerate for the pixel value. 0..127 correspond to ascii chars */ +enum pixel_value { + /* answer/close functions */ + KEY_PICK_UP = 128, + KEY_HANG_UP = 129, + + /* other functions */ + KEY_MUTE = 130, + KEY_AUTOANSWER = 131, + KEY_SENDVIDEO = 132, + KEY_LOCALVIDEO = 133, + KEY_REMOTEVIDEO = 134, + KEY_WRITEMESSAGE = 135, + KEY_GUI_CLOSE = 136, /* close gui */ + + /* other areas within the keypad */ + KEY_DIGIT_BACKGROUND = 255, + + /* areas outside the keypad - simulated */ + KEY_OUT_OF_KEYPAD = 251, + KEY_REM_DPY = 252, + KEY_LOC_DPY = 253, +}; + +/* + * Handlers for the various keypad functions + */ + +/*! \brief append a character, or reset if '\0' */ +static void append_char(char *str, int *str_pos, const char c) +{ + int i = *str_pos; + if (c == '\0') + i = 0; + else if (i < GUI_BUFFER_LEN - 1) + str[i++] = c; + else + i = GUI_BUFFER_LEN - 1; /* unnecessary, i think */ + str = '\0'; + *str_pos = i; +} + +/* 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 */ + append_char(env->gui.inbuf, &env->gui.inbuf_pos, digit); + } +} + +/* this is a wrapper for actions that are available through the cli */ +/* TODO append arg to command and send the resulting string as cli command */ +static void keypad_send_command(struct video_desc *env, char *command) +{ + ast_log(LOG_WARNING, "keypad_send_command(%s) called\n", command); + ast_cli_command(env->gui.outfd, command); + return; +} + +/* 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) +{ + ast_log(LOG_WARNING, "keypad_pick_up called\n"); + + if (env->owner) { /* someone is calling us, just answer */ + console_do_answer(-1); + } else if (env->gui.inbuf_pos) { /* we have someone to call */ + ast_cli_command(env->gui.outfd, env->gui.inbuf); + } + + append_char(env->gui.inbuf, &env->gui.inbuf_pos, '\0'); /* clear buffer */ +} + +#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) +{ +#ifndef HAVE_SDL_TTF + return 1; /* error, not supported */ +#else + int x = 30, y = 20; /* XXX change */ + SDL_Surface *output = NULL; + SDL_Color color = {0, 0, 0}; /* text color */ + SDL_Rect dest = {env->win[WIN_KEYPAD].rect.x + x, y}; + + /* clean surface each rewrite */ + SDL_BlitSurface(env->gui.keypad, NULL, env->screen, &env->win[WIN_KEYPAD].rect); + + output = TTF_RenderText_Solid(env->gui.font, text, color); + if (output == NULL) { + ast_log(LOG_WARNING, "Cannot render text on gui - %s\n", TTF_GetError()); + return 1; + } + + SDL_BlitSurface(output, NULL, env->screen, &dest); + + SDL_UpdateRects(env->gui.keypad, 1, &env->win[WIN_KEYPAD].rect); + SDL_FreeSurface(output); + return 0; /* success */ +#endif +} +#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); + +/* + * 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_button_event(struct video_desc *env, SDL_MouseButtonEvent button) +{ + uint8_t index = KEY_OUT_OF_KEYPAD; /* the key or region of the display we clicked on */ + + /* for each click we come back in normal mode */ + env->gui.text_mode = 0; + + /* define keypad boundary */ + if (button.x < env->in.rem_dpy.w) + index = KEY_REM_DPY; /* click on remote video */ + else if (button.x > env->in.rem_dpy.w + env->out.keypad_dpy.w) + index = KEY_LOC_DPY; /* click on local video */ + else if (button.y > env->out.keypad_dpy.h) + index = KEY_OUT_OF_KEYPAD; /* click outside the keypad */ + else if (env->gui.kp) { + int i; + for (i = 0; i < env->gui.kp_used; i++) { + if (kp_match_area(&env->gui.kp[i], button.x - env->in.rem_dpy.w, button.y)) { + index = env->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: + keypad_send_command(env, "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_WRITEMESSAGE: + /* goes in text-mode */ + env->gui.text_mode = 1; + 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) { + /* store points where the drag start + * and switch in drag mode */ + env->gui.x_drag = button.x; + env->gui.y_drag = button.y; + env->gui.drag_mode = 1; + } + break; + } else { + char buf[128]; + struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->out.loc_dpy : &env->in.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_GUI_CLOSE: + cleanup_sdl(env); + 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. + */ +static void handle_keyboard_input(struct video_desc *env, SDLKey key) +{ + if (env->gui.text_mode) { + /* append in the text-message buffer */ + if (key == SDLK_RETURN) { + /* send the text message and return in normal mode */ + env->gui.text_mode = 0; + keypad_send_command(env, "send text"); + } else { + /* accumulate the key in the message buffer */ + append_char(env->gui.msgbuf, &env->gui.msgbuf_pos, key); + } + } + else { + /* append in the dial buffer */ + append_char(env->gui.inbuf, &env->gui.inbuf_pos, key); + } + + return; +} + +/* + * Check if the grab point is inside the X screen. + * + * x represent the new grab value + * limit represent the upper value to use + */ +static int boundary_checks(int x, int limit) +{ + return (x <= 0) ? 0 : (x > limit ? limit : x); +} + +/* implement superlinear acceleration on the movement */ +static int move_accel(int delta) +{ + int d1 = delta*delta / 100; + return (delta > 0) ? delta + d1 : delta - d1; +} + +/* + * Move the source of the captured video. + * + * x_final_drag and y_final_drag are the coordinates where the drag ends, + * start coordinares are in the gui_info structure. + */ +static void move_capture_source(struct video_desc *env, int x_final_drag, int y_final_drag) +{ + int new_x, new_y; /* new coordinates for grabbing local video */ + int x = env->out.loc_src.x; /* old value */ + int y = env->out.loc_src.y; /* old value */ + + /* move the origin */ +#define POLARITY -1 /* +1 or -1 depending on the desired direction */ + new_x = x + POLARITY*move_accel(x_final_drag - env->gui.x_drag) * 3; + new_y = y + POLARITY*move_accel(y_final_drag - env->gui.y_drag) * 3; +#undef POLARITY + env->gui.x_drag = x_final_drag; /* update origin */ + env->gui.y_drag = y_final_drag; + + /* check boundary and let the source to grab from the new points */ + env->out.loc_src.x = boundary_checks(new_x, env->out.screen_width - env->out.loc_src.w); + env->out.loc_src.y = boundary_checks(new_y, env->out.screen_height - env->out.loc_src.h); + return; +} + +/* + * 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. + */ + +/* grab a bunch of events */ +static void eventhandler(struct video_desc *env) +{ +#define N_EVENTS 32 + int i, n; + SDL_Event ev[N_EVENTS]; + +#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.sym); + break; + case SDL_MOUSEMOTION: + if (env->gui.drag_mode != 0) + move_capture_source(env, ev[i].motion.x, ev[i].motion.y); + break; + case SDL_MOUSEBUTTONDOWN: + handle_button_event(env, ev[i].button); + break; + case SDL_MOUSEBUTTONUP: + if (env->gui.drag_mode != 0) { + move_capture_source(env, ev[i].button.x, ev[i].button.y); + env->gui.drag_mode = 0; + } + 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 *get_keypad(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; +} + +/* TODO: consistency checks, check for bpp, widht and height */ +/* Init the mask image used to grab the action. */ +static int gui_init(struct video_desc *env) +{ + /* initialize keypad status */ + env->gui.text_mode = 0; + env->gui.drag_mode = 0; + + /* initialize grab coordinates */ + env->out.loc_src.x = 0; + env->out.loc_src.y = 0; + + /* initialize keyboard buffer */ + append_char(env->gui.inbuf, &env->gui.inbuf_pos, '\0'); + append_char(env->gui.msgbuf, &env->gui.msgbuf_pos, '\0'); + +#ifdef HAVE_SDL_TTF + /* Initialize SDL_ttf library and load font */ + if (TTF_Init() == -1) { + ast_log(LOG_WARNING, "Unable to init SDL_ttf, no output available\n"); + return -1; + } + +#define GUI_FONTSIZE 28 + env->gui.font = TTF_OpenFont( env->keypad_font, GUI_FONTSIZE); + if (!env->gui.font) { + ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", env->keypad_font); + return -1; + } + ast_log(LOG_WARNING, "Loaded font %s\n", env->keypad_font); +#endif + + env->gui.outfd = open ("/dev/null", O_WRONLY); /* discard output, temporary */ + if ( env->gui.outfd < 0 ) { + ast_log(LOG_WARNING, "Unable output fd\n"); + return -1; + } + + return 0; +} + +/* 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 video_desc *env) +{ + int fd = -1; + void *p = NULL; + off_t l = 0; + + if (env->gui.keypad) + return; + env->gui.keypad = get_keypad(env->keypad_file); + if (!env->gui.keypad) + return; + + env->out.keypad_dpy.w = env->gui.keypad->w; + env->out.keypad_dpy.h = env->gui.keypad->h; + /* + * If the keypad image has a comment field, try to read + * the button location from there. The block must be + * keypad_entry = token shape x0 y0 x1 y1 h + * ... + * (basically, lines have the same format as config file entries. + * same as the keypad_entry. + * You can add it to a jpeg file using wrjpgcom + */ + do { /* only once, in fact */ + const char region[] = "region"; + int reg_len = strlen(region); + const unsigned char *s, *e; + + fd = open(env->keypad_file, O_RDONLY); + if (fd < 0) { + ast_log(LOG_WARNING, "fail to open %s\n", env->keypad_file); + break; + } + l = lseek(fd, 0, SEEK_END); + if (l <= 0) { + ast_log(LOG_WARNING, "fail to lseek %s\n", env->keypad_file); + break; + } + p = mmap(NULL, l, PROT_READ, 0, fd, 0); + if (p == NULL) { + ast_log(LOG_WARNING, "fail to mmap %s size %ld\n", env->keypad_file, (long)l); + break; + } + e = (const unsigned char *)p + l; + for (s = p; s < e - 20 ; s++) { + if (!memcmp(s, region, reg_len)) { /* keyword found */ + /* reset previous entries */ + keypad_cfg_read(&env->gui, "reset"); + break; + } + } + for ( ;s < e - 20; s++) { + char buf[256]; + const unsigned char *s1; + if (index(" \t\r\n", *s)) /* ignore blanks */ + continue; + if (*s > 127) /* likely end of comment */ + break; + if (memcmp(s, region, reg_len)) /* keyword not found */ + break; + s += reg_len; + l = MIN(sizeof(buf), e - s); + ast_copy_string(buf, s, l); + s1 = ast_skip_blanks(buf); /* between token and '=' */ + if (*s1++ != '=') /* missing separator */ + break; + if (*s1 == '>') /* skip => */ + s1++; + keypad_cfg_read(&env->gui, ast_skip_blanks(s1)); + /* now wait for a newline */ + s1 = s; + while (s1 < e - 20 && !index("\r\n", *s1) && *s1 < 128) + s1++; + s = s1; + } + } while (0); + if (p) + munmap(p, l); + if (fd >= 0) + close(fd); +} + +/* [re]set the main sdl window, useful in case of resize */ +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 = 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; + /* + * 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 + */ + + keypad_setup(env); +#define BORDER 5 /* border around our windows */ + maxw = env->in.rem_dpy.w + env->out.loc_dpy.w + env->out.keypad_dpy.w; + maxh = MAX( MAX(env->in.rem_dpy.h, env->out.loc_dpy.h), env->out.keypad_dpy.h); + maxw += 4 * BORDER; + maxh += 2 * BORDER; + env->screen = SDL_SetVideoMode(maxw, maxh, depth, 0); + if (!env->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(env->screen, &env->win[WIN_REMOTE], dpy_fmt, + env->in.rem_dpy.w, env->in.rem_dpy.h, BORDER, BORDER)) + goto no_sdl; + if (set_win(env->screen, &env->win[WIN_LOCAL], dpy_fmt, + env->out.loc_dpy.w, env->out.loc_dpy.h, + 3*BORDER+env->in.rem_dpy.w + env->out.keypad_dpy.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 (env->gui.keypad) { + struct SDL_Rect *dest = &env->win[WIN_KEYPAD].rect; + dest->x = 2*BORDER + env->in.rem_dpy.w; + dest->y = BORDER; + dest->w = env->gui.keypad->w; + dest->h = env->gui.keypad->h; + SDL_BlitSurface(env->gui.keypad, NULL, env->screen, dest); + SDL_UpdateRects(env->screen, 1, dest); + } + env->in.dec_in_cur = &env->in.dec_in[0]; + env->in.dec_in_dpy = NULL; /* nothing to display */ + env->sdl_ok = 1; + +no_sdl: + if (env->sdl_ok == 0) /* free resources in case of errors */ + cleanup_sdl(env); +} + +/* + * 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; +} + +/* + * 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 + */ +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 }, + {"AUTOANSWER", KEY_AUTOANSWER }, + {"SENDVIDEO", KEY_SENDVIDEO }, + {"LOCALVIDEO", KEY_LOCALVIDEO }, + {"REMOTEVIDEO", KEY_REMOTEVIDEO }, + {"WRITEMESSAGE", KEY_WRITEMESSAGE }, + {"GUI_CLOSE", KEY_GUI_CLOSE }, + {NULL, 0 } }; + +static int keypad_cfg_read(struct gui_info *gui, const char *val) +{ + struct keypad_entry e; + char s1[16], s2[16]; + int i, ret = 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); + + switch (i) { + default: + break; + case 1: /* only "reset" is allowed */ + if (strcasecmp(s1, "reset")) /* invalid */ + break; + if (gui->kp) { + gui->kp_used = 0; + } + break; + case 5: /* token circle xc yc diameter */ + if (strcasecmp(s2, "circle")) /* invalid */ + break; + 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: /* 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; + /* map the string into token to be returned */ + i = atoi(s1); + if (i > 0 || s1[1] == '\0') /* numbers or single characters */ + e.c = (i > 9) ? i : s1[0]; + else { + struct _s_k *p; + for (p = gui_key_map; p->s; p++) { + if (!strcasecmp(p->s, s1)) { + e.c = p->k; + break; + } + } + } + if (e.c == 0) { + ast_log(LOG_WARNING, "missing token\n"); + 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; + return 1; +} -- cgit v1.2.3