/* * Copyright (c) 2009-2024 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. */ /* * Driver for OpenGL graphics on Windows. This is a multiple display * driver; one context is created for each Agar window. */ #include #include #include #include #include #include #include #include #include /* Print out events in WndProc */ /* #define DEBUG_WGL */ static int nDrivers = 0; /* Drivers open */ static int wndClassCount = 1; /* Window class counter */ static AG_DriverEventQ wglEventQ; /* Event queue */ #ifdef AG_THREADS static _Nonnull_Mutex AG_Mutex wglClassLock; /* Lock on wndClassCount */ static _Nonnull_Mutex AG_Mutex wglEventLock; /* Lock on wglEventQ */ #endif static _Nullable HKL wglKbdLayout = NULL; /* Keyboard layout */ static AG_EventSink *_Nullable wglEventSpinner = NULL; /* Standard event sink */ static AG_EventSink *_Nullable wglEventEpilogue = NULL; /* Standard event epilogue */ /* Driver instance data */ typedef struct ag_driver_wgl { struct ag_driver_mw _inherit; HWND hwnd; /* Window handle */ HDC hdc; /* Device context */ HGLRC hglrc; /* Rendering context */ WNDCLASSEX wndclass; /* Window class */ AG_GL_Context gl; /* Common OpenGL context data */ LRESULT nchittest; /* Result of last WM_NCHITTEST */ } AG_DriverWGL; typedef struct ag_cursor_wgl { struct ag_cursor _inherit; int shared; /* Shared cursor */ COLORREF black; COLORREF white; HCURSOR cursor; } AG_CursorWGL; AG_DriverMwClass agDriverWGL; #define AGDRIVER_IS_WGL(drv) \ (AGDRIVER_CLASS(drv) == (AG_DriverClass *)&agDriverWGL) struct ag_windows_key_mapping { int kcode; /* Windows VK_ virtual key */ AG_KeySym key; /* Agar keysym */ }; #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static void WGL_InitDefaultCursor(AG_DriverWGL *_Nonnull); static void WGL_PostResizeCallback(AG_Window *_Nonnull, AG_SizeAlloc *_Nonnull); static int WGL_RaiseWindow(AG_Window *_Nonnull); static int WGL_SetInputFocus(AG_Window *_Nonnull); static void WGL_PostMoveCallback(AG_Window *_Nonnull, AG_SizeAlloc *_Nonnull); static int WGL_GetNextEvent(void *_Nullable, AG_DriverEvent *_Nonnull); static int WGL_ProcessEvent(void *_Nullable, AG_DriverEvent *_Nonnull); static int WGL_GetDisplaySize(Uint *_Nonnull, Uint *_Nonnull); static int WGL_PendingEvents(void *_Nonnull drvCaller); static int WGL_SetCursor(void *_Nonnull, AG_Cursor *_Nonnull); static void WGL_SetWindowsError(char *_Nonnull errorMessage, DWORD errorCode) { char lpBuffer[65536]; if (FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&lpBuffer, 0, NULL)) { AG_SetError("WGL Driver: %s! (%s)", errorMessage, lpBuffer); } else { AG_SetError("WGL Driver: %s!", errorMessage); } } /* Return the Agar window corresponding to a Windows window handle */ static AG_Window *_Nullable LookupWindowByID(HWND hwnd) { AG_Window *win; AG_DriverWGL *wgl; /* XXX can we set a user pointer to avoid this traversal? */ AGOBJECT_FOREACH_CHILD(wgl, &agDrivers, ag_driver_wgl) { if (!AGDRIVER_IS_WGL(wgl) || wgl->hwnd != hwnd) { continue; } win = AGDRIVERMW(wgl)->win; if (!(AGDRIVERMW(wgl)->flags & AG_DRIVER_MW_OPEN) || (win->flags & AG_WINDOW_DETACHING) || WIDGET(win)->drv == NULL) { return (NULL); } return (win); } return (NULL); } /* * Standard AG_EventLoop() event sink. */ static int WGL_EventSink(AG_EventSink *_Nonnull es, AG_Event *_Nonnull event) { AG_DriverEvent dev; /* expand WGL_PendingEvents(): */ if ((TAILQ_EMPTY(&wglEventQ) && GetQueueStatus(QS_ALLINPUT) == 0)) { return (0); } if (WGL_GetNextEvent(NULL, &dev) == 1) { return WGL_ProcessEvent(NULL, &dev); } return (0); } static int WGL_EventEpilogue(AG_EventSink *_Nonnull es, AG_Event *_Nonnull event) { AG_WindowDrawQueued(); AG_WindowProcessQueued(); return (0); } static int WGL_Open(void *_Nonnull obj, const char *_Nullable spec) { AG_Driver *drv = obj; AG_DriverWGL *wgl = obj; /* Register Mouse and keyboard */ if ((drv->mouse = AG_MouseNew(wgl, "Windows mouse")) == NULL || (drv->kbd = AG_KeyboardNew(wgl, "Windows keyboard")) == NULL) goto fail; /* Driver manages rendering of window background. */ drv->flags |= AG_DRIVER_WINDOW_BG; if (nDrivers == 0) { char kbdLayoutName[KL_NAMELENGTH]; /* Set up event filters for standard AG_EventLoop(). */ if ((wglEventSpinner = AG_AddEventSpinner(WGL_EventSink, NULL)) == NULL || (wglEventEpilogue = AG_AddEventEpilogue(WGL_EventEpilogue, NULL)) == NULL) goto fail; /* Initialize globals */ TAILQ_INIT(&wglEventQ); AG_MutexInitRecursive(&wglClassLock); AG_MutexInitRecursive(&wglEventLock); /* Set up keyboard layout */ GetKeyboardLayoutName(kbdLayoutName); if ((wglKbdLayout = LoadKeyboardLayout("00000409", KLF_NOTELLSHELL)) == NULL) { wglKbdLayout = GetKeyboardLayout(0); } LoadKeyboardLayout(kbdLayoutName, KLF_ACTIVATE); InitKeymaps(); } nDrivers++; return (0); fail: if (wglEventSpinner) { AG_DelEventSpinner(wglEventSpinner); wglEventSpinner = NULL; } if (wglEventEpilogue) { AG_DelEventEpilogue(wglEventEpilogue); wglEventEpilogue = NULL; } if (drv->kbd) { AG_ObjectDelete(drv->kbd); drv->kbd = NULL; } if (drv->mouse) { AG_ObjectDelete(drv->mouse); drv->mouse = NULL; } return (-1); } static void WGL_Close(void *_Nonnull obj) { AG_Driver *drv = obj; /* AG_DriverEvent *dev, *devNext; */ #ifdef AG_DEBUG if (nDrivers == 0) { AG_FatalError("Driver close without open"); } #endif if (--nDrivers == 0) { AG_DelEventSink(wglEventSpinner); wglEventSpinner = NULL; AG_DelEventEpilogue(wglEventEpilogue); wglEventEpilogue = NULL; #if 0 for (dev = TAILQ_FIRST(&wglEventQ); dev != TAILQ_LAST(&wglEventQ, ag_driver_eventq); dev = devNext) { devNext = TAILQ_NEXT(dev, events); free(dev); } #endif TAILQ_INIT(&wglEventQ); AG_MutexDestroy(&wglClassLock); AG_MutexDestroy(&wglEventLock); } AG_ObjectDelete(drv->mouse); drv->mouse = NULL; AG_ObjectDelete(drv->kbd); drv->kbd = NULL; } /* Return suitable window style from Agar window flags. */ static void WGL_GetWndStyle(AG_Window *_Nonnull win, DWORD *_Nonnull wndStyle, DWORD *_Nonnull wndStyleEx) { if (win->wmType == AG_WINDOW_WM_NORMAL) { *wndStyle = WS_OVERLAPPEDWINDOW; } else { *wndStyle = WS_POPUP; } *wndStyleEx = 0; if (win->flags & AG_WINDOW_NOTITLE) (*wndStyle) &= ~(WS_CAPTION); if (win->flags & AG_WINDOW_NOMINIMIZE) (*wndStyle) &= ~(WS_MINIMIZEBOX); if (win->flags & AG_WINDOW_NOMAXIMIZE) (*wndStyle) &= ~(WS_MAXIMIZEBOX); if (win->flags & AG_WINDOW_NORESIZE) (*wndStyle) &= ~(WS_THICKFRAME); #if defined(WINVER) && (WINVER >= 0x0400) if (win->wmType == AG_WINDOW_WM_TOOLBAR || win->wmType == AG_WINDOW_WM_MENU || win->wmType == AG_WINDOW_WM_DROPDOWN_MENU || win->wmType == AG_WINDOW_WM_POPUP_MENU) (*wndStyleEx) |= WS_EX_TOOLWINDOW; #endif #if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0500) if (win->flags & AG_WINDOW_KEEPBELOW) (*wndStyleEx) |= WS_EX_NOACTIVATE; #endif #if defined(WS_EX_TOPMOST) if (win->flags & AG_WINDOW_KEEPABOVE) (*wndStyleEx) |= WS_EX_TOPMOST; #endif } /* Return window rectangle adjusted for titlebar/border style. */ static void WGL_GetWndRect(AG_Window *_Nonnull win, AG_Rect *_Nonnull r) { DWORD wndStyle, wndStyleEx; RECT wndRect = { r->x, r->y, r->x + r->w, r->y + r->h }; WGL_GetWndStyle(win, &wndStyle, &wndStyleEx); AdjustWindowRectEx(&wndRect, wndStyle, 0, wndStyleEx); r->x = wndRect.left; r->y = wndRect.top; r->w = wndRect.right - wndRect.left; r->h = wndRect.bottom - wndRect.top; } static int WGL_OpenWindow(AG_Window *_Nonnull win, const AG_Rect *_Nonnull r, int depthReq, Uint mwFlags) { char wndClassName[32]; AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; AG_Driver *drv = WIDGET(win)->drv; GLuint pixelFormat; WNDCLASSEX wndClass; DWORD wndStyle, wndStyleEx; PIXELFORMATDESCRIPTOR pixelFormatDescriptor = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 16, /* Color depth */ 0, 0, 0, 0, 0, 0, 0, 0, /* Ignore Color bits */ 0, 0, 0, 0, 0, /* No Accumulation buffer */ 0, /* No Z-Buffer */ 0, 0, /* No Stencil + AUX-Buffer */ 0, 0, 0, 0, 0 /* All other attributes are not used */ }; RECT wndRect; AG_Rect rVP; int x,y; if (agStereo) pixelFormatDescriptor.dwFlags |= PFD_STEREO; /* Register Window Class */ AG_MutexLock(&wglClassLock); sprintf(wndClassName, "agar-wgl-windowclass-%d", wndClassCount); wndClassCount++; AG_MutexUnlock(&wglClassLock); memset(&wndClass, 0, sizeof(WNDCLASSEX)); wndClass.cbSize = sizeof(WNDCLASSEX); wndClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wndClass.lpfnWndProc = WndProc; wndClass.hInstance = GetModuleHandle(NULL); wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndClass.hCursor = NULL; wndClass.lpszClassName = wndClassName; if (!RegisterClassEx(&wndClass)) { WGL_SetWindowsError("Cannot register WGL window class", GetLastError()); return (-1); } /* Translate Agar window flags to window style. */ WGL_GetWndStyle(win, &wndStyle, &wndStyleEx); x = r->x; y = r->y; /* * XXX TODO it would be best to pass CW_USEDEFAULT here, but * I could not find a way to retrieve the final allocated * coordinates; GetWindowRect() for instance does not seem * to work as expected. For now we'll just center the window. */ if (mwFlags & AG_DRIVER_MW_ANYPOS) { Uint wDisp, hDisp; if (WGL_GetDisplaySize(&wDisp, &hDisp) == 0) { x = wDisp/2 - r->w/2; y = hDisp/2 - r->h/2; } } /* Adjust window with account for window borders, if any */ wndRect.left = x; wndRect.top = y; wndRect.right = x + r->w; wndRect.bottom = y + r->h; AdjustWindowRectEx(&wndRect, wndStyle, 0, wndStyleEx); /* Create OpenGL Window */ wgl->hwnd = CreateWindowEx( wndStyleEx, wndClassName, win->caption, wndStyle, wndRect.left, wndRect.top, (wndRect.right - wndRect.left), (wndRect.bottom - wndRect.top), NULL, NULL, GetModuleHandle(NULL), NULL ); if (!wgl->hwnd) { WGL_SetWindowsError("Cannot create window", GetLastError()); return (-1); } wgl->nchittest = 0; /* Initialize device & rendering context */ if (!(wgl->hdc = GetDC(wgl->hwnd))) { DestroyWindow(wgl->hwnd); WGL_SetWindowsError("GetDC failed", GetLastError()); return (-1); } if (!(pixelFormat = ChoosePixelFormat(wgl->hdc, &pixelFormatDescriptor))) { ReleaseDC(wgl->hwnd, wgl->hdc); DestroyWindow(wgl->hwnd); WGL_SetWindowsError("ChoosePixelFormat failed", GetLastError()); return (-1); } if (!(SetPixelFormat(wgl->hdc, pixelFormat, &pixelFormatDescriptor))) { ReleaseDC(wgl->hwnd, wgl->hdc); DestroyWindow(wgl->hwnd); WGL_SetWindowsError("SetPixelFormat failed", GetLastError()); return (-1); } if (!(wgl->hglrc = wglCreateContext(wgl->hdc))) { ReleaseDC(wgl->hwnd, wgl->hdc); DestroyWindow(wgl->hwnd); WGL_SetWindowsError("wglCreateContext failed", GetLastError()); return (-1); } if (!(wglMakeCurrent(wgl->hdc, wgl->hglrc))) { wglDeleteContext(wgl->hglrc); ReleaseDC(wgl->hwnd, wgl->hdc); DestroyWindow(wgl->hwnd); WGL_SetWindowsError("wglMakeCurrent failed", GetLastError()); return (-1); } AG_GL_InitContext(wgl, &wgl->gl); /* Create the built-in cursors */ WGL_InitDefaultCursor(wgl); AG_InitStockCursors(drv); rVP.x = 0; rVP.y = 0; rVP.w = WIDTH(win); rVP.h = HEIGHT(win); AG_GL_SetViewport(&wgl->gl, &rVP); /* Perform initial size allocation */ { AG_SizeAlloc wa; wa.x = 0; wa.y = 0; wa.w = WIDTH(win); wa.h = HEIGHT(win); AG_WidgetSizeAlloc(win, &wa); AG_WidgetUpdateCoords(win, 0,0); WIDGET(win)->x = x; WIDGET(win)->y = y; } /* Show the window */ ShowWindow(wgl->hwnd, SW_SHOW); if (!(win->flags & AG_WINDOW_KEEPBELOW)) { SetForegroundWindow(wgl->hwnd); /* SetCapture(wgl->hwnd); */ } /* Set the pixel format */ if ((drv->videoFmt = TryMalloc(sizeof(AG_PixelFormat))) == NULL) { goto fail; } #if AG_MODEL == AG_LARGE if (depthReq == 48) { /* Deep color */ # if AG_BYTEORDER == AG_BIG_ENDIAN AG_PixelFormatRGB(drv->videoFmt, 48, 0xffff000000000000, 0x0000ffff00000000, 0x00000000ffff0000); # else AG_PixelFormatRGB(drv->videoFmt, 48, 0x000000000000ffff, 0x00000000ffff0000, 0x0000ffff00000000); # endif } else #endif /* AG_LARGE */ { /* True Color */ #if AG_BYTEORDER == AG_BIG_ENDIAN AG_PixelFormatRGB(drv->videoFmt, 32, 0xff000000, 0x00ff0000, 0x0000ff00); #else AG_PixelFormatRGB(drv->videoFmt, 32, 0x000000ff, 0x0000ff00, 0x00ff0000); #endif } /* Focus the window. */ if (!(win->flags & AG_WINDOW_DENYFOCUS)) { SetFocus(wgl->hwnd); agWindowFocused = win; AG_PostEvent(win, "window-gainfocus", NULL); } return (0); fail: wglDeleteContext(wgl->hglrc); DestroyWindow(wgl->hwnd); if (drv->videoFmt) { AG_PixelFormatFree(drv->videoFmt); free(drv->videoFmt); drv->videoFmt = NULL; } return (-1); } static void WGL_CloseWindow(AG_Window *_Nonnull win) { AG_Driver *drv = WIDGET(win)->drv; AG_DriverWGL *wgl = (AG_DriverWGL *)drv; /* Release allocated cursors. */ AG_FreeCursors(drv); /* Destroy our OpenGL context. */ wglMakeCurrent(wgl->hdc, wgl->hglrc); AG_GL_DestroyContext(wgl); wglDeleteContext(wgl->hglrc); /* Close the window. */ DestroyWindow(wgl->hwnd); if (drv->videoFmt) { AG_PixelFormatFree(drv->videoFmt); free(drv->videoFmt); drv->videoFmt = NULL; } } static int WGL_GetDisplaySize(Uint *_Nonnull w, Uint *_Nonnull h) { RECT r; HWND desktop = GetDesktopWindow(); GetWindowRect(desktop, &r); *w = r.right - r.left; *h = r.bottom - r.top; return (0); } #ifdef DEBUG_WGL # include "drv_wgl_prwincmd.inc" #endif #undef IN_KEYPAD #define IN_KEYPAD(scan) (scan & 0x100) /* Convert scancode to a proper index into agWindowsKeymap[]. */ static int ScanToVirtualKey(int scan, Uint vKey) { int vk = MapVirtualKeyEx(scan & 0xff, 1, wglKbdLayout); switch (vKey) { case VK_DIVIDE: case VK_MULTIPLY: case VK_SUBTRACT: case VK_ADD: case VK_LWIN: case VK_RWIN: case VK_APPS: case VK_LCONTROL: case VK_RCONTROL: case VK_LSHIFT: case VK_RSHIFT: case VK_LMENU: case VK_RMENU: case VK_SNAPSHOT: case VK_PAUSE: return (vKey); } switch (vk) { case VK_INSERT: return IN_KEYPAD(scan) ? vk : VK_NUMPAD0; case VK_END: return IN_KEYPAD(scan) ? vk : VK_NUMPAD1; case VK_DOWN: return IN_KEYPAD(scan) ? vk : VK_NUMPAD2; case VK_NEXT: return IN_KEYPAD(scan) ? vk : VK_NUMPAD3; case VK_LEFT: return IN_KEYPAD(scan) ? vk : VK_NUMPAD4; case VK_CLEAR: return IN_KEYPAD(scan) ? vk : VK_NUMPAD5; case VK_RIGHT: return IN_KEYPAD(scan) ? vk : VK_NUMPAD6; case VK_HOME: return IN_KEYPAD(scan) ? vk : VK_NUMPAD7; case VK_UP: return IN_KEYPAD(scan) ? vk : VK_NUMPAD8; case VK_PRIOR: return IN_KEYPAD(scan) ? vk : VK_NUMPAD9; case VK_DELETE: return IN_KEYPAD(scan) ? vk : VK_DECIMAL; } return (vk != 0) ? vk : vKey; } static __inline__ AG_DriverEvent *_Nullable /* _Malloc_Like_Attribute */ NewEvent(AG_Window *_Nonnull win, enum ag_driver_event_type type) { AG_DriverEvent *dev; if ((dev = TryMalloc(sizeof(AG_DriverEvent))) == NULL) { return (NULL); } dev->win = win; dev->type = type; return (dev); } /* * Window procedure. Most events are just translated to AG_DriverEvent form * and queued for later retrieval by getNextEvent(). */ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { AG_Driver *drv; AG_DriverWGL *wgl; AG_Window *win; AG_DriverEvent *dev = NULL; AG_Mouse *ms; int x, y; LRESULT rv = 1; AG_LockVFS(&agDrivers); if ((win = LookupWindowByID(hWnd)) == NULL) { goto fallback; } drv = WIDGET(win)->drv; wgl = (AG_DriverWGL *)drv; #ifdef DEBUG_WGL WGL_Print_WinMsg(win, uMsg, wParam, lParam); #endif switch (uMsg) { case WM_MOUSEMOVE: if ((dev = NewEvent(win, AG_DRIVER_MOUSE_MOTION)) == NULL) { goto fallback; } x = (int)LOWORD(lParam); y = (int)HIWORD(lParam); ms = drv->mouse; ms->xRel = x - ms->x; /* Update state */ ms->yRel = y - ms->y; ms->x = x; ms->y = y; dev->motion.x = x; dev->motion.y = y; break; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: if ((dev = NewEvent(win, AG_DRIVER_MOUSE_BUTTON_DOWN)) == NULL) goto fallback; drv->mouse->btnState |= AG_MOUSE_BUTTON(dev->button.which); dev->button.which = (wParam & MK_LBUTTON) ? AG_MOUSE_LEFT : (wParam & MK_MBUTTON) ? AG_MOUSE_MIDDLE : (wParam & MK_RBUTTON) ? AG_MOUSE_RIGHT : 0; x = (int)LOWORD(lParam); y = (int)HIWORD(lParam); dev->button.x = AGDRIVER_BOUNDED_WIDTH(win, x); dev->button.y = AGDRIVER_BOUNDED_HEIGHT(win, y); break; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: if ((dev = NewEvent(win, AG_DRIVER_MOUSE_BUTTON_UP)) == NULL) goto fallback; drv->mouse->btnState &= ~(AG_MOUSE_BUTTON(dev->button.which)); dev->button.which = (uMsg == WM_LBUTTONUP) ? AG_MOUSE_LEFT : (uMsg == WM_MBUTTONUP) ? AG_MOUSE_MIDDLE : (uMsg == WM_RBUTTONUP) ? AG_MOUSE_RIGHT : 0; x = (int)LOWORD(lParam); y = (int)HIWORD(lParam); dev->button.x = AGDRIVER_BOUNDED_WIDTH(win, x); dev->button.y = AGDRIVER_BOUNDED_HEIGHT(win, y); break; case 0x020a: /* WM_MOUSEWHEEL (missing define) */ { int move = (short)HIWORD(wParam); if (move == 0) { goto fallback; } if ((dev = NewEvent(win, AG_DRIVER_MOUSE_BUTTON_DOWN)) == NULL) { goto fallback; } dev->button.which = (move > 0) ? AG_MOUSE_WHEELUP : AG_MOUSE_WHEELDOWN; x = (int)LOWORD(lParam) - WIDGET(win)->x; y = (int)HIWORD(lParam) - WIDGET(win)->y; dev->button.x = AGDRIVER_BOUNDED_WIDTH(win, x); dev->button.y = AGDRIVER_BOUNDED_HEIGHT(win, y); drv->mouse->btnState |= AG_MOUSE_BUTTON(dev->button.which); } break; case WM_KEYUP: case WM_KEYDOWN: { AG_KeyboardAction ka; int scan = HIWORD(lParam); Uint vKey = wParam; Uint8 keyState[256]; Uint16 wc[2]; if ((dev = NewEvent(win, (uMsg == WM_KEYDOWN) ? AG_DRIVER_KEY_DOWN : AG_DRIVER_KEY_UP)) == NULL) { goto fallback; } dev->key.ucs = 0; ka = (uMsg == WM_KEYDOWN) ? AG_KEY_PRESSED : AG_KEY_RELEASED; switch (vKey) { case VK_CONTROL: vKey = (lParam & (1<<24)) ? VK_RCONTROL : VK_LCONTROL; break; case VK_RETURN: if (IN_KEYPAD(scan)) { AG_KeyboardUpdate(drv->kbd, ka, AG_KEY_KP_ENTER); dev->key.ks = AG_KEY_KP_ENTER; goto out; } break; } dev->key.ks = agWindowsKeymap[ScanToVirtualKey(scan,vKey)]; GetKeyboardState(keyState); if ((keyState[VK_NUMLOCK] & 1) && vKey >= VK_NUMPAD0 && vKey <= VK_NUMPAD9) { dev->key.ucs = vKey - '0'+VK_NUMPAD0; } else if (ToUnicode((UINT)vKey, scan, keyState, wc,2, 0) > 0) { dev->key.ucs = wc[0]; } AG_KeyboardUpdate(drv->kbd, ka, dev->key.ks); } break; case WM_SETFOCUS: if (win->visible) { dev = NewEvent(win, AG_DRIVER_FOCUS_IN); rv = 0; goto out; } else { goto fallback; } case WM_KILLFOCUS: if (win->visible) { dev = NewEvent(win, AG_DRIVER_FOCUS_OUT); rv = 0; goto out; } else { goto fallback; } case WM_SIZE: if (AGDRIVERMW(drv)->flags & AG_DRIVER_MW_OPEN) { TAILQ_FOREACH(dev, &wglEventQ, events) { if (dev->type == AG_DRIVER_VIDEORESIZE) break; } if (dev) { dev->videoresize.w = LOWORD(lParam); dev->videoresize.h = HIWORD(lParam); dev = NULL; rv = 0; goto out; } if ((dev = NewEvent(win, AG_DRIVER_VIDEORESIZE)) == NULL) { goto fallback; } dev->videoresize.x = -1; dev->videoresize.y = -1; dev->videoresize.w = LOWORD(lParam); dev->videoresize.h = HIWORD(lParam); } else { goto fallback; } rv = 0; goto out; case WM_MOVE: WIDGET(win)->x = (int)(short)LOWORD(lParam); WIDGET(win)->y = (int)(short)HIWORD(lParam); rv = 0; goto out; #if 0 /* * XXX TODO: use TrackMouseEvent(), translate WM_MOUSEHOVER * events to AG_DRIVER_MOUSE_ENTER. */ case WM_MOUSEHOVER: break; #endif case WM_MOUSELEAVE: dev = NewEvent(win, AG_DRIVER_MOUSE_LEAVE); break; case WM_ERASEBKGND: dev = NewEvent(win, AG_DRIVER_EXPOSE); rv = 0; goto out; case WM_CLOSE: dev = NewEvent(win, AG_DRIVER_CLOSE); rv = 0; goto out; case WM_SETCURSOR: x = drv->mouse->x; y = drv->mouse->y; AG_MouseCursorUpdate(win, x, y); break; case WM_NCHITTEST: rv = DefWindowProc(hWnd, uMsg, wParam, lParam); wgl->nchittest = rv; goto out; default: goto fallback; } out: if (dev) { TAILQ_INSERT_TAIL(&wglEventQ, dev, events); } AG_UnlockVFS(&agDrivers); return (rv); fallback: AG_UnlockVFS(&agDrivers); return DefWindowProc(hWnd, uMsg, wParam, lParam); } static int WGL_PendingEvents(void *_Nonnull drvCaller) { return (!TAILQ_EMPTY(&wglEventQ) || GetQueueStatus(QS_ALLINPUT) != 0); } static int WGL_GetNextEvent(void *_Nonnull drvCaller, AG_DriverEvent *_Nonnull dev) { AG_DriverEvent *devFirst; MSG msg; if (!TAILQ_EMPTY(&wglEventQ)) { goto get_event; } if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (!TAILQ_EMPTY(&wglEventQ)) goto get_event; } return (0); get_event: devFirst = TAILQ_FIRST(&wglEventQ); memcpy(dev, devFirst, sizeof(AG_DriverEvent)); TAILQ_REMOVE(&wglEventQ, devFirst, events); free(devFirst); return (1); } static int WGL_ProcessEvent(void *_Nullable drvCaller, AG_DriverEvent *_Nonnull dev) { AG_SizeAlloc a; AG_Driver *drv; AG_Window *win; int rv=1, useText; if ((win = dev->win) == NULL || win->flags & AG_WINDOW_DETACHING) return (0); AG_LockVFS(&agDrivers); drv = WIDGET(win)->drv; 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); 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 (win != agWindowFocused) { agWindowFocused = win; AG_PostEvent(win, "window-gainfocus", NULL); } break; case AG_DRIVER_FOCUS_OUT: if (win == agWindowFocused) { AG_PostEvent(win, "window-lostfocus", NULL); agWindowFocused = NULL; } break; case AG_DRIVER_VIDEORESIZE: a.x = dev->videoresize.x; a.y = dev->videoresize.y; a.w = dev->videoresize.w; a.h = dev->videoresize.h; if (a.w != WIDTH(win) || a.h != HEIGHT(win)) { WGL_PostResizeCallback(win, &a); } else { WGL_PostMoveCallback(win, &a); } break; case AG_DRIVER_CLOSE: AG_PostEvent(win, "window-close", NULL); break; case AG_DRIVER_EXPOSE: win->dirty = 1; break; default: rv = 0; break; } if (useText) { AG_PopTextState(); } AG_UnlockVFS(&agDrivers); return (rv); } static void WGL_BeginRendering(void *_Nonnull obj) { AG_DriverWGL *wgl = obj; wglMakeCurrent(wgl->hdc, wgl->hglrc); } static void WGL_RenderWindow(AG_Window *_Nonnull win) { const AG_Color *cBg = &WCOLOR(win, BG_COLOR); AG_PushClipRect(win, &WIDGET(win)->r); glClearColor((float)cBg->r / AG_COLOR_LASTF, (float)cBg->g / AG_COLOR_LASTF, (float)cBg->b / AG_COLOR_LASTF, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); AG_WidgetDraw(win); AG_PopClipRect(win); } static void WGL_EndRendering(void *_Nonnull obj) { AG_DriverWGL *wgl = obj; AG_GL_Context *gl = &wgl->gl; Uint i; SwapBuffers(wgl->hdc); /* Remove textures and display lists queued for deletion */ glDeleteTextures(gl->nTextureGC, gl->textureGC); for (i = 0; i < gl->nListGC; i++) { glDeleteLists(gl->listGC[i], 1); } gl->nTextureGC = 0; gl->nListGC = 0; } static int WGL_MapWindow(AG_Window *_Nonnull win) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; ShowWindow(wgl->hwnd, SW_SHOW); return (0); } static int WGL_UnmapWindow(AG_Window *_Nonnull win) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; ShowWindow(wgl->hwnd, SW_HIDE); return (0); } static int WGL_RaiseWindow(AG_Window *_Nonnull win) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; SetWindowPos(wgl->hwnd, HWND_TOP, 0,0, 0,0, SWP_NOMOVE | SWP_NOSIZE); return (0); } static int WGL_LowerWindow(AG_Window *_Nonnull win) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; SetWindowPos(wgl->hwnd, HWND_BOTTOM, 0,0, 0,0, SWP_NOMOVE | SWP_NOSIZE); return (0); } static int WGL_ReparentWindow(AG_Window *_Nonnull win, AG_Window *_Nonnull winParent, int x, int y) { AG_DriverWGL *wglWin = (AG_DriverWGL *)WIDGET(win)->drv; AG_DriverWGL *wglParentWin = (AG_DriverWGL *)WIDGET(winParent)->drv; SetParent(wglWin->hwnd, wglParentWin->hwnd); return (0); } static int WGL_GetInputFocus(AG_Window **rv) { AG_DriverWGL *wgl = NULL; HWND hwnd; hwnd = GetFocus(); AGOBJECT_FOREACH_CHILD(wgl, &agDrivers, ag_driver_wgl) { if (!AGDRIVER_IS_WGL(wgl)) { continue; } if (wgl->hwnd == hwnd) break; } if (wgl == NULL) { AG_SetError("Input focus is external to this application"); return (-1); } *rv = AGDRIVERMW(wgl)->win; return (0); } static int WGL_SetInputFocus(AG_Window *win) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; SetFocus(wgl->hwnd); return (0); } static int WGL_MoveWindow(AG_Window *win, int x, int y) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; AG_SizeAlloc a; SetWindowPos(wgl->hwnd, NULL, x,y, 0,0, SWP_NOZORDER | SWP_NOSIZE); a.x = x; a.y = y; a.w = WIDTH(win); a.h = HEIGHT(win); WGL_PostMoveCallback(win, &a); return (0); } static int WGL_ResizeWindow(AG_Window *win, Uint w, Uint h) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; AG_SizeAlloc a; AG_Rect r; r.x = WIDGET(win)->x; r.y = WIDGET(win)->y; r.w = w; r.h = h; WGL_GetWndRect(win, &r); SetWindowPos(wgl->hwnd, NULL, 0,0, r.w,r.h, SWP_NOZORDER | SWP_NOMOVE); a.x = WIDGET(win)->x; a.y = WIDGET(win)->y; a.w = w; a.h = h; WGL_PostResizeCallback(win, &a); return (0); } static int WGL_MoveResizeWindow(AG_Window *win, AG_SizeAlloc *a) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; AG_Rect r; r.x = a->x; r.y = a->y; r.w = a->w; r.h = a->h; WGL_GetWndRect(win, &r); SetWindowPos(wgl->hwnd, NULL, r.x, r.y, r.w, r.h, SWP_NOZORDER); WGL_PostResizeCallback(win, a); return (0); } static int WGL_SetWindowCaption(AG_Window *win, const char *s) { AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; SetWindowText(wgl->hwnd, s); return (0); } static void WGL_SetTransientFor(AG_Window *win, AG_Window *winParent) { /* Nothing to do */ } static void WGL_TweakAlignment(AG_Window *win, AG_SizeAlloc *a, Uint wMax, Uint hMax) { /* XXX TODO */ switch (win->alignment) { case AG_WINDOW_TL: case AG_WINDOW_TC: case AG_WINDOW_TR: a->y += 50; if (a->y+a->h > hMax) { a->y = 0; } break; case AG_WINDOW_BL: case AG_WINDOW_BC: case AG_WINDOW_BR: a->y -= 100; if (a->y < 0) { a->y = 0; } break; } } /* * Cursor operations */ /* Initialize the default cursor. */ static void WGL_InitDefaultCursor(AG_DriverWGL *wgl) { AG_Driver *drv = AGDRIVER(wgl); const int nStockCursors = 1; /* TODO map */ int i; for (i = 0; i < nStockCursors; i++) { AG_CursorWGL *acWGL; acWGL = Malloc(sizeof(AG_CursorWGL)); acWGL->shared = 1; acWGL->cursor = LoadCursor(NULL, IDC_ARROW); AG_CursorInit(AGCURSOR(acWGL)); TAILQ_INSERT_HEAD(&drv->cursors, AGCURSOR(acWGL), cursors); drv->nCursors++; } } static AG_Cursor * WGL_CreateCursor(void *_Nonnull obj, Uint w, Uint h, const Uint8 *_Nonnull data, const Uint8 *_Nonnull mask, int xHot, int yHot) { AG_Cursor *ac; AG_CursorWGL *acWGL; int dataSize, i; BYTE *xorMask, *andMask; Uint size = w*h; /* * Initialize generic Agar cursor part. */ if ((acWGL = TryMalloc(sizeof(AG_CursorWGL))) == NULL) { return (NULL); } ac = (AG_Cursor *)acWGL; if ((ac->data = TryMalloc(size)) == NULL) { goto fail; } if ((ac->mask = TryMalloc(size)) == NULL) { free(ac->data); goto fail; } memcpy(ac->data, data, size); memcpy(ac->mask, mask, size); ac->w = w; ac->h = h; ac->xHot = xHot; ac->yHot = yHot; /* * Initialize Windows-specific part. */ acWGL->shared = 0; acWGL->black = RGB(0, 0, 0); acWGL->white = RGB(0xFF, 0xFF, 0xFF); dataSize = (ac->w / 8) * ac->h; if ((xorMask = TryMalloc(dataSize)) == NULL) { free(ac->data); goto fail; } if ((andMask = TryMalloc(dataSize)) == NULL) { free(xorMask); free(ac->data); goto fail; } for (i = 0; i < dataSize; i++) { andMask[i] = ~ac->mask[i]; xorMask[i] = ~ac->data[i] ^ ~ac->mask[i]; } acWGL->cursor = CreateCursor(GetModuleHandle(NULL), ac->xHot, ac->yHot, ac->w, ac->h, andMask, xorMask); if (!acWGL->cursor) { WGL_SetWindowsError("CreateCursor", GetLastError()); goto fail; } return (ac); fail: free(ac); return (NULL); } static void WGL_FreeCursor(void *_Nonnull obj, AG_Cursor *_Nonnull ac) { AG_Driver *drv = obj; AG_CursorWGL *acWGL = (AG_CursorWGL *)ac; if (ac == drv->activeCursor) { drv->activeCursor = NULL; } if (!acWGL->shared) { DestroyCursor(acWGL->cursor); } free(ac->data); free(ac->mask); free(ac); } static int WGL_SetCursor(void *_Nonnull obj, AG_Cursor *_Nonnull ac) { AG_Driver *drv = obj; AG_CursorWGL *acWGL = (AG_CursorWGL *)ac; if (drv->activeCursor == ac) { return (0); } SetCursor(acWGL->cursor); drv->activeCursor = ac; return (0); } static void WGL_UnsetCursor(void *_Nonnull obj) { AG_Driver *drv = obj; AG_DriverWGL *wgl = (AG_DriverWGL *)drv; switch (wgl->nchittest) { case HTBOTTOM: SetCursor(LoadCursor(NULL, IDC_SIZENS)); break; case HTBOTTOMLEFT: SetCursor(LoadCursor(NULL, IDC_SIZENESW)); break; case HTBOTTOMRIGHT: SetCursor(LoadCursor(NULL, IDC_SIZENWSE)); break; case HTLEFT: case HTRIGHT: SetCursor(LoadCursor(NULL, IDC_SIZEWE)); break; default: SetCursor(LoadCursor(NULL, IDC_ARROW)); break; } drv->activeCursor = TAILQ_FIRST(&drv->cursors); } static int WGL_GetCursorVisibility(void *_Nonnull obj) { /* XXX TODO */ return (1); } static void WGL_SetCursorVisibility(void *_Nonnull obj, int flag) { /* XXX TODO */ } static void WGL_PreResizeCallback(AG_Window *_Nonnull win) { #if 0 AG_DriverWGL *wgl = (AG_DriverWGL *)WIDGET(win)->drv; /* * Backup all GL resources since it is not portable to assume that a * display resize will not cause a change in GL contexts * (XXX TODO test for platforms where this is unnecessary) */ glXMakeCurrent(agDisplay, glx->w, glx->glxCtx); FreeWidgetResources(WIDGET(win)); AG_ClearGlyphCache(); #endif } static void WGL_PostResizeCallback(AG_Window *_Nonnull win, AG_SizeAlloc *_Nonnull a) { AG_Driver *drv = WIDGET(win)->drv; AG_DriverWGL *wgl = (AG_DriverWGL *)drv; AG_SizeAlloc wa; AG_Rect rVP; const int x = (a->x == -1) ? WIDGET(win)->x : a->x; const int y = (a->y == -1) ? WIDGET(win)->y : a->y; wa.x = 0; wa.y = 0; wa.w = a->w; wa.h = a->h; AG_WidgetSizeAlloc(win, &wa); AG_WidgetUpdateCoords(win, 0,0); WIDGET(win)->x = x; WIDGET(win)->y = y; win->dirty = 1; wglMakeCurrent(wgl->hdc, wgl->hglrc); rVP.x = 0; rVP.y = 0; rVP.w = WIDTH(win); rVP.h = HEIGHT(win); AG_GL_SetViewport(&wgl->gl, &rVP); } static void WGL_PostMoveCallback(AG_Window *_Nonnull win, AG_SizeAlloc *_Nonnull a) { AG_SizeAlloc wa; wa.x = 0; wa.y = 0; wa.w = a->w; wa.h = a->h; AG_WidgetSizeAlloc(win, &wa); AG_WidgetUpdateCoords(win, 0,0); WIDGET(win)->x = a->x; WIDGET(win)->y = a->y; win->dirty = 1; if (agWindowPinnedCount > 0) AG_WindowMovePinned(win, a->x - WIDGET(win)->x, a->y - WIDGET(win)->y); } AG_DriverMwClass agDriverWGL = { { { "AG_Driver:AG_DriverMw:AG_DriverWGL", sizeof(AG_DriverWGL), { 1,7, AGC_DRIVER_WGL, 0xE093 }, NULL, /* init */ NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL, /* edit */ }, "wgl", AG_VECTOR, AG_WM_MULTIPLE, AG_DRIVER_OPENGL | AG_DRIVER_TEXTURES, WGL_Open, WGL_Close, WGL_GetDisplaySize, NULL, /* beginEventProcessing */ WGL_PendingEvents, WGL_GetNextEvent, WGL_ProcessEvent, NULL, /* genericEventLoop */ NULL, /* endEventProcessing */ NULL, /* terminate */ WGL_BeginRendering, WGL_RenderWindow, WGL_EndRendering, AG_GL_FillRect, NULL, /* updateRegion */ AG_GL_StdUploadTexture, AG_GL_StdUpdateTexture, AG_GL_StdDeleteTexture, NULL, /* setRefreshRate */ AG_GL_StdPushClipRect, AG_GL_StdPopClipRect, AG_GL_StdPushBlendingMode, AG_GL_StdPopBlendingMode, WGL_CreateCursor, WGL_FreeCursor, WGL_SetCursor, WGL_UnsetCursor, WGL_GetCursorVisibility, WGL_SetCursorVisibility, AG_GL_BlitSurface, AG_GL_BlitSurfaceFrom, #ifdef HAVE_OPENGL AG_GL_BlitSurfaceGL, AG_GL_BlitSurfaceFromGL, AG_GL_BlitSurfaceFlippedGL, #endif AG_GL_BackupSurfaces, AG_GL_RestoreSurfaces, AG_GL_RenderToSurface, AG_GL_PutPixel, AG_GL_PutPixel32, AG_GL_PutPixelRGB8, #if AG_MODEL == AG_LARGE AG_GL_PutPixel64, AG_GL_PutPixelRGB16, #endif AG_GL_BlendPixel, AG_GL_DrawLine, AG_GL_DrawLineH, AG_GL_DrawLineV, AG_GL_DrawLineBlended, AG_GL_DrawLineW, AG_GL_DrawLineW_Sti16, AG_GL_DrawTriangle, AG_GL_DrawPolygon, AG_GL_DrawPolygon_Sti32, AG_GL_DrawArrow, AG_GL_DrawBoxRounded, AG_GL_DrawBoxRoundedTop, AG_GL_DrawCircle, AG_GL_DrawCircleFilled, AG_GL_DrawRectFilled, AG_GL_DrawRectBlended, AG_GL_DrawRectDithered, AG_GL_UpdateGlyph, AG_GL_DrawGlyph, AG_GL_StdDeleteList, NULL, /* getClipboardText */ NULL, /* setClipboardText */ NULL /* setMouseAutoCapture */ }, WGL_OpenWindow, WGL_CloseWindow, WGL_MapWindow, WGL_UnmapWindow, WGL_RaiseWindow, WGL_LowerWindow, WGL_ReparentWindow, WGL_GetInputFocus, WGL_SetInputFocus, WGL_MoveWindow, WGL_ResizeWindow, WGL_MoveResizeWindow, WGL_PreResizeCallback, WGL_PostResizeCallback, NULL, /* setBorderWidth */ WGL_SetWindowCaption, WGL_SetTransientFor, NULL, /* setOpacity (TODO) */ WGL_TweakAlignment, NULL, /* setWindowMinSize */ NULL /* setWindowMaxSize */ };