#include <jni.h> #include <stdlib.h> #include <assert.h> #define WIN32_LEAN_AND_MEAN #include <windows.h> #undef WIN32_LEAN_AND_MEAN #include <wingdi.h> #include <stddef.h> #include <gluegen_stdint.h> #include <stdio.h> #include "NativewindowCommon.h" #include "jogamp_nativewindow_windows_GDIUtil.h" // #define VERBOSE_ON 1 #ifdef VERBOSE_ON #define DBG_PRINT(args...) fprintf(stderr, args); #define DBG_PRINT_FLUSH(args...) fprintf(stderr, args); fflush(stderr); #else #define DBG_PRINT(args...) #define DBG_PRINT_FLUSH(args...) #endif static const char * const ClazzNamePoint = "javax/media/nativewindow/util/Point"; static const char * const ClazzAnyCstrName = "<init>"; static const char * const ClazzNamePointCstrSignature = "(II)V"; static jclass pointClz = NULL; static jmethodID pointCstr = NULL; static jmethodID dumpStackID = NULL; typedef struct { HANDLE threadHandle; DWORD threadId; volatile BOOL threadReady; volatile BOOL threadDead; } DummyThreadContext; typedef struct { HINSTANCE hInstance; const TCHAR* wndClassName; const TCHAR* wndName; jint x; jint y; jint width; jint height; volatile HWND hWnd; volatile BOOL threadReady; } DummyThreadCommand; #define TM_OPENWIN WM_APP+1 #define TM_CLOSEWIN WM_APP+2 #define TM_STOP WM_APP+3 static const char * sTM_OPENWIN = "TM_OPENWIN"; static const char * sTM_CLOSEWIN = "TM_CLOSEWIN"; static const char * sTM_STOP = "TM_STOP"; /** 3s timeout */ static const int64_t TIME_OUT = 3000000; static jboolean DDT_CheckAlive(JNIEnv *env, const char *msg, DummyThreadContext *ctx) { if( ctx->threadDead ) { NativewindowCommon_throwNewRuntimeException(env, "DDT is dead at %s", msg); return JNI_FALSE; } else { DBG_PRINT_FLUSH("*** DDT-Check ALIVE @ %s\n", msg); return JNI_TRUE; } } static jboolean DDT_WaitUntilCreated(JNIEnv *env, DummyThreadContext *ctx, BOOL created) { const int64_t t0 = NativewindowCommon_CurrentTimeMillis(); int64_t t1 = t0; if( created ) { while( !ctx->threadReady && t1-t0 < TIME_OUT ) { t1 = NativewindowCommon_CurrentTimeMillis(); } if( !ctx->threadReady ) { NativewindowCommon_throwNewRuntimeException(env, "TIMEOUT (%d ms) while waiting for DDT CREATED", (int)(t1-t0)); return JNI_FALSE; } DBG_PRINT_FLUSH("*** DDT-Check CREATED\n"); } else { while( !ctx->threadDead && t1-t0 < TIME_OUT ) { t1 = NativewindowCommon_CurrentTimeMillis(); } if( !ctx->threadDead ) { NativewindowCommon_throwNewRuntimeException(env, "TIMEOUT (%d ms) while waiting for DDT DESTROYED", (int)(t1-t0)); return JNI_FALSE; } DBG_PRINT_FLUSH("*** DDT-Check DEAD\n"); } return JNI_TRUE; } static jboolean DDT_WaitUntilReady(JNIEnv *env, const char *msg, DummyThreadCommand *cmd) { const int64_t t0 = NativewindowCommon_CurrentTimeMillis(); int64_t t1 = t0; while( !cmd->threadReady && t1-t0 < TIME_OUT ) { t1 = NativewindowCommon_CurrentTimeMillis(); } if( !cmd->threadReady ) { NativewindowCommon_throwNewRuntimeException(env, "TIMEOUT (%d ms) while waiting for DDT %s", (int)(t1-t0), msg); return JNI_FALSE; } DBG_PRINT_FLUSH("*** DDT-Check READY @ %s\n", msg); return JNI_TRUE; } static HWND DummyWindowCreate (HINSTANCE hInstance, const TCHAR* wndClassName, const TCHAR* wndName, jint x, jint y, jint width, jint height) { DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; DWORD dwStyle = WS_OVERLAPPEDWINDOW; HWND hWnd = CreateWindowEx( dwExStyle, wndClassName, wndName, dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, x, y, width, height, NULL, NULL, hInstance, NULL ); return hWnd; } static DWORD WINAPI DummyDispatchThreadFunc(LPVOID param) { MSG msg; BOOL bRet; BOOL bEOL=FALSE; DummyThreadContext *threadContext = (DummyThreadContext*)param; /* there can not be any messages for us now, as the creator waits for threadReady before continuing, but we must use this PeekMessage() to create the thread message queue */ PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); /* now we can safely say: we have a qeue and are ready to receive messages */ // threadContext->threadId = GetCurrentThreadId(); threadContext->threadDead = FALSE; threadContext->threadReady = TRUE; while( !bEOL && (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0 ) { if ( -1 == bRet ) { fprintf(stderr, "DummyDispatchThread (id %d): GetMessage Error %d, werr %d\n", (int)threadContext->threadId, (int)bRet, (int)GetLastError()); fflush(stderr); bEOL = TRUE; break; // EOL } else { switch(msg.message) { case TM_OPENWIN: { DummyThreadCommand *tParam = (DummyThreadCommand*)msg.wParam; DBG_PRINT_FLUSH("*** DDT-Dispatch OPENWIN\n"); tParam->hWnd = DummyWindowCreate(tParam->hInstance, tParam->wndClassName, tParam->wndName, tParam->x, tParam->y, tParam->width, tParam->height); tParam->threadReady = TRUE; } break; case TM_CLOSEWIN: { DummyThreadCommand *tParam = (DummyThreadCommand*)msg.wParam; DBG_PRINT_FLUSH("*** DDT-Dispatch CLOSEWIN\n"); DestroyWindow(tParam->hWnd); tParam->threadReady = TRUE; } break; case TM_STOP: { DummyThreadCommand *tParam = (DummyThreadCommand*)msg.wParam; DBG_PRINT_FLUSH("*** DDT-Dispatch STOP -> DEAD\n"); tParam->threadReady = TRUE; bEOL = TRUE; } break; // EOL default: TranslateMessage(&msg); DispatchMessage(&msg); break; } } } /* dead */ DBG_PRINT_FLUSH("*** DDT-Dispatch DEAD\n"); threadContext->threadDead = TRUE; ExitThread(0); return 0; } HINSTANCE GetApplicationHandle() { return GetModuleHandle(NULL); } /* Java->C glue code: * Java package: jogamp.nativewindow.windows.GDIUtil * Java method: long CreateDummyDispatchThread0() */ JNIEXPORT jlong JNICALL Java_jogamp_nativewindow_windows_GDIUtil_CreateDummyDispatchThread0 (JNIEnv *env, jclass _unused) { DWORD threadId = 0; DummyThreadContext * dispThreadCtx = calloc(1, sizeof(DummyThreadContext)); dispThreadCtx->threadHandle = CreateThread(NULL, 0, DummyDispatchThreadFunc, (LPVOID)dispThreadCtx, 0, &threadId); if( NULL == dispThreadCtx->threadHandle || 0 == threadId ) { const HANDLE threadHandle = dispThreadCtx->threadHandle; if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT CREATE failed handle %p, id %d, werr %d", (void*)threadHandle, (int)threadId, (int)GetLastError()); return (jlong)0; } if( JNI_FALSE == DDT_WaitUntilCreated(env, dispThreadCtx, TRUE) ) { const HANDLE threadHandle = dispThreadCtx->threadHandle; if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT CREATE (ack) failed handle %p, id %d, werr %d", (void*)threadHandle, (int)threadId, (int)GetLastError()); return (jlong)0; } DBG_PRINT_FLUSH("*** DDT Created %d\n", (int)threadId); dispThreadCtx->threadId = threadId; return (jlong) (intptr_t) dispThreadCtx; } /* Java->C glue code: * Java package: jogamp.nativewindow.windows.GDIUtil * Java method: boolean CreateWindowClass0(long hInstance, java.lang.String clazzName, long wndProc) * C function: BOOL CreateWindowClass0(HANDLE hInstance, LPCSTR clazzName, HANDLE wndProc); */ JNIEXPORT jboolean JNICALL Java_jogamp_nativewindow_windows_GDIUtil_CreateWindowClass0 (JNIEnv *env, jclass _unused, jlong jHInstance, jstring jClazzName, jlong wndProc, jlong iconSmallHandle, jlong iconBigHandle) { HINSTANCE hInstance = (HINSTANCE) (intptr_t) jHInstance; const TCHAR* clazzName = NULL; WNDCLASSEX wc; jboolean res; #ifdef UNICODE clazzName = NewtCommon_GetNullTerminatedStringChars(env, jClazzName); #else clazzName = (*env)->GetStringUTFChars(env, jClazzName, NULL); #endif ZeroMemory( &wc, sizeof( wc ) ); if( GetClassInfoEx( hInstance, clazzName, &wc ) ) { // registered already res = JNI_TRUE; } else { // register now ZeroMemory( &wc, sizeof( wc ) ); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW ; wc.lpfnWndProc = (WNDPROC) (intptr_t) wndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = (HICON) (intptr_t) iconBigHandle; wc.hCursor = NULL; wc.hbrBackground = NULL; // no background paint - GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = clazzName; wc.hIconSm = (HICON) (intptr_t) iconSmallHandle; res = ( 0 != RegisterClassEx( &wc ) ) ? JNI_TRUE : JNI_FALSE ; } #ifdef UNICODE free((void*) clazzName); #else (*env)->ReleaseStringUTFChars(env, jClazzName, clazzName); #endif return res; } /* Java->C glue code: * Java package: jogamp.nativewindow.windows.GDIUtil * Java method: boolean DestroyWindowClass0(long hInstance, java.lang.String className, long dispThreadCtx) */ JNIEXPORT jboolean JNICALL Java_jogamp_nativewindow_windows_GDIUtil_DestroyWindowClass0 (JNIEnv *env, jclass gdiClazz, jlong jHInstance, jstring jClazzName, jlong jDispThreadCtx) { HINSTANCE hInstance = (HINSTANCE) (intptr_t) jHInstance; const TCHAR* clazzName = NULL; DummyThreadContext * dispThreadCtx = (DummyThreadContext *) (intptr_t) jDispThreadCtx; jboolean res; #ifdef UNICODE clazzName = NewtCommon_GetNullTerminatedStringChars(env, jClazzName); #else clazzName = (*env)->GetStringUTFChars(env, jClazzName, NULL); #endif res = ( 0 != UnregisterClass( clazzName, hInstance ) ) ? JNI_TRUE : JNI_FALSE ; #ifdef UNICODE free((void*) clazzName); #else (*env)->ReleaseStringUTFChars(env, jClazzName, clazzName); #endif if( NULL != dispThreadCtx) { const HANDLE threadHandle = dispThreadCtx->threadHandle; const DWORD threadId = dispThreadCtx->threadId; DummyThreadCommand tParam = {0}; tParam.threadReady = FALSE; DBG_PRINT_FLUSH("*** DDT Destroy %d\n", (int)threadId); #ifdef VERBOSE_ON (*env)->CallStaticVoidMethod(env, gdiClazz, dumpStackID); #endif if( JNI_FALSE == DDT_CheckAlive(env, sTM_STOP, dispThreadCtx) ) { free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT %s (alive) failed handle %p, id %d, werr %d", sTM_STOP, (void*)threadHandle, (int)threadId, (int)GetLastError()); return JNI_FALSE; } if ( 0 != PostThreadMessage(dispThreadCtx->threadId, TM_STOP, (WPARAM)&tParam, 0) ) { if( JNI_FALSE == DDT_WaitUntilReady(env, sTM_STOP, &tParam) ) { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT Post %s (ack) failed handle %p, id %d, werr %d", sTM_STOP, (void*)threadHandle, (int)threadId, (int)GetLastError()); return JNI_FALSE; } if( JNI_FALSE == DDT_WaitUntilCreated(env, dispThreadCtx, FALSE) ) { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT KILL %s (ack) failed handle %p, id %d, werr %d", sTM_STOP, (void*)threadHandle, (int)threadId, (int)GetLastError()); return JNI_FALSE; } free(dispThreadCtx); // free after proper DDT shutdown! } else { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT Post %s failed handle %p, id %d, werr %d", sTM_STOP, (void*)threadHandle, (int)threadId, (int)GetLastError()); return JNI_FALSE; } } return res; } /* Java->C glue code: * Java package: jogamp.nativewindow.windows.GDIUtil * Java method: long CreateDummyWindow0(long hInstance, java.lang.String className, jlong dispThreadCtx, java.lang.String windowName, int x, int y, int width, int height) */ JNIEXPORT jlong JNICALL Java_jogamp_nativewindow_windows_GDIUtil_CreateDummyWindow0 (JNIEnv *env, jclass _unused, jlong jHInstance, jstring jWndClassName, jlong jDispThreadCtx, jstring jWndName, jint x, jint y, jint width, jint height) { DummyThreadContext * dispThreadCtx = (DummyThreadContext *) (intptr_t) jDispThreadCtx; DummyThreadCommand tParam = {0}; tParam.hInstance = (HINSTANCE) (intptr_t) jHInstance; tParam.x = x; tParam.y = y; tParam.width = width; tParam.height = height; tParam.hWnd = 0; tParam.threadReady = FALSE; #ifdef UNICODE tParam.wndClassName = NewtCommon_GetNullTerminatedStringChars(env, jWndClassName); tParam.wndName = NewtCommon_GetNullTerminatedStringChars(env, jWndName); #else tParam.wndClassName = (*env)->GetStringUTFChars(env, jWndClassName, NULL); tParam.wndName = (*env)->GetStringUTFChars(env, jWndName, NULL); #endif if( NULL == dispThreadCtx ) { tParam.hWnd = DummyWindowCreate(tParam.hInstance, tParam.wndClassName, tParam.wndName, tParam.x, tParam.y, tParam.width, tParam.height); } else { const HANDLE threadHandle = dispThreadCtx->threadHandle; const DWORD threadId = dispThreadCtx->threadId; if( JNI_FALSE == DDT_CheckAlive(env, sTM_OPENWIN, dispThreadCtx) ) { free(dispThreadCtx); NativewindowCommon_throwNewRuntimeException(env, "DDT %s (alive) failed handle %p, id %d, werr %d", sTM_OPENWIN, (void*)threadHandle, (int)threadId, (int)GetLastError()); } else { if( 0 != PostThreadMessage(dispThreadCtx->threadId, TM_OPENWIN, (WPARAM)&tParam, 0) ) { if( JNI_FALSE == DDT_WaitUntilReady(env, sTM_OPENWIN, &tParam) ) { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); tParam.hWnd = 0; NativewindowCommon_throwNewRuntimeException(env, "DDT Post %s (ack) failed handle %p, id %d, werr %d", sTM_OPENWIN, (void*)threadHandle, (int)threadId, (int)GetLastError()); } } else { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); tParam.hWnd = 0; NativewindowCommon_throwNewRuntimeException(env, "DDT Post %s to handle %p, id %d failed, werr %d", sTM_OPENWIN, (void*)threadHandle, (int)threadId, (int)GetLastError()); } } } #ifdef UNICODE free((void*) tParam.wndClassName); free((void*) tParam.wndName); #else (*env)->ReleaseStringUTFChars(env, jWndClassName, tParam.wndClassName); (*env)->ReleaseStringUTFChars(env, jWndName, tParam.wndName); #endif return (jlong) (intptr_t) tParam.hWnd; } /* * Class: jogamp_nativewindow_windows_GDIUtil * Method: DestroyWindow0 */ JNIEXPORT jboolean JNICALL Java_jogamp_nativewindow_windows_GDIUtil_DestroyWindow0 (JNIEnv *env, jclass unused, jlong jDispThreadCtx, jlong jwin) { jboolean res = JNI_TRUE; DummyThreadContext * dispThreadCtx = (DummyThreadContext *) (intptr_t) jDispThreadCtx; HWND hWnd = (HWND) (intptr_t) jwin; if( NULL == dispThreadCtx ) { DestroyWindow(hWnd); } else { const HANDLE threadHandle = dispThreadCtx->threadHandle; const DWORD threadId = dispThreadCtx->threadId; DummyThreadCommand tParam = {0}; tParam.hWnd = hWnd; tParam.threadReady = FALSE; if( JNI_FALSE == DDT_CheckAlive(env, sTM_CLOSEWIN, dispThreadCtx) ) { free(dispThreadCtx); res = JNI_FALSE; NativewindowCommon_throwNewRuntimeException(env, "DDT %s (alive) failed handle %p, id %d, werr %d", sTM_CLOSEWIN, (void*)threadHandle, (int)threadId, (int)GetLastError()); } else { if ( 0 != PostThreadMessage(dispThreadCtx->threadId, TM_CLOSEWIN, (WPARAM)&tParam, 0) ) { if( JNI_FALSE == DDT_WaitUntilReady(env, sTM_CLOSEWIN, &tParam) ) { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); res = JNI_FALSE; NativewindowCommon_throwNewRuntimeException(env, "DDT Post %s (ack) failed handle %p, id %d, werr %d", sTM_CLOSEWIN, (void*)threadHandle, (int)threadId, (int)GetLastError()); } } else { if( NULL != threadHandle ) { TerminateThread(threadHandle, 0); } free(dispThreadCtx); res = JNI_FALSE; NativewindowCommon_throwNewRuntimeException(env, "DDT Post %s to handle %p, id %d failed, werr %d", sTM_CLOSEWIN, (void*)threadHandle, (int)threadId, (int)GetLastError()); } } } return res; } /* * Class: jogamp_nativewindow_windows_GDIUtil * Method: initIDs0 * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_jogamp_nativewindow_windows_GDIUtil_initIDs0 (JNIEnv *env, jclass gdiClazz) { if( NativewindowCommon_init(env) ) { jclass c = (*env)->FindClass(env, ClazzNamePoint); if(NULL==c) { NativewindowCommon_FatalError(env, "FatalError jogamp_nativewindow_windows_GDIUtil: can't find %s", ClazzNamePoint); } pointClz = (jclass)(*env)->NewGlobalRef(env, c); (*env)->DeleteLocalRef(env, c); if(NULL==pointClz) { NativewindowCommon_FatalError(env, "FatalError jogamp_nativewindow_windows_GDIUtil: can't use %s", ClazzNamePoint); } pointCstr = (*env)->GetMethodID(env, pointClz, ClazzAnyCstrName, ClazzNamePointCstrSignature); if(NULL==pointCstr) { NativewindowCommon_FatalError(env, "FatalError jogamp_nativewindow_windows_GDIUtil: can't fetch %s.%s %s", ClazzNamePoint, ClazzAnyCstrName, ClazzNamePointCstrSignature); } dumpStackID = (*env)->GetStaticMethodID(env, gdiClazz, "dumpStack", "()V"); if(NULL==dumpStackID) { NativewindowCommon_FatalError(env, "FatalError jogamp_nativewindow_windows_GDIUtil: can't get method dumpStack"); } } return JNI_TRUE; } static LRESULT CALLBACK DummyWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hWnd,uMsg,wParam,lParam); } /* * Class: jogamp_nativewindow_windows_GDIUtil * Method: getDummyWndProc0 * Signature: ()J */ JNIEXPORT jlong JNICALL Java_jogamp_nativewindow_windows_GDIUtil_getDummyWndProc0 (JNIEnv *env, jclass clazz) { return (jlong) (intptr_t) DummyWndProc; } /* * Class: jogamp_nativewindow_windows_GDIUtil * Method: GetRelativeLocation0 * Signature: (JJII)Ljavax/media/nativewindow/util/Point; */ JNIEXPORT jobject JNICALL Java_jogamp_nativewindow_windows_GDIUtil_GetRelativeLocation0 (JNIEnv *env, jclass unused, jlong jsrc_win, jlong jdest_win, jint src_x, jint src_y) { HWND src_win = (HWND) (intptr_t) jsrc_win; HWND dest_win = (HWND) (intptr_t) jdest_win; POINT dest = { src_x, src_y } ; int res; res = MapWindowPoints(src_win, dest_win, &dest, 1); DBG_PRINT("*** WindowsWindow: getRelativeLocation0: %p %d/%d -> %p %d/%d - ok: %d\n", (void*)src_win, src_x, src_y, (void*)dest_win, (int)dest.x, (int)dest.y, res); return (*env)->NewObject(env, pointClz, pointCstr, (jint)dest.x, (jint)dest.y); } /* * Class: jogamp_nativewindow_windows_GDIUtil * Method: IsChild0 */ JNIEXPORT jboolean JNICALL Java_jogamp_nativewindow_windows_GDIUtil_IsChild0 (JNIEnv *env, jclass unused, jlong jwin) { HWND hWnd = (HWND) (intptr_t) jwin; LONG style = GetWindowLong(hWnd, GWL_STYLE); BOOL bIsChild = 0 != (style & WS_CHILD) ; return bIsChild ? JNI_TRUE : JNI_FALSE; } /* * Class: jogamp_nativewindow_windows_GDIUtil * Method: IsUndecorated0 */ JNIEXPORT jboolean JNICALL Java_jogamp_nativewindow_windows_GDIUtil_IsUndecorated0 (JNIEnv *env, jclass unused, jlong jwin) { HWND hWnd = (HWND) (intptr_t) jwin; LONG style = GetWindowLong(hWnd, GWL_STYLE); BOOL bIsUndecorated = 0 != (style & (WS_CHILD|WS_POPUP)) ; return bIsUndecorated ? JNI_TRUE : JNI_FALSE; }