/* * Copyright (c) 2022-2023 Julien Nadeau Carriere * 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. */ /* * Common code for SDL 2.0 drivers. */ #include #include #include #include #include #include #include #define AG_SDL2_CLIPPED_PIXEL(s, ax, ay) \ ((ax) < (s)->clip_rect.x || \ (ax) >= (s)->clip_rect.x + (s)->clip_rect.w || \ (ay) < (s)->clip_rect.y || \ (ay) >= (s)->clip_rect.y + (s)->clip_rect.h) /* #define DEBUG_KEYBOARD */ /* #define DEBUG_MOUSE */ /* #define DEBUG_UNKNOWN_EVENTS */ static void AG_SDL2_SoftBlit_Colorkey(const AG_Surface *_Nonnull, AG_Rect, SDL_Surface *_Nonnull, AG_Rect); static void AG_SDL2_SoftBlit_NoColorkey(const AG_Surface *_Nonnull, AG_Rect, SDL_Surface *_Nonnull, AG_Rect); /* * Initialize Agar with an existing SDL2 display. If the display surface has * flags SDL_OPENGL / SDL_OPENGLBLIT set, the "sdl2gl" driver is selected; * otherwise, the "sdl2fb" driver is used. */ int AG_InitVideoSDL2(void *pDisplay, Uint flags) { SDL_Surface *display = pDisplay; AG_Driver *drv = NULL; AG_DriverClass *dc = NULL, **pd; int useGL = 0; if (AG_InitGUIGlobals() == -1) return (-1); #if 0 /* Enable OpenGL mode if the surface has SDL_OPENGL set. */ if ((display->flags & SDL_OPENGL) #ifdef SDL_OPENGLBLIT || (display->flags & SDL_OPENGLBLIT) #endif ) { useGL = 1; } else { if (flags & AG_VIDEO_OPENGL) { AG_SetErrorS(_("AG_VIDEO_OPENGL flag requested and " "display surface is !SDL_OPENGL")); goto fail; } } #endif for (pd = &agDriverList[0]; *pd != NULL; pd++) { if (((*pd)->wm == AG_WM_SINGLE) && ((*pd)->flags & AG_DRIVER_SDL2)) { if (useGL) { if (!((*pd)->flags & AG_DRIVER_OPENGL)) continue; } else { if ((*pd)->flags & AG_DRIVER_OPENGL) continue; } dc = *pd; break; } } if (dc == NULL) { AG_SetError(_("No compatible %s driver is available"), useGL ? "SDL2/OpenGL" : "SDL2"); goto fail; } if ((drv = AG_DriverOpen(dc, NULL)) == NULL) goto fail; /* Open a video display. */ if (AGDRIVER_SW_CLASS(drv)->openVideoContext(drv, (void *)display, flags) == -1) { AG_DriverClose(drv); goto fail; } if (drv->videoFmt == NULL) AG_FatalError("Driver did not set video format"); /* Generic Agar-GUI initialization. */ if (AG_InitGUI(0) == -1) { AG_DriverClose(drv); goto fail; } agDriverOps = dc; agDriverSw = AGDRIVERSW(drv); return (0); fail: AG_DestroyGUIGlobals(); return (-1); } /* * Reattach to a different SDL2 display surface. */ int AG_SetVideoSurfaceSDL2(void *pDisplay) { if (agDriverSw == NULL || !(agDriverOps->flags & AG_DRIVER_SDL2)) { AG_SetErrorS(_("Current driver is not an SDL2 driver")); return (-1); } if (AGDRIVER_SW_CLASS(agDriverSw)->setVideoContext(agDriverSw, pDisplay) == -1) { return (-1); } AG_PostResizeDisplay(agDriverSw); return (0); } /* Return the Agar pixel format corresponding to that of an SDL2 surface. */ AG_PixelFormat * AG_SDL2_GetPixelFormat(const SDL_Surface *s) { AG_PixelFormat *pf = Malloc(sizeof(AG_PixelFormat)); SDL_Palette *sp; int i; if ((sp = s->format->palette) != NULL) { AG_PixelFormatIndexed(pf, s->format->BitsPerPixel); for (i = 0; i < sp->ncolors; i++) { AG_Color *c = &pf->palette->colors[i]; c->r = AG_8toH(sp->colors[i].r); c->g = AG_8toH(sp->colors[i].g); c->b = AG_8toH(sp->colors[i].b); c->a = AG_OPAQUE; } } else { AG_PixelFormatRGBA(pf, s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, s->format->Bmask, s->format->Amask); } return (pf); } /* Return a 32-bit representation of the pixel at p in an SDL surface. */ static __inline__ Uint32 AG_SDL2_GetPixel(SDL_Surface *_Nonnull s, Uint8 *_Nonnull p) { switch (s->format->BytesPerPixel) { case 4: return *(Uint32 *)p; case 3: #if AG_BYTEORDER == AG_BIG_ENDIAN return (p[0] << 16) + (p[1] << 8) + p[2]; #else return (p[0] + (p[1] << 8) + (p[2] << 16)); #endif case 2: return *(Uint16 *)p; default: return *p; } } /* Write the pixel at p in an SDL surface. */ static __inline__ void AG_SDL2_PutPixel(SDL_Surface *_Nonnull s, Uint8 *_Nonnull p, Uint32 px) { switch (s->format->BytesPerPixel) { case 4: *(Uint32 *)p = px; break; case 3: #if AG_BYTEORDER == AG_BIG_ENDIAN p[0] = (px >> 16) & 0xff; p[1] = (px >> 8) & 0xff; p[2] = (px) & 0xff; #else p[0] = (px >> 16) & 0xff; p[1] = (px >> 8) & 0xff; p[2] = (px) & 0xff; #endif break; case 2: *(Uint16 *)p = px; break; default: *p = px; break; } } static __inline__ void AG_SDL2_PutPixel2(SDL_Surface *_Nonnull S, int x, int y, Uint32 px) { AG_SDL2_PutPixel(S, (Uint8 *)S->pixels + y*S->pitch + x*S->format->BytesPerPixel, px); } /* * Blend an AG_Color c against the pixel at p in an SDL_Surface. * The surface must be locked. */ static void AG_SDL2_SurfaceBlend(SDL_Surface *_Nonnull s, Uint8 *_Nonnull p, AG_Color c, AG_AlphaFn fn) { Uint32 px = AG_SDL2_GetPixel(s,p); Uint8 r = AG_Hto8(c.r); Uint8 g = AG_Hto8(c.g); Uint8 b = AG_Hto8(c.b); Uint8 a = AG_Hto8(c.a); Uint8 R,G,B,A; SDL_GetRGBA(px, s->format, &R,&G,&B,&A); switch (fn) { case AG_ALPHA_OVERLAY: a = (Uint8)((A+a) > 255) ? 255 : (A+a); break; case AG_ALPHA_DST: a = A; break; case AG_ALPHA_SRC: /* a = a; */ break; case AG_ALPHA_ZERO: a = 0; break; case AG_ALPHA_ONE_MINUS_DST: a = 255-A; break; case AG_ALPHA_ONE_MINUS_SRC: a = 255-a; break; case AG_ALPHA_ONE: default: a = 255; break; } AG_SDL2_PutPixel(s, p, SDL_MapRGBA(s->format, R+(((r - R)*a) >> 8), G+(((g - G)*a) >> 8), B+(((b - B)*a) >> 8), a)); } /* * Blit an AG_Surface directly to an SDL_Surface. * If the formats of the two surfaces may differ, convert implicitely. * The source surface must be locked. Locks the target SDL surface if needed. */ /* XXX TODO optimized cases, per-surface alpha */ void AG_SDL2_BlitSurface(const AG_Surface *S, const AG_Rect *srcRect, SDL_Surface *D, int xDst, int yDst) { AG_Rect sr, dr, cr; #ifdef AG_DEBUG if (S->flags & AG_SURFACE_TRACE) Debug(NULL, "SURFACE(%dx%dx%d): " AGSI_BR_BLU "Blit" AGSI_RST " to SDL_Surface (%p) @ %d,%d\n", S->w, S->h, S->format.BitsPerPixel, D, xDst, yDst); #endif if (srcRect != NULL) { sr = *srcRect; if (sr.x < 0) { sr.x = 0; } if (sr.y < 0) { sr.y = 0; } if (sr.x+sr.w >= S->w) { sr.w = S->w - sr.x; } if (sr.y+sr.h >= S->h) { sr.h = S->h - sr.y; } if (sr.w < 0) { sr.w = 0; } if (sr.h < 0) { sr.h = 0; } } else { sr.x = 0; sr.y = 0; sr.w = S->w; sr.h = S->h; } dr.x = xDst; dr.y = yDst; dr.w = sr.w; dr.h = sr.h; cr.x = D->clip_rect.x; cr.y = D->clip_rect.y; cr.w = D->clip_rect.w; cr.h = D->clip_rect.h; if (!AG_RectIntersect(&dr, &dr, &cr)) return; if (sr.w > dr.w) { /* Partial */ if (xDst < cr.x) { sr.x += (cr.x - xDst); } sr.w -= (sr.w - dr.w); } if (sr.h > dr.h) { if (yDst < cr.y) { sr.y += (cr.y - yDst); } sr.h -= (sr.h - dr.h); } if (sr.w <= 0 || sr.h <= 0) return; if (SDL_MUSTLOCK(D)) { SDL_LockSurface(D); } if (S->flags & AG_SURFACE_COLORKEY) { AG_SDL2_SoftBlit_Colorkey(S,sr, D,dr); } else { AG_SDL2_SoftBlit_NoColorkey(S,sr, D,dr); } if (SDL_MUSTLOCK(D)) { SDL_UnlockSurface(D); } } static void AG_SDL2_SoftBlit_Colorkey(const AG_Surface *_Nonnull S, AG_Rect sr, SDL_Surface *_Nonnull ds, AG_Rect dr) { const int dsBytesPerPixel = ds->format->BytesPerPixel; Uint8 *pDst = (Uint8 *)ds->pixels + dr.y * ds->pitch + dr.x * dsBytesPerPixel; const int dsPadding = ds->pitch - dr.w * dsBytesPerPixel; int x, y; for (y = 0; y < sr.h; y++) { for (x = 0; x < sr.w; x++) { AG_Pixel px; AG_Color c; px = AG_SurfaceGet(S, sr.x+x, sr.y+y); if (S->colorkey == px) { pDst += dsBytesPerPixel; continue; } AG_GetColor(&c, px, &S->format); if (c.a < AG_OPAQUE) { AG_SDL2_SurfaceBlend(ds, pDst, c, AG_ALPHA_SRC); } else { AG_SDL2_PutPixel2(ds, dr.x + x, dr.y + y, SDL_MapRGB(ds->format, AG_Hto8(c.r), AG_Hto8(c.g), AG_Hto8(c.b))); } pDst += dsBytesPerPixel; } pDst += dsPadding; } } static void AG_SDL2_SoftBlit_NoColorkey(const AG_Surface *_Nonnull S, AG_Rect sr, SDL_Surface *_Nonnull ds, AG_Rect dr) { const int dsBytesPerPixel = ds->format->BytesPerPixel; Uint8 *pDst = (Uint8 *)ds->pixels + dr.y * ds->pitch + dr.x * dsBytesPerPixel; const int dsPadding = ds->pitch - dr.w * dsBytesPerPixel; int x, y; for (y = 0; y < dr.h; y++) { for (x = 0; x < dr.w; x++) { AG_Pixel px; AG_Color c; px = AG_SurfaceGet(S, sr.x+x, sr.y+y); AG_GetColor(&c, px, &S->format); if (c.a < AG_OPAQUE) { AG_SDL2_SurfaceBlend(ds, pDst, c, AG_ALPHA_SRC); } else { AG_SDL2_PutPixel2(ds, dr.x + x, dr.y + y, SDL_MapRGB(ds->format, AG_Hto8(c.r), AG_Hto8(c.g), AG_Hto8(c.b))); } pDst += dsBytesPerPixel; } pDst += dsPadding; } } /* * Import an SDL surface into a newly allocated AG_Surface (of the same * pixel format so the pixel data can be block copied). * For indexed surfaces, convert the SDL_Palette to an AG_Palette. */ AG_Surface * AG_SDL2_ImportSurface(SDL_Surface *src) { AG_PixelFormat pf; AG_Surface *dst; const SDL_Palette *sp; int i; if ((sp = src->format->palette) != NULL) { AG_PixelFormatIndexed(&pf, src->format->BitsPerPixel); #ifdef AG_DEBUG if (sp->ncolors != pf.palette->nColors) AG_FatalError("nColors"); #endif for (i = 0; i < sp->ncolors; i++) { const SDL_Color *sc = &sp->colors[i]; AG_Color *c = &pf.palette->colors[i]; c->r = AG_8toH(sc->r); c->g = AG_8toH(sc->g); c->b = AG_8toH(sc->b); c->a = AG_OPAQUE; } } else { AG_PixelFormatRGBA(&pf, src->format->BitsPerPixel, src->format->Rmask, src->format->Gmask, src->format->Bmask, src->format->Amask); } dst = AG_SurfaceNew(&pf, src->w, src->h, 0); if (SDL_MUSTLOCK(src)) SDL_LockSurface(src); memcpy(dst->pixels, (Uint8 *)src->pixels, src->h * src->pitch); if (SDL_MUSTLOCK(src)) SDL_UnlockSurface(src); AG_PixelFormatFree(&pf); return (dst); } /* Convert a SDL2 surface to Agar surface. */ AG_Surface * AG_SurfaceFromSDL2(void *p) { return AG_SDL2_ImportSurface((SDL_Surface *)p); } /* Export an AG_Surface to a newly-created SDL_Surface. */ void * AG_SurfaceExportSDL2(const AG_Surface *ss) { Uint32 sdlFlags = SDL_SWSURFACE; SDL_Surface *ds; /* TODO INDEXED->INDEXED & GRAYSCALE->PACKED */ ds = SDL_CreateRGBSurface(sdlFlags, ss->w, ss->h, ss->format.BitsPerPixel, ss->format.Rmask, ss->format.Gmask, ss->format.Bmask, ss->format.Amask); if (ds == NULL) { AG_SetError("SDL_CreateRGBSurface: %s", SDL_GetError()); return (NULL); } AG_SDL2_BlitSurface(ss, NULL, ds, 0,0); return (void *)ds; } /* Initialize the default cursor. */ void AG_SDL2_InitDefaultCursor(void *obj) { AG_Driver *drv = AGDRIVER(obj); AG_Cursor *ac; SDL_Cursor *sc; if ((sc = SDL_GetCursor()) == NULL) { AG_FatalError("SDL_GetCursor"); } ac = Malloc(sizeof(AG_Cursor)); AG_CursorInit(ac); ac->p = sc; TAILQ_INSERT_HEAD(&drv->cursors, ac, cursors); drv->nCursors++; } /* Change the cursor. */ int AG_SDL2_SetCursor(void *obj, AG_Cursor *ac) { AG_Driver *drv = obj; if (drv->activeCursor == ac) return (0); SDL_SetCursor((SDL_Cursor *)ac->p); drv->activeCursor = ac; return (0); } /* Revert to the default cursor. */ void AG_SDL2_UnsetCursor(void *obj) { AG_Driver *drv = obj; AG_Cursor *ac0 = TAILQ_FIRST(&drv->cursors); if (drv->activeCursor == ac0) return; SDL_SetCursor((SDL_Cursor *)ac0->p); drv->activeCursor = ac0; } /* Configure refresh rate. */ int AG_SDL2_SetRefreshRate(void *obj, int fps) { AG_DriverSw *dsw = obj; if (fps < 1) { AG_SetError(_("Invalid refresh rate")); return (-1); } dsw->rNom = 1000/fps; return (0); } /* Create a cursor. */ AG_Cursor * AG_SDL2_CreateCursor(void *obj, Uint w, Uint h, const Uint8 *data, const Uint8 *mask, int xHot, int yHot) { AG_Cursor *ac; SDL_Cursor *sc; Uint size = w*h; if ((ac = TryMalloc(sizeof(AG_Cursor))) == NULL) { return (NULL); } if ((ac->data = TryMalloc(size)) == NULL) { free(ac); return (NULL); } if ((ac->mask = TryMalloc(size)) == NULL) { free(ac->data); free(ac); return (NULL); } memcpy(ac->data, data, size); memcpy(ac->mask, mask, size); ac->w = w; ac->h = h; ac->xHot = xHot; ac->yHot = yHot; sc = SDL_CreateCursor(ac->data, ac->mask, ac->w, ac->h, ac->xHot, ac->yHot); if (sc == NULL) { AG_SetError(_("SDL_CreateCursor() failed")); goto fail; } ac->p = (void *)sc; return (ac); fail: free(ac->data); free(ac->mask); free(ac); return (NULL); } /* Release a cursor. */ void AG_SDL2_FreeCursor(void *obj, AG_Cursor *ac) { AG_Driver *drv = obj; if (ac == drv->activeCursor) drv->activeCursor = NULL; SDL_FreeCursor((SDL_Cursor *)(ac->p)); free(ac->data); free(ac->mask); free(ac); } /* Retrieve cursor visibility status. */ int AG_SDL2_GetCursorVisibility(void *obj) { return (SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE); } /* Set cursor visibility. */ void AG_SDL2_SetCursorVisibility(void *obj, int flag) { SDL_ShowCursor(flag ? SDL_ENABLE : SDL_DISABLE); } /* Return the desktop display size in pixels. */ int AG_SDL2_GetDisplaySize(Uint *w, Uint *h) { SDL_DisplayMode mode; int i; for (i = 0; i < SDL_GetNumVideoDisplays(); i++) { int ret = SDL_GetCurrentDisplayMode(i, &mode); if (ret != 0) { AG_SetError("SDL_GetCurrentDisplayMode: %s", SDL_GetError()); return (-1); } *w = mode.w; *h = mode.h; return (0); } AG_SetErrorS(_("No video display")); return (-1); } /* Apply default display settings where 0 is specified. */ void AG_SDL2_GetPrefDisplaySettings(void *obj, Uint *w, Uint *h, int *depth) { char buf[16], *ep; AG_Driver *drv = obj; AG_DriverSw *dsw = obj; Uint wDisp, hDisp; if (*w == 0 || *h == 0) { if (AG_SDL2_GetDisplaySize(&wDisp, &hDisp) == -1) { wDisp = 640; hDisp = 480; } } if (*w == 0) { if (AG_Defined(drv, "width")) { AG_GetString(drv, "width", buf, sizeof(buf)); if (buf[0] == 'a') { /* auto */ *w = wDisp; } else { Uint wNew = strtol(buf, &ep, 10); if (ep != NULL && ep[0] == '%') { *w = wDisp*wNew/100; } else { *w = wNew; } } } else { *w = (wDisp > 1000) ? (wDisp - wDisp*0.3) : wDisp; } } if (*h == 0) { if (AG_Defined(drv, "height")) { AG_GetString(drv, "height", buf, sizeof(buf)); if (buf[0] == 'a') { /* auto */ *h = hDisp; } else { Uint hNew = strtol(buf, &ep, 10); if (ep != NULL && ep[0] == '%') { *h = hDisp*hNew/100; } else { *h = hNew; } } } else { *h = (hDisp > 700) ? (hDisp - hDisp*0.3) : hDisp; } } if (*depth == 0) { if (AG_Defined(drv, "depth")) { AG_GetString(drv, "depth", buf, sizeof(buf)); *depth = (buf[0] == 'a') ? 32 : atoi(buf); } else { *depth = 32; } } if (AG_Defined(drv, "fpsMax")) { char *ep; float v; AG_GetString(drv, "fpsMax", buf, sizeof(buf)); v = (float)strtod(buf, &ep); if (*ep == '\0') { dsw->rNom = (Uint)(1000.0f/v); Verbose("%s: max fps %s (%u ms)\n", OBJECT(drv)->name, buf, dsw->rNom); } } if (AG_Defined(drv, "bgColor")) { Verbose("%s: bgColor -> %s\n", OBJECT(drv)->name, AG_GetStringP(drv,"bgColor")); AG_ColorFromString(&dsw->bgColor, AG_GetStringP(drv,"bgColor"), NULL); } if (!AG_Defined(drv, "!bgPopup")) dsw->flags |= AG_DRIVER_SW_BGPOPUP; } /* Standard beginEventProcessing() method for SDL drivers. */ void AG_SDL2_BeginEventProcessing(void *obj) { /* Nothing to do */ } /* Standard pendingEvents() method for SDL drivers. */ int AG_SDL2_PendingEvents(void *obj) { return (SDL_PollEvent(NULL) != 0); } /* Convert an SDL2 SDL_Scancode to an AG_KeySym */ static __inline__ AG_KeySym AG_SDL2_KeySymFromScancode(const SDL_Scancode scancode) { switch (scancode) { case SDL_SCANCODE_CAPSLOCK: return AG_KEY_CAPSLOCK; case SDL_SCANCODE_F1: return AG_KEY_F1; case SDL_SCANCODE_F2: return AG_KEY_F2; case SDL_SCANCODE_F3: return AG_KEY_F3; case SDL_SCANCODE_F4: return AG_KEY_F4; case SDL_SCANCODE_F5: return AG_KEY_F5; case SDL_SCANCODE_F6: return AG_KEY_F6; case SDL_SCANCODE_F7: return AG_KEY_F7; case SDL_SCANCODE_F8: return AG_KEY_F8; case SDL_SCANCODE_F9: return AG_KEY_F9; case SDL_SCANCODE_F10: return AG_KEY_F10; case SDL_SCANCODE_F11: return AG_KEY_F11; case SDL_SCANCODE_F12: return AG_KEY_F12; case SDL_SCANCODE_PRINTSCREEN: return AG_KEY_PRINT_SCREEN; case SDL_SCANCODE_SCROLLLOCK: return AG_KEY_SCROLLOCK; case SDL_SCANCODE_PAUSE: return AG_KEY_PAUSE; case SDL_SCANCODE_INSERT: return AG_KEY_INSERT; case SDL_SCANCODE_HOME: return AG_KEY_HOME; case SDL_SCANCODE_PAGEUP: return AG_KEY_PAGEUP; case SDL_SCANCODE_DELETE: return AG_KEY_DELETE; case SDL_SCANCODE_END: return AG_KEY_END; case SDL_SCANCODE_PAGEDOWN: return AG_KEY_PAGEDOWN; case SDL_SCANCODE_RIGHT: return AG_KEY_RIGHT; case SDL_SCANCODE_LEFT: return AG_KEY_LEFT; case SDL_SCANCODE_DOWN: return AG_KEY_DOWN; case SDL_SCANCODE_UP: return AG_KEY_UP; case SDL_SCANCODE_NUMLOCKCLEAR: return AG_KEY_NUMLOCK; case SDL_SCANCODE_KP_DIVIDE: return AG_KEY_KP_DIVIDE; case SDL_SCANCODE_KP_MULTIPLY: return AG_KEY_KP_MULTIPLY; case SDL_SCANCODE_KP_MINUS: return AG_KEY_KP_MINUS; case SDL_SCANCODE_KP_PLUS: return AG_KEY_KP_PLUS; case SDL_SCANCODE_KP_ENTER: return AG_KEY_KP_ENTER; case SDL_SCANCODE_KP_1: return AG_KEY_KP1; case SDL_SCANCODE_KP_2: return AG_KEY_KP2; case SDL_SCANCODE_KP_3: return AG_KEY_KP3; case SDL_SCANCODE_KP_4: return AG_KEY_KP4; case SDL_SCANCODE_KP_5: return AG_KEY_KP5; case SDL_SCANCODE_KP_6: return AG_KEY_KP6; case SDL_SCANCODE_KP_7: return AG_KEY_KP7; case SDL_SCANCODE_KP_8: return AG_KEY_KP8; case SDL_SCANCODE_KP_9: return AG_KEY_KP9; case SDL_SCANCODE_KP_0: return AG_KEY_KP0; case SDL_SCANCODE_KP_PERIOD: return AG_KEY_KP_PERIOD; case SDL_SCANCODE_NONUSBACKSLASH: return AG_KEY_NON_US_BACKSLASH; case SDL_SCANCODE_APPLICATION: return AG_KEY_APPLICATION; case SDL_SCANCODE_POWER: return AG_KEY_POWER; case SDL_SCANCODE_KP_EQUALS: return AG_KEY_KP_EQUALS; case SDL_SCANCODE_F13: return AG_KEY_F13; case SDL_SCANCODE_F14: return AG_KEY_F14; case SDL_SCANCODE_F15: return AG_KEY_F15; case SDL_SCANCODE_F16: return AG_KEY_F16; case SDL_SCANCODE_F17: return AG_KEY_F17; case SDL_SCANCODE_F18: return AG_KEY_F18; case SDL_SCANCODE_F19: return AG_KEY_F19; case SDL_SCANCODE_F20: return AG_KEY_F20; case SDL_SCANCODE_F21: return AG_KEY_F21; case SDL_SCANCODE_F22: return AG_KEY_F22; case SDL_SCANCODE_F23: return AG_KEY_F23; case SDL_SCANCODE_F24: return AG_KEY_F24; case SDL_SCANCODE_EXECUTE: return AG_KEY_EXECUTE; case SDL_SCANCODE_HELP: return AG_KEY_HELP; case SDL_SCANCODE_MENU: return AG_KEY_MENU; case SDL_SCANCODE_SELECT: return AG_KEY_SELECT; case SDL_SCANCODE_STOP: return AG_KEY_STOP; case SDL_SCANCODE_AGAIN: return AG_KEY_AGAIN; case SDL_SCANCODE_UNDO: return AG_KEY_UNDO; case SDL_SCANCODE_CUT: return AG_KEY_CUT; case SDL_SCANCODE_PASTE: return AG_KEY_PASTE; case SDL_SCANCODE_FIND: return AG_KEY_FIND; case SDL_SCANCODE_MUTE: return AG_KEY_VOLUME_MUTE; case SDL_SCANCODE_VOLUMEUP: return AG_KEY_VOLUME_UP; case SDL_SCANCODE_VOLUMEDOWN: return AG_KEY_VOLUME_DOWN; case SDL_SCANCODE_KP_COMMA: return AG_KEY_KP_COMMA; case SDL_SCANCODE_KP_EQUALSAS400: return AG_KEY_KP_EQUALS_AS_400; case SDL_SCANCODE_INTERNATIONAL1: return AG_KEY_INTERNATIONAL_1; case SDL_SCANCODE_INTERNATIONAL2: return AG_KEY_INTERNATIONAL_2; case SDL_SCANCODE_INTERNATIONAL3: return AG_KEY_INTERNATIONAL_3; case SDL_SCANCODE_INTERNATIONAL4: return AG_KEY_INTERNATIONAL_4; case SDL_SCANCODE_INTERNATIONAL5: return AG_KEY_INTERNATIONAL_5; case SDL_SCANCODE_INTERNATIONAL6: return AG_KEY_INTERNATIONAL_6; case SDL_SCANCODE_INTERNATIONAL7: return AG_KEY_INTERNATIONAL_7; case SDL_SCANCODE_INTERNATIONAL8: return AG_KEY_INTERNATIONAL_8; case SDL_SCANCODE_INTERNATIONAL9: return AG_KEY_INTERNATIONAL_9; case SDL_SCANCODE_LANG1: return AG_KEY_LANGUAGE_1; case SDL_SCANCODE_LANG2: return AG_KEY_LANGUAGE_2; case SDL_SCANCODE_LANG3: return AG_KEY_LANGUAGE_3; case SDL_SCANCODE_LANG4: return AG_KEY_LANGUAGE_4; case SDL_SCANCODE_LANG5: return AG_KEY_LANGUAGE_5; case SDL_SCANCODE_LANG6: return AG_KEY_LANGUAGE_6; case SDL_SCANCODE_LANG7: return AG_KEY_LANGUAGE_7; case SDL_SCANCODE_LANG8: return AG_KEY_LANGUAGE_8; case SDL_SCANCODE_LANG9: return AG_KEY_LANGUAGE_9; case SDL_SCANCODE_ALTERASE: return AG_KEY_ALT_ERASE; case SDL_SCANCODE_SYSREQ: return AG_KEY_SYSREQ; case SDL_SCANCODE_CANCEL: return AG_KEY_CANCEL; case SDL_SCANCODE_CLEAR: return AG_KEY_CLEAR; case SDL_SCANCODE_PRIOR: return AG_KEY_PRIOR; case SDL_SCANCODE_RETURN2: return AG_KEY_RETURN2; case SDL_SCANCODE_SEPARATOR: return AG_KEY_SEPARATOR; case SDL_SCANCODE_OUT: return AG_KEY_OUT; case SDL_SCANCODE_OPER: return AG_KEY_OPER; case SDL_SCANCODE_CLEARAGAIN: return AG_KEY_CLEAR_AGAIN; case SDL_SCANCODE_CRSEL: return AG_KEY_CRSEL; case SDL_SCANCODE_EXSEL: return AG_KEY_EXSEL; case SDL_SCANCODE_KP_00: return AG_KEY_KP_00; case SDL_SCANCODE_KP_000: return AG_KEY_KP_000; case SDL_SCANCODE_THOUSANDSSEPARATOR: return AG_KEY_THOUSANDS_SEPARATOR; case SDL_SCANCODE_DECIMALSEPARATOR: return AG_KEY_DECIMALS_SEPARATOR; case SDL_SCANCODE_CURRENCYUNIT: return AG_KEY_CURRENCY_UNIT; case SDL_SCANCODE_CURRENCYSUBUNIT: return AG_KEY_CURRENCY_SUBUNIT; case SDL_SCANCODE_KP_LEFTPAREN: return AG_KEY_KP_LEFT_PAREN; case SDL_SCANCODE_KP_RIGHTPAREN: return AG_KEY_KP_RIGHT_PAREN; case SDL_SCANCODE_KP_LEFTBRACE: return AG_KEY_KP_LEFT_BRACE; case SDL_SCANCODE_KP_RIGHTBRACE: return AG_KEY_KP_RIGHT_BRACE; case SDL_SCANCODE_KP_TAB: return AG_KEY_KP_TAB; case SDL_SCANCODE_KP_BACKSPACE: return AG_KEY_KP_BACKSPACE; case SDL_SCANCODE_KP_A: return AG_KEY_KP_A; case SDL_SCANCODE_KP_B: return AG_KEY_KP_B; case SDL_SCANCODE_KP_C: return AG_KEY_KP_C; case SDL_SCANCODE_KP_D: return AG_KEY_KP_D; case SDL_SCANCODE_KP_E: return AG_KEY_KP_E; case SDL_SCANCODE_KP_F: return AG_KEY_KP_F; case SDL_SCANCODE_KP_XOR: return AG_KEY_KP_XOR; case SDL_SCANCODE_KP_POWER: return AG_KEY_KP_POWER; case SDL_SCANCODE_KP_PERCENT: return AG_KEY_KP_PERCENT; case SDL_SCANCODE_KP_LESS: return AG_KEY_KP_LESS; case SDL_SCANCODE_KP_GREATER: return AG_KEY_KP_GREATER; case SDL_SCANCODE_KP_AMPERSAND: return AG_KEY_KP_AMPERSAND; case SDL_SCANCODE_KP_DBLAMPERSAND: return AG_KEY_KP_DBL_AMPERSAND; case SDL_SCANCODE_KP_VERTICALBAR: return AG_KEY_KP_VERTICAL_BAR; case SDL_SCANCODE_KP_DBLVERTICALBAR: return AG_KEY_KP_DBL_VERTICAL_BAR; case SDL_SCANCODE_KP_COLON: return AG_KEY_KP_COLON; case SDL_SCANCODE_KP_HASH: return AG_KEY_KP_HASH; case SDL_SCANCODE_KP_SPACE: return AG_KEY_KP_SPACE; case SDL_SCANCODE_KP_AT: return AG_KEY_KP_AT; case SDL_SCANCODE_KP_EXCLAM: return AG_KEY_KP_EXCLAM; case SDL_SCANCODE_KP_MEMSTORE: return AG_KEY_KP_MEM_STORE; case SDL_SCANCODE_KP_MEMRECALL: return AG_KEY_KP_MEM_RECALL; case SDL_SCANCODE_KP_MEMCLEAR: return AG_KEY_KP_MEM_CLEAR; case SDL_SCANCODE_KP_MEMADD: return AG_KEY_KP_MEM_ADD; case SDL_SCANCODE_KP_MEMSUBTRACT: return AG_KEY_KP_MEM_SUBTRACT; case SDL_SCANCODE_KP_MEMMULTIPLY: return AG_KEY_KP_MEM_MULTIPLY; case SDL_SCANCODE_KP_MEMDIVIDE: return AG_KEY_KP_MEM_DIVIDE; case SDL_SCANCODE_KP_PLUSMINUS: return AG_KEY_KP_PLUS_MINUS; case SDL_SCANCODE_KP_CLEAR: return AG_KEY_KP_CLEAR; case SDL_SCANCODE_KP_CLEARENTRY: return AG_KEY_KP_CLEAR_ENTRY; case SDL_SCANCODE_KP_BINARY: return AG_KEY_KP_BINARY; case SDL_SCANCODE_KP_OCTAL: return AG_KEY_KP_OCTAL; case SDL_SCANCODE_KP_DECIMAL: return AG_KEY_KP_DECIMAL; case SDL_SCANCODE_KP_HEXADECIMAL: return AG_KEY_KP_HEXADECIMAL; case SDL_SCANCODE_LCTRL: return AG_KEY_LCTRL; case SDL_SCANCODE_LSHIFT: return AG_KEY_LSHIFT; case SDL_SCANCODE_LALT: return AG_KEY_LALT; case SDL_SCANCODE_LGUI: return AG_KEY_LGUI; case SDL_SCANCODE_RCTRL: return AG_KEY_RCTRL; case SDL_SCANCODE_RSHIFT: return AG_KEY_RSHIFT; case SDL_SCANCODE_RALT: return AG_KEY_RALT; case SDL_SCANCODE_RGUI: return AG_KEY_RGUI; case SDL_SCANCODE_MODE: return AG_KEY_MODE; default: break; } return (AG_KEY_LAST); } /* * Return the AG_Window corresponding to the given SDL Window ID. * The agDrivers VFS must be locked. */ AG_Window * AG_SDL_GetWindowFromID(AG_Driver *drv, Uint32 windowID) { AG_DriverMw *dmw; if (AGDRIVER_SINGLE(drv)) { return (NULL); /* Not needed (single-window driver) */ } AGOBJECT_FOREACH_CHILD(dmw, &agDrivers, ag_driver_mw) { AG_Window *win; if (!AGDRIVER_MULTIPLE(dmw) || dmw->windowID != windowID) { continue; } win = AGDRIVERMW(dmw)->win; if (!(AGDRIVERMW(dmw)->flags & AG_DRIVER_MW_OPEN) || (win->flags & AG_WINDOW_DETACHING) || WIDGET(win)->drv == NULL || ((void *)win == (void *)&agDrivers)) { /* XXX */ return (NULL); } return (win); } return (NULL); } /* * Translate an SDL_Event to an AG_DriverEvent. * * Compatible with both single-window and multi-window drivers. * For single-window drivers, the win pointer is set to NULL. */ void AG_SDL2_TranslateEvent(void *obj, const SDL_Event *ev, AG_DriverEvent *dev) { AG_Driver *drv = obj; AG_Mouse *ms; const int isMW = AGDRIVER_MULTIPLE(drv); if (!isMW) dev->win = NULL; switch (ev->type) { case SDL_MOUSEMOTION: if (isMW) { if ((dev->win = AG_SDL_GetWindowFromID(drv, ev->motion.windowID)) == NULL) { goto fail; } drv = WIDGET(dev->win)->drv; } ms = drv->mouse; ms->xRel = ev->motion.x - ms->x; /* Update state */ ms->yRel = ev->motion.y - ms->y; ms->x = ev->motion.x; ms->y = ev->motion.y; dev->type = AG_DRIVER_MOUSE_MOTION; dev->motion.x = ev->motion.x; dev->motion.y = ev->motion.y; break; case SDL_MOUSEBUTTONUP: if (isMW) { dev->win = AG_SDL_GetWindowFromID(drv, ev->button.windowID); if (dev->win == NULL) { goto fail; } #ifdef DEBUG_MOUSE Debug(drv, "MOUSEBUTTONUP " "(%s, Which=" AGSI_YEL "%d" AGSI_RST " Pos=" AGSI_YEL "%d,%d" AGSI_RST ")\n", OBJECT(dev->win)->name, ev->button.which, ev->button.x, ev->button.y); #endif drv = WIDGET(dev->win)->drv; } else { #ifdef DEBUG_MOUSE Debug(drv, "MOUSEBUTTONUP " "(Which=" AGSI_YEL "%d" AGSI_RST " Pos=" AGSI_YEL "%d,%d" AGSI_RST ")\n", ev->button.which, ev->button.x, ev->button.y); #endif } drv->mouse->btnState &= ~(AG_MOUSE_BUTTON(ev->button.button)); dev->type = AG_DRIVER_MOUSE_BUTTON_UP; dev->button.which = (AG_MouseButton)ev->button.button; dev->button.x = ev->button.x; dev->button.y = ev->button.y; break; case SDL_MOUSEBUTTONDOWN: if (isMW) { dev->win = AG_SDL_GetWindowFromID(drv, ev->button.windowID); if (dev->win == NULL) { goto fail; } #ifdef DEBUG_MOUSE Debug(drv, "MOUSEBUTTONDOWN " "(%s, Which=" AGSI_YEL "%d" AGSI_RST " Pos=" AGSI_YEL "%d,%d" AGSI_RST ")\n", OBJECT(dev->win)->name, ev->button.which, ev->button.x, ev->button.y); #endif drv = WIDGET(dev->win)->drv; } else { #ifdef DEBUG_MOUSE Debug(drv, "MOUSEBUTTONDOWN " "(Which=" AGSI_YEL "%d" AGSI_RST " Pos=" AGSI_YEL "%d,%d" AGSI_RST ")\n", ev->button.which, ev->button.x, ev->button.y); #endif } drv->mouse->btnState |= AG_MOUSE_BUTTON(ev->button.button); dev->type = AG_DRIVER_MOUSE_BUTTON_DOWN; dev->button.which = (AG_MouseButton)ev->button.button; dev->button.x = ev->button.x; dev->button.y = ev->button.y; break; case SDL_MOUSEWHEEL: #ifdef DEBUG_MOUSE Debug(drv, "MOUSEWHEEL (Y=" AGSI_YEL "%d" AGSI_RST ")\n", ev->wheel.y); #endif dev->type = AG_DRIVER_MOUSE_BUTTON_DOWN; if (isMW) { dev->win = AG_SDL_GetWindowFromID(drv, ev->button.windowID); if (dev->win == NULL) { goto fail; } drv = WIDGET(dev->win)->drv; } if (ev->wheel.y == 1) { drv->mouse->btnState |= AG_MOUSE_BUTTON(AG_MOUSE_WHEELUP); dev->button.which = AG_MOUSE_WHEELUP; } else { drv->mouse->btnState |= AG_MOUSE_BUTTON(AG_MOUSE_WHEELDOWN); dev->button.which = AG_MOUSE_WHEELDOWN; } dev->button.x = drv->mouse->x; dev->button.y = drv->mouse->y; break; case SDL_KEYDOWN: if (ev->key.repeat) { /* Agar implements its own keyrepeat */ goto fail; } #ifdef DEBUG_KEYBOARD Debug(drv, "KEYDOWN " "(Sym=" AGSI_YEL "0x%x" AGSI_RST " Scan=" AGSI_YEL "0x%x" AGSI_RST ")\n", (Uint)ev->key.keysym.sym, (Uint)ev->key.keysym.scancode); #endif if ((ev->key.keysym.sym & SDLK_SCANCODE_MASK) != 0) { dev->key.ks = AG_SDL2_KeySymFromScancode( ev->key.keysym.scancode); } else { dev->key.ks = (AG_KeySym)ev->key.keysym.sym; } if (isMW) { dev->win = AG_SDL_GetWindowFromID(drv, ev->key.windowID); if (dev->win == NULL) { goto fail; } drv = WIDGET(dev->win)->drv; } AG_KeyboardUpdate(drv->kbd, AG_KEY_PRESSED, dev->key.ks); dev->type = AG_DRIVER_KEY_DOWN; dev->key.ucs = AG_SDL_KeySymToUCS4(ev->key.keysym.sym, drv->kbd->modState); break; case SDL_KEYUP: #ifdef DEBUG_KEYBOARD Debug(drv, "KEYUP " "(Sym=" AGSI_YEL "0x%x" AGSI_RST " Scan=" AGSI_YEL "0x%x" AGSI_RST ")\n", (Uint)ev->key.keysym.sym, (Uint)ev->key.keysym.scancode); #endif if (isMW) { dev->win = AG_SDL_GetWindowFromID(drv, ev->key.windowID); if (dev->win == NULL) { goto fail; } drv = WIDGET(dev->win)->drv; } if ((ev->key.keysym.sym & SDLK_SCANCODE_MASK) != 0) { dev->key.ks = AG_SDL2_KeySymFromScancode( ev->key.keysym.scancode); } else { dev->key.ks = (AG_KeySym)ev->key.keysym.sym; } AG_KeyboardUpdate(drv->kbd, AG_KEY_RELEASED, dev->key.ks); dev->type = AG_DRIVER_KEY_UP; dev->key.ucs = AG_SDL_KeySymToUCS4(ev->key.keysym.sym, drv->kbd->modState); break; case SDL_WINDOWEVENT: if (isMW) { dev->win = AG_SDL_GetWindowFromID(drv, ev->window.windowID); if (dev->win == NULL) goto fail; } switch (ev->window.event) { case SDL_WINDOWEVENT_EXPOSED: dev->type = AG_DRIVER_EXPOSE; break; case SDL_WINDOWEVENT_MOVED: dev->type = AG_DRIVER_MOVED; dev->moved.x = (int)ev->window.data1; dev->moved.y = (int)ev->window.data2; break; case SDL_WINDOWEVENT_RESIZED: dev->type = AG_DRIVER_VIDEORESIZE; dev->videoresize.x = 0; dev->videoresize.y = 0; dev->videoresize.w = (int)ev->window.data1; dev->videoresize.h = (int)ev->window.data2; break; case SDL_WINDOWEVENT_SHOWN: dev->type = AG_DRIVER_SHOWN; break; case SDL_WINDOWEVENT_HIDDEN: dev->type = AG_DRIVER_HIDDEN; break; case SDL_WINDOWEVENT_ENTER: dev->type = AG_DRIVER_MOUSE_ENTER; break; case SDL_WINDOWEVENT_FOCUS_GAINED: dev->type = AG_DRIVER_FOCUS_IN; break; case SDL_WINDOWEVENT_FOCUS_LOST: dev->type = AG_DRIVER_FOCUS_OUT; break; case SDL_WINDOWEVENT_LEAVE: dev->type = AG_DRIVER_MOUSE_LEAVE; break; case SDL_WINDOWEVENT_MINIMIZED: dev->type = AG_DRIVER_MINIMIZED; break; case SDL_WINDOWEVENT_MAXIMIZED: dev->type = AG_DRIVER_MAXIMIZED; break; case SDL_WINDOWEVENT_RESTORED: dev->type = AG_DRIVER_RESTORED; break; case SDL_WINDOWEVENT_CLOSE: dev->type = AG_DRIVER_CLOSE; break; default: #ifdef DEBUG_UNKNOWN_EVENTS if (isMW) { Debug(drv, "WINDOW EVENT (%s, " AGSI_YEL "0x%x, 0x%x,0x%x" AGSI_RST ")\n", OBJECT(dev->win)->name, (Uint)ev->window.event, (Uint)ev->window.data1, (Uint)ev->window.data2); } else { Debug(drv, "WINDOW EVENT (" AGSI_YEL "0x%x, 0x%x,0x%x" AGSI_RST ")\n", (Uint)ev->window.event, (Uint)ev->window.data1, (Uint)ev->window.data2); } #endif goto fail; } break; case SDL_CONTROLLERAXISMOTION: dev->type = AG_DRIVER_CTRL_AXIS_MOTION; dev->win = NULL; dev->ctrlAxis.instanceID = (int)ev->caxis.which; dev->ctrlAxis.axis = (int)ev->caxis.axis; dev->ctrlAxis.value = (int)ev->caxis.value; break; case SDL_CONTROLLERBUTTONDOWN: dev->type = AG_DRIVER_CTRL_BUTTON_DOWN; dev->win = NULL; dev->ctrlButton.instanceID = (int)ev->cbutton.which; dev->ctrlButton.which = (AG_ControllerButton)ev->cbutton.button; dev->ctrlButton.state = AG_CTRL_PRESSED; break; case SDL_CONTROLLERBUTTONUP: dev->type = AG_DRIVER_CTRL_BUTTON_UP; dev->win = NULL; dev->ctrlButton.instanceID = (int)ev->cbutton.which; dev->ctrlButton.which = (AG_ControllerButton)ev->cbutton.button; dev->ctrlButton.state = AG_CTRL_RELEASED; break; case SDL_CONTROLLERTOUCHPADDOWN: dev->type = AG_DRIVER_CTRL_TOUCHPAD_DOWN; dev->win = NULL; dev->ctrlTouchpad.touchpad = (int)ev->ctouchpad.touchpad; dev->ctrlTouchpad.finger = (int)ev->ctouchpad.finger; dev->ctrlTouchpad.x = ev->ctouchpad.x; dev->ctrlTouchpad.y = ev->ctouchpad.y; dev->ctrlTouchpad.pressure = ev->ctouchpad.pressure; break; case SDL_CONTROLLERTOUCHPADUP: dev->type = AG_DRIVER_CTRL_TOUCHPAD_UP; dev->win = NULL; dev->ctrlTouchpad.touchpad = (int)ev->ctouchpad.touchpad; dev->ctrlTouchpad.finger = (int)ev->ctouchpad.finger; dev->ctrlTouchpad.x = ev->ctouchpad.x; dev->ctrlTouchpad.y = ev->ctouchpad.y; dev->ctrlTouchpad.pressure = ev->ctouchpad.pressure; break; case SDL_CONTROLLERTOUCHPADMOTION: dev->type = AG_DRIVER_CTRL_TOUCHPAD_MOTION; dev->win = NULL; dev->ctrlTouchpad.instanceID = (int)ev->ctouchpad.which; dev->ctrlTouchpad.finger = (int)ev->ctouchpad.finger; dev->ctrlTouchpad.x = ev->ctouchpad.x; dev->ctrlTouchpad.y = ev->ctouchpad.y; dev->ctrlTouchpad.pressure = ev->ctouchpad.pressure; break; case SDL_CONTROLLERSENSORUPDATE: dev->type = AG_DRIVER_CTRL_SENSOR; dev->win = NULL; dev->ctrlSensor.instanceID = (int)ev->csensor.which; dev->ctrlSensor.sensor = (int)ev->csensor.sensor; memcpy(&dev->ctrlSensor.data, &ev->csensor.data, sizeof(float)*3); break; case SDL_JOYAXISMOTION: dev->type = AG_DRIVER_JOY_AXIS_MOTION; dev->win = NULL; dev->joyAxis.instanceID = (int)ev->jaxis.which; dev->joyAxis.axis = (int)ev->jaxis.axis; dev->joyAxis.value = (int)ev->jaxis.value; break; case SDL_JOYBALLMOTION: dev->type = AG_DRIVER_JOY_BALL_MOTION; dev->win = NULL; dev->joyBall.instanceID = (int)ev->jball.which; dev->joyBall.ball = (int)ev->jball.ball; dev->joyBall.dx = (int)ev->jball.xrel; dev->joyBall.dy = (int)ev->jball.yrel; break; case SDL_JOYHATMOTION: dev->type = AG_DRIVER_JOY_HAT_MOTION; dev->win = NULL; dev->joyHat.instanceID = (int)ev->jhat.which; dev->joyHat.hat = (int)ev->jhat.hat; dev->joyHat.pos = (AG_JoyHatPosition)ev->jhat.value; break; case SDL_JOYBUTTONDOWN: dev->type = AG_DRIVER_JOY_BUTTON_DOWN; dev->win = NULL; dev->joyButton.instanceID = (int)ev->jbutton.which; dev->joyButton.button = (int)ev->jbutton.button; break; case SDL_JOYBUTTONUP: dev->type = AG_DRIVER_JOY_BUTTON_UP; dev->win = NULL; dev->joyButton.instanceID = (int)ev->jbutton.which; dev->joyButton.button = (int)ev->jbutton.button; break; case SDL_CONTROLLERDEVICEADDED: dev->type = AG_DRIVER_CTRL_DEVICE_ADDED; dev->win = NULL; dev->ctrlDevice.instanceID = (int)ev->cdevice.which; break; case SDL_CONTROLLERDEVICEREMOVED: dev->type = AG_DRIVER_CTRL_DEVICE_REMOVED; dev->win = NULL; dev->ctrlDevice.instanceID = (int)ev->cdevice.which; break; case SDL_CONTROLLERDEVICEREMAPPED: dev->type = AG_DRIVER_CTRL_DEVICE_REMAPPED; dev->win = NULL; dev->ctrlDevice.instanceID = (int)ev->cdevice.which; break; case SDL_JOYDEVICEADDED: dev->type = AG_DRIVER_JOY_DEVICE_ADDED; dev->win = NULL; dev->joyDevice.which = (int)ev->jdevice.which; break; case SDL_JOYDEVICEREMOVED: dev->type = AG_DRIVER_JOY_DEVICE_REMOVED; dev->win = NULL; dev->joyDevice.which = (int)ev->jdevice.which; break; case SDL_QUIT: case SDL_USEREVENT: dev->type = AG_DRIVER_CLOSE; break; default: dev->type = AG_DRIVER_UNKNOWN; break; } return; fail: dev->type = AG_DRIVER_UNKNOWN; dev->win = NULL; } /* * Standard getNextEvent() method. * For both single-window and multi-window drivers. */ int AG_SDL2_GetNextEvent(void *obj, AG_DriverEvent *dev) { AG_Driver *drv = obj; SDL_Event ev; if (SDL_PollEvent(&ev) == 0) { return (0); } AG_SDL2_TranslateEvent(drv, &ev, dev); return (1); } /* Test if the given coordinates overlap a window resize control. */ static __inline__ int GenericMouseOverCtrl(AG_Window *_Nonnull win, int x, int y) { int xRel; if ((y - WIDGET(win)->y) < (HEIGHT(win) - win->wBorderBot)) { return (AG_WINOP_NONE); } xRel = x - WIDGET(win)->x; if (xRel < win->wResizeCtrl) { return (AG_WINOP_LRESIZE); } else if (xRel > (WIDTH(win) - win->wResizeCtrl)) { return (AG_WINOP_RRESIZE); } else if ((win->flags & AG_WINDOW_NOVRESIZE) == 0) { return (AG_WINOP_HRESIZE); } return (AG_WINOP_NONE); } static __inline__ int PushTextState(AG_Window *win) { const int useText = (win->flags & AG_WINDOW_USE_TEXT); if (useText) { AG_PushTextState(); AG_TextFont(WIDGET(win)->font); AG_TextColor(&WIDGET(win)->pal.c[WIDGET(win)->state] [AG_TEXT_COLOR]); } return (useText); } static __inline__ void PopTextState(int useText) { if (useText) { AG_PopTextState(); } } /* * Standard processEvent() for single-window SDL2 drivers (sdl2fb, sdl2gl). */ int AG_SDL2_ProcessEvent_SW(void *obj, AG_DriverEvent *dev) { AG_Driver *drv = (AG_Driver *)obj; AG_DriverSw *dsw = (AG_DriverSw *)obj; AG_Window *win, *winTop; AG_KeySym ks; AG_Char ucs; int btn, x,y, dx,dy, useText; AG_OBJECT_ISA(drv, "AG_Driver:AG_DriverSw:*"); AG_LockVFS(&agDrivers); switch (dev->type) { case AG_DRIVER_MOUSE_MOTION: x = dev->motion.x; y = dev->motion.y; dx = drv->mouse->xRel; dy = drv->mouse->yRel; winTop = NULL; AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); useText = PushTextState(win); if (dsw->winop != AG_WINOP_NONE) { if (dsw->winSelected != win) { PopTextState(useText); AG_ObjectUnlock(win); continue; /* Next window */ } AG_WM_MouseMotion(dsw, win, dx,dy); } AG_ProcessMouseMotion(win, x,y, dx,dy); if (winTop == NULL && AG_WidgetArea(win, x,y)) { winTop = win; AG_MouseCursorUpdate(win, x,y); } PopTextState(useText); AG_ObjectUnlock(win); } if (winTop == NULL) { AGDRIVER_CLASS(drv)->unsetCursor(drv); } break; case AG_DRIVER_MOUSE_BUTTON_DOWN: btn = dev->button.which; x = dev->button.x; y = dev->button.y; AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); useText = PushTextState(win); if (!AG_WidgetArea(win, x,y)) { AG_ProcessMouseButtonDown(win, x,y, btn); PopTextState(useText); AG_ObjectUnlock(win); continue; /* Next window */ } if (win->wBorderBot > 0 && /* Resize op? */ !(win->flags & AG_WINDOW_NORESIZE)) { dsw->winop = GenericMouseOverCtrl(win, x,y); if (dsw->winop != AG_WINOP_NONE) { win->dirty = 1; dsw->winSelected = win; PopTextState(useText); AG_ObjectUnlock(win); break; /* Processed */ } } AG_ProcessMouseButtonDown(win, x,y, btn); PopTextState(useText); AG_ObjectUnlock(win); break; } if (win == NULL && /* Not processed? */ (dsw->flags & AG_DRIVER_SW_BGPOPUP) && (btn == AG_MOUSE_MIDDLE || btn == AG_MOUSE_RIGHT)) { AG_WM_BackgroundPopupMenu(dsw); break; } break; case AG_DRIVER_MOUSE_BUTTON_UP: btn = dev->button.which; x = dev->button.x; y = dev->button.y; dsw->winop = AG_WINOP_NONE; dsw->winSelected = NULL; AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); useText = PushTextState(win); AG_ProcessMouseButtonUp(win, x,y, btn); if (agWindowToFocus || !TAILQ_EMPTY(&agWindowDetachQ)) { PopTextState(useText); AG_ObjectUnlock(win); break; } PopTextState(useText); AG_ObjectUnlock(win); } break; case AG_DRIVER_KEY_DOWN: ks = dev->key.ks; ucs = dev->key.ucs; if (AG_ExecGlobalKeys(ks, drv->kbd->modState) != 0) { break; } AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); useText = PushTextState(win); if (AG_ProcessKey(drv->kbd, win, AG_KEY_PRESSED, ks, ucs) == 1) { PopTextState(useText); AG_ObjectUnlock(win); break; /* Processed */ } PopTextState(useText); AG_ObjectUnlock(win); } break; case AG_DRIVER_KEY_UP: ks = dev->key.ks; ucs = dev->key.ucs; AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); useText = PushTextState(win); if (dsw->winLastKeydown != NULL && dsw->winLastKeydown != win) { /* * Key was initially pressed while another * window was holding focus, ignore. */ dsw->winLastKeydown = NULL; PopTextState(useText); AG_ObjectUnlock(win); break; } if (AG_ProcessKey(drv->kbd, win, AG_KEY_RELEASED, ks, ucs) == 1) { PopTextState(useText); AG_ObjectUnlock(win); break; /* Processed */ } PopTextState(useText); AG_ObjectUnlock(win); } break; case AG_DRIVER_CTRL_AXIS_MOTION: case AG_DRIVER_CTRL_BUTTON_DOWN: case AG_DRIVER_CTRL_BUTTON_UP: case AG_DRIVER_CTRL_TOUCHPAD_DOWN: case AG_DRIVER_CTRL_TOUCHPAD_UP: case AG_DRIVER_CTRL_TOUCHPAD_MOTION: case AG_DRIVER_CTRL_SENSOR: AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); AG_ProcessController(win, dev); AG_ObjectUnlock(win); } break; case AG_DRIVER_JOY_AXIS_MOTION: case AG_DRIVER_JOY_BALL_MOTION: case AG_DRIVER_JOY_HAT_MOTION: case AG_DRIVER_JOY_BUTTON_DOWN: case AG_DRIVER_JOY_BUTTON_UP: AG_FOREACH_WINDOW_REVERSE(win, dsw) { if (!win->visible) { continue; } AG_ObjectLock(win); AG_ProcessJoystick(win, dev); AG_ObjectUnlock(win); } break; case AG_DRIVER_EXPOSE: if (!(dsw->flags & AG_DRIVER_SW_OVERLAY)) { AG_ClearBackground(); } break; case AG_DRIVER_VIDEORESIZE: if (AG_ResizeDisplay(dev->videoresize.w, dev->videoresize.h) == -1) { Verbose("ResizeDisplay: %s\n", AG_GetError()); } break; case AG_DRIVER_CTRL_DEVICE_ADDED: AG_SDL2_ControllerAdded(drv, dev->ctrlDevice.instanceID); break; case AG_DRIVER_CTRL_DEVICE_REMOVED: AG_SDL2_JoystickRemoved(drv, dev->ctrlDevice.instanceID); break; case AG_DRIVER_JOY_DEVICE_ADDED: AG_SDL2_JoystickAdded(drv, dev->joyDevice.which); break; case AG_DRIVER_JOY_DEVICE_REMOVED: AG_SDL2_JoystickRemoved(drv, dev->joyDevice.which); break; case AG_DRIVER_CLOSE: AG_UnlockVFS(&agDrivers); AG_Terminate(0); /* NOTREACHED */ return (1); default: break; } AG_UnlockVFS(&agDrivers); return (1); } /* A controller device (subclass of joystick) has been added. */ void AG_SDL2_ControllerAdded(void *drv, int deviceIdx) { SDL_GameController *sCtrl; SDL_Joystick *sJoy; AG_Joystick *joy; AG_Controller *ctrl; AG_LockVFS(&agInputDevices); SDL_LockJoysticks(); if ((sCtrl = SDL_GameControllerOpen(deviceIdx)) == NULL) { goto out; } if ((ctrl = AG_ControllerNew(drv, "SDL2 Controller")) == NULL) { goto out; } joy = AGJOYSTICK(ctrl); if (SDL_GameControllerHasLED(sCtrl)) joy->flags |= AG_JOYSTICK_HAS_LED; if (SDL_GameControllerHasRumble(sCtrl)) joy->flags |= AG_JOYSTICK_HAS_RUMBLE; if (SDL_GameControllerHasRumbleTriggers(sCtrl)) joy->flags |= AG_JOYSTICK_HAS_RUMBLE_TRIGGERS; joy->name = Strdup(SDL_GameControllerName(sCtrl)); joy->deviceIdx = deviceIdx; sJoy = SDL_GameControllerGetJoystick(sCtrl); joy->instanceID = SDL_JoystickInstanceID(sJoy); joy->playerIdx = SDL_GameControllerGetPlayerIndex(sCtrl); joy->nButtons = SDL_JoystickNumButtons(sJoy); joy->nAxes = SDL_JoystickNumAxes(sJoy); joy->nBalls = SDL_JoystickNumBalls(sJoy); joy->nHats = SDL_JoystickNumHats(sJoy); joy->vendorID = SDL_GameControllerGetVendor(sCtrl); joy->productID = SDL_GameControllerGetProduct(sCtrl); SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(sJoy), joy->guid, sizeof(joy->guid)); joy->instancePtr = (void *)sCtrl; /* XXX TODO translate */ ctrl->type = (AG_ControllerType)SDL_GameControllerGetType(sCtrl); Debug(drv, "Controller #%d added: " AGSI_ITALIC "%s" AGSI_RST "(type %d, vendor 0x%04x, product 0x%04x)\n", joy->instanceID, joy->name, ctrl->type, joy->vendorID, joy->productID); out: SDL_UnlockJoysticks(); AG_UnlockVFS(&agInputDevices); } /* A joystick device has been attached. */ void AG_SDL2_JoystickAdded(void *drv, int deviceIdx) { SDL_Joystick *sJoy; AG_Joystick *joy; AG_LockVFS(&agInputDevices); SDL_LockJoysticks(); if ((sJoy = SDL_JoystickOpen(deviceIdx)) == NULL) { goto out; } if ((joy = AG_JoystickNew(drv, "SDL2 Joystick")) == NULL) goto out; if (SDL_JoystickHasLED(sJoy)) joy->flags |= AG_JOYSTICK_HAS_LED; if (SDL_JoystickHasRumble(sJoy)) joy->flags |= AG_JOYSTICK_HAS_RUMBLE; if (SDL_JoystickHasRumbleTriggers(sJoy)) joy->flags |= AG_JOYSTICK_HAS_RUMBLE_TRIGGERS; joy->type = (AG_JoystickType)SDL_JoystickGetType(sJoy); joy->name = Strdup(SDL_JoystickName(sJoy)); joy->deviceIdx = deviceIdx; joy->instanceID = SDL_JoystickInstanceID(sJoy); joy->playerIdx = SDL_JoystickGetPlayerIndex(sJoy); joy->nButtons = SDL_JoystickNumButtons(sJoy); joy->nAxes = SDL_JoystickNumAxes(sJoy); joy->nBalls = SDL_JoystickNumBalls(sJoy); joy->nHats = SDL_JoystickNumHats(sJoy); joy->vendorID = SDL_JoystickGetVendor(sJoy); joy->productID = SDL_JoystickGetProduct(sJoy); SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(sJoy), joy->guid, sizeof(joy->guid)); joy->instancePtr = (void *)sJoy; Debug(drv, "Joystick #%d added: " AGSI_ITALIC "%s " AGSI_RST "(vendor 0x%04x, product 0x%04x)\n", joy->instanceID, joy->name, joy->vendorID, joy->productID); out: SDL_UnlockJoysticks(); AG_UnlockVFS(&agInputDevices); } /* A joystick or controller has been detached. */ void AG_SDL2_JoystickRemoved(void *drv, int instanceID) { AG_InputDevice *idev; AG_Joystick *joy; Debug(drv, "Joystick removed (instance #%d)\n", instanceID); AG_LockVFS(&agInputDevices); SDL_LockJoysticks(); AGOBJECT_FOREACH_CHILD(idev, &agInputDevices, ag_input_device) { if (!AG_JOYSTICK_ISA(idev)) { continue; } joy = AGJOYSTICK(idev); if (joy->instanceID != instanceID) { continue; } break; } if (idev != NULL) { Debug(drv, "Joystick removed (#%d, <" AGSI_CYAN "%s" AGSI_RST ">)\n", joy->instanceID, OBJECT_CLASS(joy)->name); SDL_JoystickClose(joy->instancePtr); AG_JoystickDestroy(drv, joy); } else { Debug(drv, "Joystick removed (#%d, unknown)\n", instanceID); } SDL_UnlockJoysticks(); AG_UnlockVFS(&agInputDevices); } /* * Standard processEvent() event processing method for SDL2 drivers. * For multi-window drivers (sdl2mw). */ int AG_SDL2_ProcessEvent_MW(void *obj, AG_DriverEvent *dev) { AG_Window *win; AG_Driver *drv; int useText; int rv = 1; if ((win = dev->win) == NULL || win->flags & AG_WINDOW_DETACHING) { switch (dev->type) { case AG_DRIVER_CTRL_AXIS_MOTION: case AG_DRIVER_CTRL_BUTTON_DOWN: case AG_DRIVER_CTRL_BUTTON_UP: case AG_DRIVER_CTRL_TOUCHPAD_DOWN: case AG_DRIVER_CTRL_TOUCHPAD_UP: case AG_DRIVER_CTRL_TOUCHPAD_MOTION: case AG_DRIVER_CTRL_SENSOR: AG_ProcessController(NULL, dev); break; case AG_DRIVER_JOY_AXIS_MOTION: case AG_DRIVER_JOY_BALL_MOTION: case AG_DRIVER_JOY_HAT_MOTION: case AG_DRIVER_JOY_BUTTON_DOWN: case AG_DRIVER_JOY_BUTTON_UP: AG_ProcessJoystick(NULL, dev); break; case AG_DRIVER_CTRL_DEVICE_ADDED: AG_SDL2_ControllerAdded(obj, dev->ctrlDevice.instanceID); break; case AG_DRIVER_CTRL_DEVICE_REMOVED: AG_SDL2_JoystickRemoved(obj, dev->ctrlDevice.instanceID); break; case AG_DRIVER_JOY_DEVICE_ADDED: AG_SDL2_JoystickAdded(obj, dev->joyDevice.which); break; case AG_DRIVER_JOY_DEVICE_REMOVED: AG_SDL2_JoystickRemoved(obj, dev->joyDevice.which); break; default: break; } return (0); } AG_LockVFS(&agDrivers); AG_OBJECT_ISA(win, "AG_Widget:AG_Window:*"); drv = WIDGET(win)->drv; AG_OBJECT_ISA(drv, "AG_Driver:AG_DriverMw:*"); if ((useText = (win->flags & AG_WINDOW_USE_TEXT))) { AG_PushTextState(); AG_TextFont(WIDGET(win)->font); AG_TextColor(&WIDGET(win)->pal.c[WIDGET(win)->state] [AG_TEXT_COLOR]); } switch (dev->type) { case AG_DRIVER_MOUSE_MOTION: AG_ProcessMouseMotion(win, dev->motion.x, dev->motion.y, drv->mouse->xRel, drv->mouse->yRel); AG_MouseCursorUpdate(win, dev->motion.x, dev->motion.y); break; case AG_DRIVER_MOUSE_BUTTON_DOWN: AG_ProcessMouseButtonDown(win, dev->button.x, dev->button.y, dev->button.which); break; case AG_DRIVER_MOUSE_BUTTON_UP: AG_ProcessMouseButtonUp(win, dev->button.x, dev->button.y, dev->button.which); break; case AG_DRIVER_KEY_UP: AG_ProcessKey(drv->kbd, win, AG_KEY_RELEASED, dev->key.ks, dev->key.ucs); break; case AG_DRIVER_KEY_DOWN: AG_ProcessKey(drv->kbd, win, AG_KEY_PRESSED, dev->key.ks, dev->key.ucs); break; case AG_DRIVER_MOUSE_ENTER: AG_PostEvent(win, "window-enter", NULL); break; case AG_DRIVER_MOUSE_LEAVE: AG_PostEvent(win, "window-leave", NULL); break; case AG_DRIVER_FOCUS_IN: if (agWindowFocused != win) { agWindowFocused = win; AG_PostEvent(win, "window-gainfocus", NULL); } break; case AG_DRIVER_FOCUS_OUT: if (agWindowFocused == win) { AG_PostEvent(win, "window-lostfocus", NULL); agWindowFocused = NULL; } break; case AG_DRIVER_CLOSE: AG_PostEvent(win, "window-close", NULL); break; default: rv = 0; break; } if (useText) { AG_PopTextState(); } AG_UnlockVFS(&agDrivers); return (rv); } /* * Standard event sink for AG_EventLoop() for single-window * SDL2 drivers (sdl2fb, sdl2gl). */ int AG_SDL2_EventSink_SW(AG_EventSink *es, AG_Event *event) { AG_DriverEvent dev; AG_Driver *drv = AG_DRIVER_PTR(1); if (SDL_PollEvent(NULL) != 0) { while (AG_SDL2_GetNextEvent(drv, &dev) == 1) { if (dev.type != AG_DRIVER_UNKNOWN) (void)AG_SDL2_ProcessEvent_SW(drv, &dev); } } else { AG_Delay(1); } return (0); } int AG_SDL2_EventEpilogue(AG_EventSink *es, AG_Event *event) { AG_WindowDrawQueued(); AG_WindowProcessQueued(); return (0); }