Skip to content
Snippets Groups Projects
console_gui.c 51.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Luigi Rizzo's avatar
    Luigi Rizzo committed
    					int dy = compute_drag(&drag->y_start, ev[i].motion.y, 3);
    
    					grabber_move(&env->out.devices[env->out.device_primary], dx, dy);
    				} else if (drag->drag_window == DRAG_PIP) {
    					/* move the PiP image inside the frames of the enc_in buffers */
    					int dx = ev[i].motion.x - drag->x_start;
    					int dy = ev[i].motion.y - drag->y_start;
    					/* dx and dy value are directly applied to env->out.pip_x and
    					env->out.pip_y, so they must work as if the format was cif */
    					dx = (double)dx*env->enc_in.w/env->loc_dpy.w;
    					dy = (double)dy*env->enc_in.h/env->loc_dpy.h;
    					/* sets starts to a new value */
    					drag->x_start = ev[i].motion.x;
    					drag->y_start = ev[i].motion.y;
    					/* ast_log(LOG_WARNING, "moving: %d, %d\n", dx, dy); */
    					pip_move(env, dx, dy);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    				} 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 */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	gui->drag.drag_window = DRAG_NONE;
    
    	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");
    
    }
    
    /* 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)
    
    	gui->keypad = load_image(kp_file);
    
    	if (!gui->keypad)
    
    	/* 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 */
    
    		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);
    
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    /*! \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 */
    	}
    }
    
    
    #ifdef HAVE_X11
    /*
     * SDL is not very robust on error handling, so we need to trap ourselves
     * at least the most obvious failure conditions, e.g. a bad SDL_WINDOWID.
     * As of sdl-1.2.13, SDL_SetVideoMode crashes with bad parameters, so
     * we need to do the explicit X calls to make sure the window is correct.
     * And around these calls, we must trap X errors.
     */
    static int my_x_handler(Display *d, XErrorEvent *e)
    {
    	ast_log(LOG_WARNING, "%s error_code %d\n", __FUNCTION__, e->error_code);
    	return 0;
    }
    #endif /* HAVE_X11 */
    
    
    /*! \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;
    
    	
    	/* Some helper variables used for filling the SDL window */
    	int x0; /* the x coordinate of the center of the keypad */
    	int x1; /* userful for calculating of the size of the parent window */
    	int y0; /* y coordinate of the keypad, the remote window and the local window */
    	int src_wins_tot_w; /* total width of the source windows */
    	int i;
    	int x; /* useful for the creation of the source windows; */
    	
    
    #ifdef HAVE_X11
    	const char *e = getenv("SDL_WINDOWID");
    
    	if (!ast_strlen_zero(e)) {
    		XWindowAttributes a;
    		int (*old_x_handler)(Display *d, XErrorEvent *e) = XSetErrorHandler(my_x_handler);
    		Display *d = XOpenDisplay(getenv("DISPLAY"));
    		long w = atol(e);
    		int success = w ? XGetWindowAttributes(d, w, &a) : 0;
    
    		XSetErrorHandler(old_x_handler);
    		if (!success) {
    			ast_log(LOG_WARNING, "%s error in window\n", __FUNCTION__);
    			return;
    		}
    	}	
    #endif
    
    	/*
    	 * 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:
    
    	 * - top row: thumbnails for local video sources;
    	 * - next row: message boards for local video sources
    
    	 * - 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
    	 */
    
    	if (!info || !info->vfmt) {
     		ast_log(LOG_WARNING, "Bad SDL_GetVideoInfo - %s\n",
                            SDL_GetError());
    		return;
    	}
    
    	depth = info->vfmt->BitsPerPixel;
    	if (depth < 16)
    		depth = 16;
    
    		env->gui = gui = gui_init(env->keypad_file, env->keypad_font);
    
    		goto no_sdl;
    
    		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;
    		}
    
    	
    	/* total width of the thumbnails */
    	src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER;
    	
    	/* x coordinate of the center of the keypad */
    	x0 = MAX(env->rem_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2);
    	
    	/* from center of the keypad to right border */
    	x1 = MAX(env->loc_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2);
    	
    	/* total width of the SDL window to create */
    	maxw = x0+x1;
    	
    	/* total height of the mother window to create */
    	maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h)+2*BORDER;
    	maxh += env->out.device_num ? (2*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : 0;
    	
    
    	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;
    	}
    
    
    #ifdef HAVE_X11
    	/*
    	 * Annoying as it may be, if SDL_WINDOWID is set, SDL does
    	 * not grab keyboard/mouse events or expose or other stuff,
    	 * and it does not handle resize either.
    	 * So we need to implement workarounds here.
    	 */
        do {
    	/* First, handle the event mask */
    	XWindowAttributes attr;
            long want;
            SDL_SysWMinfo info;
    	Display *SDL_Display;
            Window win;
    
    	const char *e = getenv("SDL_WINDOWID");
    	if (ast_strlen_zero(e))	 /* no external window, don't bother doing this */
    		break;
            SDL_VERSION(&info.version); /* it is important to set the version */
            if (SDL_GetWMInfo(&info) != 1) {
                    fprintf(stderr, "no wm info\n");
                    break;
            }
    	SDL_Display = info.info.x11.display;
    	if (SDL_Display == NULL)
    		break;
            win = info.info.x11.window;
    
    	/*
    	 * A list of events we want.
    	 * Leave ResizeRedirectMask to the parent.
    	 */
            want = KeyPressMask | KeyReleaseMask | ButtonPressMask |
                               ButtonReleaseMask | EnterWindowMask |
                               LeaveWindowMask | PointerMotionMask |
                               Button1MotionMask |
                               Button2MotionMask | Button3MotionMask |
                               Button4MotionMask | Button5MotionMask |
                               ButtonMotionMask | KeymapStateMask |
                               ExposureMask | VisibilityChangeMask |
                               StructureNotifyMask | /* ResizeRedirectMask | */
                               SubstructureNotifyMask | SubstructureRedirectMask |
                               FocusChangeMask | PropertyChangeMask |
                               ColormapChangeMask | OwnerGrabButtonMask;
    
    
    Steve Murphy's avatar
    Steve Murphy committed
            memset(&attr, '\0', sizeof(attr));
    
    	XGetWindowAttributes(SDL_Display, win, &attr);
    
    	/* the following events can be delivered only to one client.
    	 * So check which ones are going to someone else, and drop
    	 * them from our request.
    	 */
    	{
    	/* ev are the events for a single recipient */
    	long ev = ButtonPressMask | ResizeRedirectMask |
    			SubstructureRedirectMask;
            ev &= (attr.all_event_masks & ~attr.your_event_mask);
    	/* now ev contains 1 for single-recipient events owned by others.
    	 * We must clear those bits in 'want'
    	 * and then add the bits in 'attr.your_event_mask' to 'want'
    	 */
    	want &= ~ev;
    	want |= attr.your_event_mask;
    	}
    	XSelectInput(SDL_Display, win, want);
    
    	/* Second, handle resize.
    	 * We do part of the things that X11Resize does,
    	 * but also generate a ConfigureNotify event so
    	 * the owner of the window has a chance to do something
    	 * with it.
     	 */
    	XResizeWindow(SDL_Display, win, maxw, maxh);
    	{
    	XConfigureEvent ce = {
    		.type = ConfigureNotify,
    		.serial = 0,
    		.send_event = 1,	/* TRUE */
    		.display = SDL_Display,
    		.event = win,
    		.window = win,
    		.x = 0,
    		.y = 0,
    		.width = maxw,
    		.height = maxh,
    		.border_width = 0,
    		.above = 0,
    		.override_redirect = 0 };
    	XSendEvent(SDL_Display, win, 1 /* TRUE */, StructureNotifyMask, (XEvent *)&ce);
    	}
        } while (0);
    #endif /* HAVE_X11 */
    
    
    	y0 = env->out.device_num ? (3*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : BORDER;
    	
    
    	SDL_WM_SetCaption("Asterisk console Video Output", NULL);
    
    	
    	/* intialize the windows for local and remote video */
    
    	if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt,
    
    			env->rem_dpy.w, env->rem_dpy.h, x0-kp_w/2-BORDER-env->rem_dpy.w, y0))
    
    	/* unfreeze incoming frames if set (to avoid showing nothing) */
    	env->frame_freeze = 0;
    
    
    	if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt,
    
    			env->loc_dpy.w, env->loc_dpy.h,
    
    	
    	/* initialize device_num source windows (thumbnails) and boards
    	(for a maximum of 9 additional windows and boards) */
    	x = x0 - src_wins_tot_w/2 + BORDER;
    	for (i = 0; i < env->out.device_num; i++){
    		struct thumb_bd *p = &gui->thumb_bd_array[i];
    		if (set_win(gui->screen, &gui->win[i+WIN_SRC1], dpy_fmt,
    			SRC_WIN_W, SRC_WIN_H, x+i*(BORDER+SRC_WIN_W), BORDER))
    			goto no_sdl;
    		/* set geometry for the rect for the message board of the device */
    		p->rect.w = SRC_WIN_W;
    		p->rect.h = SRC_MSG_BD_H;
    		p->rect.x = x+i*(BORDER+SRC_WIN_W);
    		p->rect.y = 2*BORDER+SRC_WIN_H;
    		/* the white color is used as background */
    		SDL_FillRect(gui->screen, &p->rect,
    			SDL_MapRGB(gui->screen->format, 255, 255, 255));
    		/* if necessary, initialize boards for the sources */
    		if (!p->board)
    			p->board =
    				board_setup(gui->screen, &p->rect,
    				gui->font, gui->font_rects);
    		/* update board rect */
    		SDL_UpdateRect(gui->screen, p->rect.x, p->rect.y, p->rect.w, p->rect.h);
    	}
    
    
    	/* 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;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		/* set the coordinates of the keypad relative to the main screen */
    
    		dest->x = x0-kp_w/2;
    		dest->y = y0;
    
    		dest->w = kp_w;
    		dest->h = kp_h;
    		SDL_BlitSurface(gui->keypad, src, gui->screen, dest);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		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;
    
    	/* free resources in case of errors */
    
    	env->gui = cleanup_sdl(gui, env->out.device_num);
    
    }
    
    /*
     * 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 < e->h);
    
    		} 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 const struct _s_k gui_key_map[] = {
    
    	{"FREEZE",	KEY_FREEZE},
    
    	{"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 },
            {"GUI_CLOSE",	KEY_GUI_CLOSE },
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
            {"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 */
    
    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 x0 y0 w h			# horizontal rectangle (short format)
     *					# this is used e.g. for message boards
    
     * 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;
    
    	int i, ret = 0; /* default, error */
    
    	if (gui == NULL || val == NULL)
    		return 0;
    
    
    Steve Murphy's avatar
    Steve Murphy committed
    	memset(&e, '\0', 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_KEYPAD)	/* active keypad area */
    			r = &gui->kp_rect;
    		else if (e.c == KEY_MESSAGE)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			r = gui->kp_msg;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			r = gui->kp_dialed;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			r = gui->kp_edit;
    
    			r->x = atoi(s2);	/* this becomes x0 */
    			r->y = e.x0;		/* this becomes y0 */
    			r->w = e.y0;		/* this becomes w  */
    			r->h = e.x1;		/* this becomes h  */
    
    		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\n");
    
    			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\n");
    
    			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);