/* * Copyright (c) 2005-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. */ /* * Debug server. This runs in a separate thread and accepts authenticated * connections. Various commands useful for debugging are implemented, such * as query of display variables and surface contents. */ #include #include #if defined(AG_NETWORK) && defined(AG_THREADS) #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" #include #include #include #ifdef HAVE_JPEG #undef HAVE_STDLIB_H /* Work around SDL.h retardation */ #include #endif #define PROTO_VERSION "1.0" #define DEFAULT_PORT "1234" struct client { const char *name; const char *hostname; const char *version; AG_Window *win; int sock; TAILQ_ENTRY(client) clients; }; static AG_Mutex lock = AG_MUTEX_INITIALIZER; static TAILQ_HEAD(,client) clients = TAILQ_HEAD_INITIALIZER(clients); static NS_Server server; static int server_inited = 0; static AG_Thread listenTh; static int servRunning = 0; #ifdef HAVE_JPEG static int jpegQuality = 75; #endif static void PollClients(AG_Event *event) { AG_Tlist *tl = AG_SELF(); AG_TlistItem *it; struct client *cl; AG_MutexLock(&lock); AG_TlistClear(tl); TAILQ_FOREACH(cl, &clients, clients) { it = AG_TlistAdd(tl, NULL, "%s (%s): %s", cl->name, cl->hostname, cl->version); it->p1 = cl; it->cat = "client"; } AG_TlistRestore(tl); AG_MutexUnlock(&lock); } static int auth_password(NS_Server *ns, void *p) { char buf[64]; if (fgets(buf, sizeof(buf), stdin) == NULL) return (-1); if (strcmp(buf, "foo:bar\n") == 0) { return (1); } AG_SetError("User/password mismatch"); return (0); } static int srv_error(NS_Server *srv) { NS_Log(NS_ERR, "%s: function failed (%s)", OBJECT(srv)->name, AG_GetError()); return (-1); } static int srv_login(NS_Server *srv, void *p) { return (0); } static void srv_logout(NS_Server *srv, void *p) { } static int cmd_version(NS_Server *ns, NS_Command *cmd, void *p) { char hostname[128]; hostname[0] = '\0'; NS_BeginList(ns); NS_ListString(ns, "%s", VERSION); NS_ListString(ns, "%s", RELEASE); NS_ListString(ns, "%s", "localhost"); NS_EndList(ns); return (0); } #ifdef HAVE_JPEG static void jpegError(j_common_ptr jcomp) { fprintf(stderr, "jpeg error\n"); } static void jpegOutputMsg(j_common_ptr jcomp) { fprintf(stderr, "jpeg message\n"); } #endif /* HAVE_JPEG */ /* Send the JPEG-compressed contents of a surface to the client. */ static int cmd_surface(NS_Server *ns, NS_Command *cmd, void *pSu) { #ifdef HAVE_JPEG char sendbuf[AG_BUFFER_MAX]; static struct jpeg_error_mgr jerrmgr; static struct jpeg_compress_struct jcomp; char tmp[sizeof("/tmp/")+AG_FILENAME_MAX]; AG_Surface *su = pSu; Uint8 *jcopybuf; int i, nshots = 1; size_t len; FILE *ftmp; int fd; size_t rv; #ifdef HAVE_OPENGL if (agView->opengl && pSu == agView->v) su = AG_CaptureGLView(); #endif /* Write the JPEG to a temporary file. */ jcomp.err = jpeg_std_error(&jerrmgr); jerrmgr.error_exit = jpegError; jerrmgr.output_message = jpegOutputMsg; jpeg_create_compress(&jcomp); jcomp.image_width = su->w; jcomp.image_height = su->h; jcomp.input_components = 3; jcomp.in_color_space = JCS_RGB; jpeg_set_defaults(&jcomp); jpeg_set_quality(&jcomp, jpegQuality, TRUE); #ifdef HAVE_MKSTEMP Strlcpy(tmp, "/tmp/agarXXXXXXXX", sizeof(tmp)); if ((fd = mkstemp(tmp)) == -1) { AG_SetError("mkstemp failed (%s)", tmp); return (-1); } #else Strlcpy(tmp, ".agarsurface.tmp", sizeof(tmp)); if ((fd = open(tmp, O_RDWR|O_CREAT|O_EXCL)) == -1) { AG_SetError("mkstemp failed (%s)", tmp); return (-1); } #endif if ((ftmp = fdopen(fd, "r+")) == NULL) { AG_SetError("fdopen failed"); return (-1); } jpeg_stdio_dest(&jcomp, ftmp); jcopybuf = Malloc(su->w*3); AG_SurfaceLock(su); for (i = 0; i < nshots; i++) { JSAMPROW row[1]; int x; 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); } AG_SurfaceUnlock(su); #ifdef HAVE_OPENGL if (agView->opengl && su != pSu) AG_SurfaceFree(su); #endif /* Send the JPEG data to the client. */ len = (size_t)ftell(ftmp); rewind(ftmp); NS_BeginData(ns, len); while ((rv = fread(sendbuf, 1, sizeof(sendbuf), ftmp)) > 0) { NS_Data(ns, sendbuf, rv); } NS_EndData(ns); fclose(ftmp); AG_FileDelete(tmp); Free(jcopybuf); jpeg_destroy_compress(&jcomp); return (0); #else /* !HAVE_JPEG */ AG_SetError("libjpeg is not available"); return (-1); #endif /* HAVE_JPEG */ } /* Return information about the display format. */ static int cmd_view_fmt(NS_Server *ns, NS_Command *cmd, void *p) { NS_BeginList(ns); AG_LockVFS(agView); NS_ListString(ns, "opengl:%d", agView->opengl); NS_ListString(ns, "geom:%u:%u", agView->w, agView->h); NS_ListString(ns, "bpp:%u", agVideoInfo->vfmt->BitsPerPixel); NS_ListString(ns, "rmask:%08x", agVideoInfo->vfmt->Rmask); NS_ListString(ns, "gmask:%08x", agVideoInfo->vfmt->Gmask); NS_ListString(ns, "bmask:%08x", agVideoInfo->vfmt->Bmask); AG_UnlockVFS(agView); NS_EndList(ns); return (0); } /* Return the current refresh rate. */ static int cmd_refresh(NS_Server *ns, NS_Command *cmd, void *p) { NS_BeginList(ns); AG_LockVFS(agView); NS_ListString(ns, "%d", agView->rCur); NS_ListString(ns, "%d", agView->rNom); AG_UnlockVFS(agView); NS_EndList(ns); return (0); } #if 0 static void cmd_scan_vfs(NS_Server *ns, NS_Command *cmd, AG_Object *pob, int depth) { AG_Object *cob; NS_ListString(ns, "%s", pob->name); NS_ListString(ns, "%s", pob->cls->hier); NS_ListString(ns, "0x%08x", pob->flags); TAILQ_FOREACH(cob, &pob->children, cobjs) { cmd_scan_vfs(ns, cmd, cob, depth+1); } } /* Return information about the current virtual filesystem. */ static int cmd_scan_vfs(NS_Server *ns, NS_Command *cmd, void *p) { NS_BeginList(ns); AG_LockLinkage(); cmd_scan_vfs(ns, cmd, agWorld, 0); AG_UnlockLinkage(); NS_EndList(ns); return (0); } #endif static void * ServerLoop(void *p) { AG_TextTmsg(AG_MSG_INFO, 1000, _("Debug server started")); if (NS_Listen(&server) == -1) { AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError()); } AG_ThreadExit(NULL); return (NULL); } int DEV_DebugServerStart(void) { int rv; if (!server_inited) { AG_ObjectInitStatic(&server, &nsServerClass); AG_ObjectSetName(&server, "_DebugServer"); NS_ServerSetProtocol(&server, "agar-debug", PROTO_VERSION); NS_ServerBind(&server, NULL, DEFAULT_PORT); NS_RegAuthMode(&server, "password", auth_password, NULL); NS_RegErrorFn(&server, srv_error); NS_RegLoginFn(&server, srv_login); NS_RegLogoutFn(&server, srv_logout); NS_RegCmd(&server, "version", cmd_version, NULL); NS_RegCmd(&server, "screen", cmd_surface, agView->v); NS_RegCmd(&server, "view-fmt", cmd_view_fmt, NULL); NS_RegCmd(&server, "refresh", cmd_refresh, NULL); #if 0 NS_RegCmd(&server, "scan-vfs", cmd_scan_vfs, NULL); #endif server_inited = 1; } if ((rv = AG_ThreadCreate(&listenTh, ServerLoop, NULL)) != 0) { AG_TextMsg(AG_MSG_ERROR, "AG_ThreadCreate: %s", strerror(rv)); return (-1); } return (0); } static void StartServer(AG_Event *event) { NS_InitSubsystem(0); if (DEV_DebugServerStart() == 0) servRunning = 1; } static void StopServer(AG_Event *event) { if (servRunning) { AG_ThreadKill(listenTh, 15); servRunning = 0; } } AG_Window * DEV_DebugServer(void) { AG_Window *win; AG_Tlist *tl; win = AG_WindowNewNamed(0, "DEV_DebugServer"); AG_WindowSetCaption(win, _("Debug Server")); AG_WindowSetPosition(win, AG_WINDOW_LOWER_RIGHT, 0); tl = AG_TlistNew(NULL, AG_TLIST_POLL|AG_TLIST_EXPAND); AG_TlistSizeHint(tl, "CLIENT (255.255.255.255): 0.0-beta", 8); AG_SetEvent(tl, "tlist-poll", PollClients, NULL); AG_ButtonNewFn(win, AG_BUTTON_HFILL, _("Start server"), StartServer, "%p", tl); AG_ButtonNewFn(win, AG_BUTTON_HFILL, _("Stop server"), StopServer, "%p", tl); AG_LabelNewString(win, 0, _("Connected clients:")); AG_ObjectAttach(win, tl); return (win); } #endif /* AG_NETWORK and AG_THREADS */