// Copyright 2007-2021 The Mumble Developers. All rights reserved. // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file at the root of the // Mumble source tree or at . #define GLX_GLXEXT_LEGACY #define GL_GLEXT_PROTOTYPES #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(TARGET_UNIX) # define GLX_GLXEXT_LEGACY # define GL_GLEXT_PROTOTYPES # define _GNU_SOURCE # include # include # include # include typedef unsigned char bool; # define true 1 # define false 0 #elif defined(TARGET_MAC) # include # include # include # include # # include # # include "mach_override.h" # # include "avail_mac.h" #endif #include "../overlay/overlay.h" static bool bDebug = false; static bool bCursorAvail = false; typedef struct _Context { struct _Context *next; #if defined(TARGET_UNIX) Display *dpy; GLXDrawable draw; #elif defined(TARGET_MAC) CGLContextObj cglctx; NSOpenGLContext *nsctx; #endif unsigned int uiWidth, uiHeight; unsigned int uiLeft, uiRight, uiTop, uiBottom; struct sockaddr_un saName; int iSocket; // overlay message, temporary variable for processing from socket struct OverlayMsg omMsg; // opengl overlay texture GLuint texture; // overlay texture in shared memory unsigned char *a_ucTexture; unsigned int uiMappedLength; bool bValid; bool bMesa; GLuint uiProgram; clock_t timeT; unsigned int frameCount; GLint maxVertexAttribs; GLboolean *vertexAttribStates; } Context; static const char vshader[] = "" "void main() {" "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;" "gl_TexCoord[0] = gl_MultiTexCoord0;" "}"; static const char fshader[] = "" "uniform sampler2D tex;" "void main() {" "gl_FragColor = texture2D(tex, gl_TexCoord[0].st);" "}"; const GLfloat fBorder[] = { 0.125f, 0.250f, 0.5f, 0.75f }; static Context *contexts = NULL; #define AVAIL(name) dlsym(RTLD_DEFAULT, #name) #define FDEF(name) static __typeof__(&name) o##name = NULL #if defined(TARGET_UNIX) FDEF(dlsym); FDEF(glXSwapBuffers); FDEF(glXGetProcAddressARB); FDEF(glXGetProcAddress); #elif defined(TARGET_MAC) FDEF(CGLFlushDrawable); FDEF(CGDisplayHideCursor); FDEF(CGDisplayShowCursor); #endif __attribute__((format(printf, 1, 2))) static void ods(const char *format, ...) { if (!bDebug) { return; } fprintf(stderr, "MumbleOverlay: "); va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); fprintf(stderr, "\n"); fflush(stderr); } static void newContext(Context *ctx) { ctx->iSocket = -1; ctx->omMsg.omh.iLength = -1; ctx->texture = ~0U; ctx->timeT = clock(); ctx->frameCount = 0; char *home = getenv("HOME"); if (home == NULL) { struct passwd *pwent = getpwuid(getuid()); if (pwent && pwent->pw_dir && pwent->pw_dir[0]) { home = pwent->pw_dir; } } char *xdgRuntimeDir = getenv("XDG_RUNTIME_DIR"); if (xdgRuntimeDir != NULL) { ctx->saName.sun_family = PF_UNIX; strcpy(ctx->saName.sun_path, xdgRuntimeDir); strcat(ctx->saName.sun_path, "/MumbleOverlayPipe"); } else if (home) { ctx->saName.sun_family = PF_UNIX; strcpy(ctx->saName.sun_path, home); strcat(ctx->saName.sun_path, "/.MumbleOverlayPipe"); } ods("OpenGL Version %s, Vendor %s, Renderer %s, Shader %s", glGetString(GL_VERSION), glGetString(GL_VENDOR), glGetString(GL_RENDERER), glGetString(GL_SHADING_LANGUAGE_VERSION)); const char *vsource = vshader; const char *fsource = fshader; char buffer[8192]; GLint l; GLuint vs = glCreateShader(GL_VERTEX_SHADER); GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vs, 1, &vsource, NULL); glShaderSource(fs, 1, &fsource, NULL); glCompileShader(vs); glCompileShader(fs); glGetShaderInfoLog(vs, 8192, &l, buffer); ods("VERTEX: %s", buffer); glGetShaderInfoLog(fs, 8192, &l, buffer); ods("FRAGMENT: %s", buffer); ctx->uiProgram = glCreateProgram(); glAttachShader(ctx->uiProgram, vs); glAttachShader(ctx->uiProgram, fs); glLinkProgram(ctx->uiProgram); glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &ctx->maxVertexAttribs); ctx->vertexAttribStates = calloc((size_t) ctx->maxVertexAttribs, sizeof(GLboolean)); } static void releaseMem(Context *ctx) { if (ctx->a_ucTexture) { munmap(ctx->a_ucTexture, ctx->uiMappedLength); ctx->a_ucTexture = NULL; ctx->uiMappedLength = 0; } if (ctx->texture != ~0U) { glDeleteTextures(1, &ctx->texture); ctx->texture = ~0U; } ctx->uiLeft = ctx->uiTop = ctx->uiRight = ctx->uiBottom = 0; } static void disconnect(Context *ctx) { releaseMem(ctx); ctx->uiWidth = ctx->uiHeight = 0; if (ctx->iSocket != -1) { close(ctx->iSocket); ctx->iSocket = -1; } ods("Disconnected"); } static bool sendMessage(Context *ctx, struct OverlayMsg *om) { if (ctx->iSocket != -1) { size_t wantsend = sizeof(struct OverlayMsgHeader) + (size_t) om->omh.iLength; ssize_t sent = send(ctx->iSocket, om, wantsend, MSG_DONTWAIT); if (sent != -1 && wantsend == (size_t) sent) { return true; } ods("Short write. Disconnecting pipe."); } disconnect(ctx); return false; } static void regenTexture(Context *ctx) { if (ctx->texture != ~0U) { glDeleteTextures(1, &ctx->texture); } glGenTextures(1, &ctx->texture); glBindTexture(GL_TEXTURE_2D, ctx->texture); glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, fBorder); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) ctx->uiWidth, (GLsizei) ctx->uiHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, ctx->a_ucTexture); } static void drawOverlay(Context *ctx, unsigned int width, unsigned int height) { // if no socket is active, initialize and connect to socket if (ctx->iSocket == -1) { releaseMem(ctx); if (!ctx->saName.sun_path[0]) return; ctx->iSocket = socket(AF_UNIX, SOCK_STREAM, 0); if (ctx->iSocket == -1) { ods("socket() failure"); return; } fcntl(ctx->iSocket, F_SETFL, O_NONBLOCK, 1); if (connect(ctx->iSocket, (struct sockaddr *) (&ctx->saName), sizeof(ctx->saName)) != 0) { close(ctx->iSocket); ctx->iSocket = -1; ods("connect() failure %s", ctx->saName.sun_path); return; } ods("Socket connected"); struct OverlayMsg om; om.omh.uiMagic = OVERLAY_MAGIC_NUMBER; om.omh.uiType = OVERLAY_MSGTYPE_PID; om.omh.iLength = sizeof(struct OverlayMsgPid); om.omp.pid = (unsigned int) getpid(); // getpid can't fail if (!sendMessage(ctx, &om)) return; ods("SentPid"); } // if overlay size (width or height) is not up-to-date create and send an overlay initialization message if ((ctx->uiWidth != width) || (ctx->uiHeight != height)) { ods("Sending init overlay msg with w h %i %i", width, height); releaseMem(ctx); ctx->uiWidth = width; ctx->uiHeight = height; struct OverlayMsg om; om.omh.uiMagic = OVERLAY_MAGIC_NUMBER; om.omh.uiType = OVERLAY_MSGTYPE_INIT; om.omh.iLength = sizeof(struct OverlayMsgInit); om.omi.uiWidth = ctx->uiWidth; om.omi.uiHeight = ctx->uiHeight; if (!sendMessage(ctx, &om)) return; } // receive and process overlay messages while (1) { if (ctx->omMsg.omh.iLength < 0) { // receive the overlay message header ssize_t length = recv(ctx->iSocket, ctx->omMsg.headerbuffer, sizeof(struct OverlayMsgHeader), 0); if (length < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) break; disconnect(ctx); return; } else if (length != sizeof(struct OverlayMsgHeader)) { ods("Short header read on overlay message"); disconnect(ctx); return; } } else { // receive the overlay message body ssize_t length = recv(ctx->iSocket, ctx->omMsg.msgbuffer, (size_t) ctx->omMsg.omh.iLength, 0); if (length < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) break; disconnect(ctx); return; } else if (length != ctx->omMsg.omh.iLength) { ods("Short overlay message read %x %zd/%d", ctx->omMsg.omh.uiType, length, ctx->omMsg.omh.iLength); disconnect(ctx); return; } // set len to -1 again for a clean state on next receive ctx->omMsg.omh.iLength = -1; switch (ctx->omMsg.omh.uiType) { // shared memory overlay message: case OVERLAY_MSGTYPE_SHMEM: { struct OverlayMsgShmem *oms = (struct OverlayMsgShmem *) &ctx->omMsg.omi; ods("SHMEM %s", oms->a_cName); releaseMem(ctx); int fd = shm_open(oms->a_cName, O_RDONLY, 0600); if (fd != -1) { struct stat buf; if (fstat(fd, &buf) != -1) { unsigned int buflen = buf.st_size; if (buflen >= ctx->uiWidth * ctx->uiHeight * 4 && buflen < 512 * 1024 * 1024) { ctx->uiMappedLength = buflen; ctx->a_ucTexture = mmap(NULL, (size_t) buflen, PROT_READ, MAP_SHARED, fd, 0); if (ctx->a_ucTexture != MAP_FAILED) { // mmap successfull; send a new bodyless sharedmemory overlay message and regenerate // the overlay texture struct OverlayMsg om; om.omh.uiMagic = OVERLAY_MAGIC_NUMBER; om.omh.uiType = OVERLAY_MSGTYPE_SHMEM; om.omh.iLength = 0; if (!sendMessage(ctx, &om)) return; regenTexture(ctx); continue; } ctx->a_ucTexture = NULL; } ctx->uiMappedLength = 0; } else { ods("Failed to fstat memory map"); } close(fd); } ods("Failed to map memory"); } break; // blit overlay message: blit overlay texture from shared memory to gl-texture var case OVERLAY_MSGTYPE_BLIT: { struct OverlayMsgBlit *omb = &ctx->omMsg.omb; ods("BLIT %d %d %d %d", omb->x, omb->y, omb->w, omb->h); if ((ctx->a_ucTexture != NULL) && (ctx->texture != ~0U)) { glBindTexture(GL_TEXTURE_2D, ctx->texture); if ((omb->x == 0) && (omb->y == 0) && (omb->w == ctx->uiWidth) && (omb->h == ctx->uiHeight)) { ods("Optimzied fullscreen blit"); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) ctx->uiWidth, (GLsizei) ctx->uiHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, ctx->a_ucTexture); } else { // allocate temporary memory unsigned int x = omb->x; unsigned int y = omb->y; unsigned int w = omb->w; unsigned int h = omb->h; unsigned char *ptr = (unsigned char *) malloc(w * h * 4); unsigned int row; memset(ptr, 0, w * h * 4); // copy overlay texture to temporary memory to adapt to full opengl ui size (overlay at // correct place) for (row = 0; row < h; ++row) { const unsigned char *sptr = ctx->a_ucTexture + 4 * ((y + row) * ctx->uiWidth + x); unsigned char *dptr = ptr + 4 * w * row; memcpy(dptr, sptr, w * 4); } // copy temporary texture to opengl glTexSubImage2D(GL_TEXTURE_2D, 0, (GLint) x, (GLint) y, (GLint) w, (GLint) h, GL_BGRA, GL_UNSIGNED_BYTE, ptr); free(ptr); } } } break; case OVERLAY_MSGTYPE_ACTIVE: { struct OverlayMsgActive *oma = &ctx->omMsg.oma; ods("ACTIVE %d %d %d %d", oma->x, oma->y, oma->w, oma->h); ctx->uiLeft = oma->x; ctx->uiTop = oma->y; ctx->uiRight = oma->x + oma->w; ctx->uiBottom = oma->y + oma->h; } break; case OVERLAY_MSGTYPE_INTERACTIVE: { #if defined(TARGET_MAC) struct OverlayMsgInteractive *omin = &ctx->omMsg.omin; ods("Interactive %d", omin->state); if (bCursorAvail) { if (omin->state) { oCGDisplayHideCursor(kCGNullDirectDisplay); } else { oCGDisplayShowCursor(kCGNullDirectDisplay); } } #endif } break; default: break; } } } if ((ctx->a_ucTexture == NULL) || (ctx->texture == ~0U)) return; // texture checks, that our gltexture is still valid and sane if (!glIsTexture(ctx->texture)) { ctx->texture = ~0U; ods("Lost texture"); regenTexture(ctx); } else { glBindTexture(GL_TEXTURE_2D, ctx->texture); GLfloat bordercolor[4]; glGetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bordercolor); if (bordercolor[0] != fBorder[0] || bordercolor[1] != fBorder[1] || bordercolor[2] != fBorder[2] || bordercolor[3] != fBorder[3]) { ods("Texture was hijacked! Texture will be regenerated."); regenTexture(ctx); } } glBindTexture(GL_TEXTURE_2D, ctx->texture); glPushMatrix(); float w = (float) (ctx->uiWidth); float h = (float) (ctx->uiHeight); float left = (float) (ctx->uiLeft); float top = (float) (ctx->uiTop); float right = (float) (ctx->uiRight); float bottom = (float) (ctx->uiBottom); float xm = left / w; float ym = top / h; float xmx = right / w; float ymx = bottom / h; GLfloat vertex[] = { left, bottom, left, top, right, top, left, bottom, right, top, right, bottom }; glVertexPointer(2, GL_FLOAT, 0, vertex); GLfloat tex[] = { xm, ymx, xm, ym, xmx, ym, xm, ymx, xmx, ym, xmx, ymx }; glTexCoordPointer(2, GL_FLOAT, 0, tex); glDrawArrays(GL_TRIANGLES, 0, 6); glPopMatrix(); } static void drawContext(Context *ctx, int width, int height) { // calculate FPS and send it as an overlay message clock_t t = clock(); float elapsed = (float) (t - ctx->timeT) / CLOCKS_PER_SEC; ++(ctx->frameCount); if (elapsed > OVERLAY_FPS_INTERVAL) { struct OverlayMsg om; om.omh.uiMagic = OVERLAY_MAGIC_NUMBER; om.omh.uiType = OVERLAY_MSGTYPE_FPS; om.omh.iLength = sizeof(struct OverlayMsgFps); om.omf.fps = (float) ctx->frameCount / elapsed; sendMessage(ctx, &om); ctx->frameCount = 0; ctx->timeT = t; } GLuint program; GLint viewport[4]; int i; glPushAttrib(GL_ALL_ATTRIB_BITS); glPushClientAttrib(GL_ALL_ATTRIB_BITS); glGetIntegerv(GL_VIEWPORT, viewport); glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *) &program); glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, width, height, 0, -100.0, 100.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_TEXTURE); glPushMatrix(); glLoadIdentity(); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDisable(GL_ALPHA_TEST); glDisable(GL_AUTO_NORMAL); // Skip clip planes, there are thousands of them. glDisable(GL_COLOR_LOGIC_OP); glDisable(GL_COLOR_TABLE); glDisable(GL_CONVOLUTION_1D); glDisable(GL_CONVOLUTION_2D); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glDisable(GL_DITHER); glDisable(GL_FOG); glDisable(GL_HISTOGRAM); glDisable(GL_INDEX_LOGIC_OP); glDisable(GL_LIGHTING); glDisable(GL_NORMALIZE); // Skip line smmooth // Skip map glDisable(GL_MINMAX); // Skip polygon offset glDisable(GL_SEPARABLE_2D); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST); GLboolean b = 0; glGetBooleanv(GL_TEXTURE_GEN_Q, &b); if (b) glDisable(GL_TEXTURE_GEN_Q); glGetBooleanv(GL_TEXTURE_GEN_R, &b); if (b) glDisable(GL_TEXTURE_GEN_R); glGetBooleanv(GL_TEXTURE_GEN_S, &b); if (b) glDisable(GL_TEXTURE_GEN_S); glGetBooleanv(GL_TEXTURE_GEN_T, &b); if (b) glDisable(GL_TEXTURE_GEN_T); glRenderMode(GL_RENDER); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_INDEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_EDGE_FLAG_ARRAY); glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); glPixelStorei(GL_UNPACK_LSB_FIRST, 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); GLint texunits = 1; glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texunits); for (i = texunits - 1; i >= 0; --i) { glActiveTexture(GL_TEXTURE0 + (GLenum) i); glDisable(GL_TEXTURE_1D); glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_3D); } glDisable(GL_TEXTURE_CUBE_MAP); glDisable(GL_VERTEX_PROGRAM_ARB); glDisable(GL_FRAGMENT_PROGRAM_ARB); GLint enabled; for (i = 0; i < ctx->maxVertexAttribs; ++i) { enabled = GL_FALSE; glGetVertexAttribiv((GLuint) i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled); if (enabled == GL_TRUE) { glDisableVertexAttribArray((GLuint) i); ctx->vertexAttribStates[i] = GL_TRUE; } } glUseProgram(ctx->uiProgram); glEnable(GL_COLOR_MATERIAL); glEnable(GL_TEXTURE_2D); glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glMatrixMode(GL_MODELVIEW); GLint uni = glGetUniformLocation(ctx->uiProgram, "tex"); glUniform1i(uni, 0); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); GLuint bound = 0, vbobound = 0; glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, (GLint *) &bound); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint *) &vbobound); if (bound != 0) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); } if (vbobound != 0) { glBindBuffer(GL_ARRAY_BUFFER, 0); } drawOverlay(ctx, (unsigned int) width, (unsigned int) height); if (bound != 0) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bound); } if (vbobound != 0) { glBindBuffer(GL_ARRAY_BUFFER, vbobound); } for (i = 0; i < ctx->maxVertexAttribs; ++i) { if (ctx->vertexAttribStates[i] == GL_TRUE) { glEnableVertexAttribArray((GLuint) i); ctx->vertexAttribStates[i] = GL_FALSE; } } glMatrixMode(GL_TEXTURE); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopClientAttrib(); glPopAttrib(); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); glUseProgram(program); // drain opengl error queue while (glGetError() != GL_NO_ERROR) ; } #if defined(TARGET_UNIX) # include "init_unix.c" #elif defined(TARGET_MAC) # include "init_mac.c" #endif