Newer
Older
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);
} 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->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;
gui->keypad = load_image(kp_file);
/* 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 */
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 */
}
}
#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;
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);
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;
}
/* 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;
}
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
#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;
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
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))
goto no_sdl;
/* 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,
x0+kp_w/2+BORDER, y0))
goto no_sdl;
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
/* 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 */
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 = x0-kp_w/2;
dest->y = y0;
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);
no_sdl:
env->gui = cleanup_sdl(gui, env->out.device_num);
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
}
/*
* 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},
{"PIP", KEY_PIP},
{"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 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;
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';
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)
if (gui->kp)
gui->kp_used = 0;
break;
if (e.c == KEY_KEYPAD) /* active keypad area */
r = &gui->kp_rect;
else if (e.c == KEY_MESSAGE)
else if (e.c == KEY_DIALED)
else if (e.c == KEY_EDIT)
if (r) {
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 */
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 */
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
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);
return 1;
}
#endif /* HAVE_SDL */