/* * Copyright (c) 2002-2007 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Screenshot upload tool. This runs in a separate thread, connects to the * given agar screenshot server and periodically uploads the contents of * the display in JPEG format. */ #include #include #include #include #if defined(AG_NETWORK) && defined(AG_THREADS) && defined(HAVE_JPEG) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" #undef HAVE_STDLIB_H /* Work around SDL.h retardation */ #include static AG_Thread thread; static AG_Mutex lock = AG_MUTEX_INITIALIZER; static AG_Mutex xmit_lock = AG_MUTEX_INITIALIZER; static int default_port = 1173; static int sock = -1; static int aflag = 0; static int xmit_delay = 1000; static AG_Textbox *hosttb, *porttb; static char status[128]; static struct jpeg_error_mgr jerrmgr; static struct jpeg_compress_struct jcomp; static void HandleJpegError(j_common_ptr jcomp) { AG_TextMsg(AG_MSG_ERROR, _("A JPEG error has occured.")); } static void OutputJpegMessage(j_common_ptr jcomp) { } static void XmitLoop(int fd) { int nframe = 0; FILE *fp; AG_Surface *su; Uint8 *jcopybuf; if ((fp = fdopen(fd, "w")) == NULL) { AG_TextMsg(AG_MSG_ERROR, "fdopen failed"); return; } jcomp.err = jpeg_std_error(&jerrmgr); jerrmgr.error_exit = HandleJpegError; jerrmgr.output_message = OutputJpegMessage; jpeg_create_compress(&jcomp); jcomp.image_width = agView->w; jcomp.image_height = agView->h; jcomp.input_components = 3; jcomp.in_color_space = JCS_RGB; jpeg_set_defaults(&jcomp); jpeg_set_quality(&jcomp, agScreenshotQuality, TRUE); jpeg_stdio_dest(&jcomp, fp); jcopybuf = Malloc(agView->w*3); for (;;) { JSAMPROW row[1]; int x; AG_MutexLock(&xmit_lock); if (aflag) { aflag = 0; AG_MutexUnlock(&xmit_lock); break; } Snprintf(status, sizeof(status), _("Transmitting frame %d"), nframe); if (!agView->opengl) { su = agView->v; } else { #ifdef HAVE_OPENGL su = AG_CaptureGLView(); #else su = NULL; #endif } jpeg_start_compress(&jcomp, TRUE); while (jcomp.next_scanline < jcomp.image_height) { Uint8 *pSrc = (Uint8 *)su->pixels + jcomp.next_scanline * su->pitch; Uint8 *pDst = jcopybuf; Uint8 r, g, b; for (x = agView->w; x > 0; x--) { AG_GetRGB(AG_GET_PIXEL(su,pSrc), su->format, &r,&g,&b); *pDst++ = r; *pDst++ = g; *pDst++ = b; pSrc += su->format->BytesPerPixel; } row[0] = jcopybuf; jpeg_write_scanlines(&jcomp, row, 1); } jpeg_finish_compress(&jcomp); #ifdef HAVE_OPENGL if (agView->opengl) AG_SurfaceFree(su); #endif SDL_Delay(xmit_delay); nframe++; AG_MutexUnlock(&xmit_lock); } Free(jcopybuf); jpeg_destroy_compress(&jcomp); fclose(fp); } static void * XmitThread(void *p) { char host[256]; char port[32]; struct addrinfo hints, *res, *res0; const char *cause = ""; int rv; AG_MutexLock(&lock); if (sock != -1) { AG_TextMsg(AG_MSG_ERROR, _("Already connected to a server.")); goto out1; } AG_TextboxCopyString(hosttb, host, sizeof(host)); AG_TextboxCopyString(porttb, port, sizeof(port)); if (host[0] == '\0' || port[0] == '\0') { AG_TextMsg(AG_MSG_ERROR, _("Missing server hostname/port.")); goto out1; } AG_MutexLock(&xmit_lock); Snprintf(status, sizeof(status), _("Connecting to %s:%s.."), host, port); AG_MutexUnlock(&xmit_lock); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(host, port, &hints, &res0)) != 0) { AG_TextMsg(AG_MSG_ERROR, "%s: %s", host, gai_strerror(rv)); AG_MutexLock(&xmit_lock); Snprintf(status, sizeof(status), "%s", gai_strerror(rv)); AG_MutexUnlock(&xmit_lock); goto out1; } for (sock = -1, res = res0; res != NULL; res = res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) { cause = _("failed to create socket"); continue; } if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) { cause = _("connection refused"); close(sock); sock = -1; continue; } break; } if (sock == -1) { AG_TextMsg(AG_MSG_ERROR, "%s: %s", host, cause); AG_MutexLock(&xmit_lock); Snprintf(status, sizeof(status), "%s: %s", host, cause); AG_MutexUnlock(&xmit_lock); goto out2; } AG_MutexLock(&xmit_lock); Snprintf(status, sizeof(status), _("Connected to %s"), host); AG_MutexUnlock(&xmit_lock); XmitLoop(sock); close(sock); sock = -1; out2: freeaddrinfo(res0); out1: AG_MutexUnlock(&lock); AG_ThreadExit(NULL); } static void Connect(AG_Event *event) { if (AG_ThreadCreate(&thread, XmitThread, NULL) != 0) { AG_TextMsg(AG_MSG_ERROR, "Failed to create thread!"); } } static void Disconnect(AG_Event *event) { AG_MutexLock(&xmit_lock); aflag++; Snprintf(status, sizeof(status), _("Disconnected")); AG_MutexUnlock(&xmit_lock); } AG_Window * DEV_ScreenshotUploader(void) { AG_Window *win; AG_VBox *vb; AG_HBox *hb; AG_Label *lbl; AG_Numerical *num; if ((win = AG_WindowNewNamed(AG_WINDOW_NOVRESIZE, "DEV_ScreenshotUploader")) == NULL) { return (NULL); } AG_WindowSetCaption(win, _("Screenshot")); AG_WindowSetCloseAction(win, AG_WINDOW_DETACH); vb = AG_VBoxNew(win, AG_VBOX_HFILL); { Strlcpy(status, _("Idle"), sizeof(status)); #ifdef AG_THREADS lbl = AG_LabelNewPolledMT(vb, AG_LABEL_HFILL, &xmit_lock, _("Status: %s."), &status); #else lbl = AG_LabelNewPolled(vb, AG_LABEL_HFILL, _("Status: %s."), &status); #endif AG_LabelSizeHint(lbl, 1, _("Status: Transmitting frame XXXXXXXXXX")); hosttb = AG_TextboxNew(vb, 0, _("Host: ")); porttb = AG_TextboxNew(vb, 0, _("Port: ")); AG_WidgetFocus(hosttb); num = AG_NumericalNew(vb, 0, "ms", _("Refresh rate: ")); AG_BindIntMp(num, "value", &xmit_delay, &xmit_lock); AG_NumericalSetRange(num, 1, 10000); AG_TextboxPrintf(porttb, "%i", default_port); } hb = AG_HBoxNew(win, AG_HBOX_HOMOGENOUS|AG_HBOX_HFILL|AG_HBOX_VFILL); { AG_Event *ev; AG_Button *bu; bu = AG_ButtonNew(hb, 0, _("Connect")); AG_SetEvent(bu, "button-pushed", Connect, NULL); bu = AG_ButtonNew(hb, 0, _("Disconnect")); ev = AG_SetEvent(bu, "button-pushed", Disconnect, NULL); //ev->flags |= AG_EVENT_ASYNC; } return (win); } #endif /* AG_NETWORK and AG_THREADS and HAVE_JPEG */