/** * \defgroup winsimulator Simulation of the Borg API for the Win32 platform. */ /*@{*/ /** * This is a native Win32 port of the Borgware-2D API simulator. Although the * OpenGL based simulator is in fact platform independent, there are some * obstacles regarding Cygwin's OpenGL support. * * Earlier versions of Cygwin used to ship bindings to Win32's native OpenGL * libraries. Unfortunately some of those native components (GLUT in particular) * weren't maintained for years so it was decided to cease support for them. * * The reasons are explained in more detail at * http://cygwin.com/ml/cygwin/2012-05/msg00276.html * * The OpenGL bindings which are now shipped with Cygwin require a running * X-Server which I consider clumsy to use on a Windows platform (especially for * a small application like this simulator). So I decided to write a native * Win32 application to free Windows developers from the hassles of rolling out * a complete X11 setup. * * The native simulator feels like the OpenGL based one, with the exception that * you can't rotate the matrix (I'm using the plain GDI32 API for the graphics). * * @file winmain.c * @brief Simulator for the Win32 platform. * @author Christian Kroll */ #include #include #include #include "../config.h" #include "../display_loop.h" /** Number of bytes per row. */ #define LINEBYTES (((NUM_COLS - 1) / 8) + 1) /** The width (in pixels) of the margin around a LED. */ #define LED_MARGIN 1 /** The diameter (in pixels) of a LED. */ #define LED_DIAMETER 14 /** The extent of the whole LED including its margin. */ #define LED_EXTENT (2 * LED_MARGIN + LED_DIAMETER) /** Width of the canvas. */ #define WND_X_EXTENTS (NUM_COLS * LED_EXTENT) /** Height of the canvas. */ #define WND_Y_EXTENTS (NUM_ROWS * LED_EXTENT) /* string constants */ LPCSTR g_strWindowClass = "BorgSimulatorWindowClass"; LPCSTR g_strWindowTitle = "Borg Simulator"; LPCSTR g_strErr = "Error"; LPCSTR g_strErrTimerResolution = "Could not retrieve minimum timer resolution."; LPCSTR g_strErrRegisterWindow = "Could not register window class."; LPCSTR g_strErrCreateWindow = "Could not create window."; LPCSTR g_strErrCreateEvent = "Could not create wait event."; LPCSTR g_strErrCreateThread = "Could not create display loop thread."; LPCSTR g_strErrCreateUITimer = "Could not create UI Timer."; /** Minimum timer resolution the system is capable of. */ UINT g_uResolution; /** Event object for the multimedia timer (wait() function). */ HANDLE g_hWaitEvent; /** Fake port for simulating joystick input. */ volatile unsigned char fakeport; /** Flag which indicates if wait should jump to the menu if fire is pressed. */ volatile unsigned char waitForFire; /** The simulated frame buffer of the borg. */ volatile unsigned char pixmap[NUMPLANE][NUM_ROWS][LINEBYTES]; /** Jump buffer which leads directly the menu. */ extern jmp_buf newmode_jmpbuf; /* forward declaration of window message handler */ LRESULT CALLBACK simWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); /** * Creates a new window and makes it visible. * @param hInstance Handle of the instance where this window should belong to. * @param nCmdShow Flag for showing the window minimized, maximized etc. * @return TRUE if successful, otherwise FALSE. */ HWND simCreateWindow(HINSTANCE hInstance, int nCmdShow) { HWND hWnd = NULL; /* register window class for an window with a neat black background */ WNDCLASSA lpwc; lpwc.style = 0; lpwc.lpfnWndProc = simWndProc; lpwc.cbClsExtra = 0; lpwc.cbWndExtra = 0; lpwc.hInstance = hInstance; lpwc.hIcon = LoadIcon(NULL, IDI_WINLOGO); lpwc.hCursor = LoadCursor(NULL, IDC_ARROW); lpwc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); lpwc.lpszMenuName = NULL; lpwc.lpszClassName = g_strWindowClass; if (RegisterClassA(&lpwc) != 0) { /* ensure that the client area has the right proportions */ RECT rect = {0, 0, WND_X_EXTENTS * 1.5 - 1, WND_Y_EXTENTS * 1.5 - 1}; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW & ~(WS_OVERLAPPED), FALSE); /* create window and retrieve its handle */ hWnd = CreateWindow(g_strWindowClass, g_strWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, HWND_DESKTOP, NULL, hInstance, NULL); /* if window was created successfully, make it visible */ if (hWnd != NULL) { ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); } /* otherwise clean up*/ else { UnregisterClass(g_strWindowClass, hInstance); } } else { fprintf(stderr, g_strErrRegisterWindow); } return hWnd; } /** * Closes windows and unregisters its associated window class. * @param lphWnd Pointer to window handle. * @param hInstance Handle of the instance where this window belongs to. */ void simDestroyWindow(HWND hWnd, HINSTANCE hInstance) { DestroyWindow(hWnd); UnregisterClassA(g_strWindowClass, hInstance); } /** * Draws the LED matrix on the given device context. * @param hdc The device context where the LED matrix should be drawn on. */ void simDrawMatrix(HDC hdc) { /* color, pen and brush for drawing the LEDS */ COLORREF colorLed; /* RGB color for the pen and the brush */ HPEN hPen; /* pen for the border of the LEDs */ HGDIOBJ hPenOld; /* SelectObject swap space for the pen */ HBRUSH hBrushLed; /* brush for filling the LED circles */ HGDIOBJ hOldBrush; /* SelectObject swap space for the brush */ /* loop variables */ unsigned int c, p, x, y, absX; /* geometric values */ int left, right, top, bottom; /* lookup table for fast bit shifts of the value 0x01 */ static unsigned char const shl_map[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; /* clear background */ FloodFill(hdc, 0, 0, RGB(0, 0, 0)); /* go through every plane */ for (p = 0; p < NUMPLANE; ++p) { /* create and select red brush into device context */ colorLed = RGB((255.0 / NUMPLANE) * (p + 1), 0, 0); hBrushLed = CreateSolidBrush(colorLed); hOldBrush = SelectObject(hdc, hBrushLed); hPen = CreatePen(PS_INSIDEFRAME | PS_SOLID, 1, colorLed); hPenOld = SelectObject(hdc, hPen); /* translate pixmap into LEDs */ for (y = 0; y < NUM_ROWS; ++y) { for (c = 0; c < LINEBYTES; ++c) { for (x = 0; x < 8; ++x) { if (pixmap[p][y][c] & shl_map[x]) { /* eventually draw a LED, mirroring its coordinates */ absX = (c * 8 + x) * LED_EXTENT + LED_MARGIN; left = WND_X_EXTENTS - absX; right = WND_X_EXTENTS - absX - LED_DIAMETER + 1; top = y * LED_EXTENT + LED_MARGIN; bottom = top + LED_DIAMETER - 1; Ellipse(hdc, left, top, right, bottom); } } } } /* dispose old brush and pen */ DeleteObject(SelectObject(hdc, hOldBrush)); DeleteObject(SelectObject(hdc, hPenOld)); } } /** * Retrieves device context from given window, creates a compatible memory * device context for double buffering and hands that thing over to * simDrawMatrix(). * @param hWnd The window where the LED-Matrix should be displayed. */ void simDisplay(HWND hWnd) { RECT rect; HDC hdc; HDC hMemDc; HBITMAP hBmp; HBITMAP hOldBmp; /* retrieve window dimensions */ if (GetClientRect(hWnd, &rect)) { int const cx = rect.right; int const cy = rect.bottom; /* retrieve device context */ if ((hdc = GetDC(hWnd)) != NULL) { /* make window contents scalable */ SetMapMode(hdc, MM_ANISOTROPIC); SetWindowExtEx(hdc, WND_X_EXTENTS, WND_Y_EXTENTS, NULL); SetViewportExtEx(hdc, cx, cy, NULL); /* create memory device context for double buffering */ hMemDc = CreateCompatibleDC(hdc); if (hMemDc != NULL) { /* contents of the memory DC should be scaled as well */ SetMapMode(hMemDc, MM_ANISOTROPIC); SetWindowExtEx(hMemDc, WND_X_EXTENTS, WND_Y_EXTENTS, NULL); SetViewportExtEx(hMemDc, cx, cy, NULL); /* create a bitmap to be associated with the memory DC... */ hBmp = CreateCompatibleBitmap(hdc, cx, cy); if (hBmp != NULL) { /* ...and select that into that DC */ hOldBmp = (HBITMAP)SelectObject(hMemDc, hBmp); /* finally *sigh* draw the LED matrix */ simDrawMatrix(hMemDc); /* and blit that into the window DC */ BitBlt(hdc, 0, 0, cx, cy, hMemDc, 0, 0, SRCCOPY); /* clean up */ DeleteObject(SelectObject(hMemDc, hOldBmp)); } DeleteDC(hMemDc); } ReleaseDC(hWnd, hdc); } InvalidateRect(hWnd, &rect, FALSE); } } /** * Message handler for the main window. * @param hWnd The window whose messages should be processed. * @param msg The message fired from the operating system. * @param wParam First message parameter. * @param lParam Second message parameter. */ LRESULT CALLBACK simWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { LRESULT lResult = 0; PAINTSTRUCT ps; HDC hdc; LPMINMAXINFO lpminmax; /* minimum size of the window's client area */ RECT rectMin = {0, 0, WND_X_EXTENTS - 1, WND_Y_EXTENTS - 1}; switch (msg) { /* enforce minimum window size */ case WM_GETMINMAXINFO: /* minimum size applies to client area */ AdjustWindowRect(&rectMin, GetWindowLongA(hWnd, GWL_STYLE), GetMenu(hWnd) != NULL); /* actually set minimum and maximum size */ lpminmax = (LPMINMAXINFO)lParam; lpminmax->ptMinTrackSize.x = rectMin.right - rectMin.left; lpminmax->ptMinTrackSize.y = rectMin.bottom - rectMin.top; lpminmax->ptMaxTrackSize.x = GetSystemMetrics(SM_CXMAXTRACK); lpminmax->ptMaxTrackSize.y = GetSystemMetrics(SM_CYMAXTRACK); lpminmax->ptMaxSize.x = GetSystemMetrics(SM_CXMAXTRACK); lpminmax->ptMaxSize.y = GetSystemMetrics(SM_CYMAXTRACK); break; /* paint window contents */ case WM_PAINT: hdc = BeginPaint(hWnd, &ps); if (hdc != NULL) { simDisplay(hWnd); EndPaint(hWnd, &ps); } break; /* map key presses to fake joystick movements */ case WM_KEYDOWN: switch (wParam) { case VK_ESCAPE: /* quit simulator */ case 'Q': PostQuitMessage(0); break; case VK_SPACE: /* fire */ fakeport |= 0x01; break; case 'A': /* left */ fakeport |= 0x02; break; case 'D': /* right */ fakeport |= 0x04; break; case 'S': /* down */ fakeport |= 0x08; break; case 'W': /* up */ fakeport |= 0x10; break; default: lResult = DefWindowProcA(hWnd, msg, wParam, lParam); break; } break; /* map key releases to fake joystick movements */ case WM_KEYUP: switch (wParam) { case VK_SPACE: /* fire */ fakeport &= ~0x01; break; case 'A': /* left */ fakeport &= ~0x02; break; case 'D': /* right */ fakeport &= ~0x04; break; case 'S': /* down */ fakeport &= ~0x08; break; case 'W': /* up */ fakeport &= ~0x10; break; default: lResult = DefWindowProcA(hWnd, msg, wParam, lParam); break; } break; /* refresh the LED matrix every 40 ms */ case WM_TIMER: simDisplay(hWnd); UpdateWindow(hWnd); break; /* quit application */ case WM_DESTROY: PostQuitMessage(0); break; /* Windows' default handler */ default: lResult = DefWindowProcA(hWnd, msg, wParam, lParam); break; } return lResult; } /** * Entry point for starting the the display loop in a thread. * @param lpParam Free style arguments for the thread function (not used here). * @return Always zero. */ DWORD WINAPI simLoop(LPVOID lpParam) { display_loop(); return 0; } /** * Retrieves and enforces the minimum timer resolution of the current system. * @return Result of the multimedia timer operations. */ MMRESULT simSetMinimumTimerResolution() { TIMECAPS tc; MMRESULT mmresult; mmresult = timeGetDevCaps(&tc, sizeof(tc)); if (mmresult == TIMERR_NOERROR) { /* retrieve best resolution and configure timer services accordingly */ g_uResolution = min(max(tc.wPeriodMin, 0), tc.wPeriodMax); mmresult = timeBeginPeriod(g_uResolution); } return mmresult; } /** * Wait function which utilizes multimedia timers and thread synchronization * objects. Although this is much more complicated than calling the Sleep() * function, it is also much more precise. * @param ms The requested delay in milliseconds. * @param uResolution The minimum timer resolution the system is capable of. */ void wait(int ms) { MMRESULT mmTimerEventId; /* check if fire button is pressed (and if it is, jump to the menu) */ if (waitForFire) { if (fakeport & 0x01) { longjmp(newmode_jmpbuf, 43); } } /* retrieve a multimedia timer */ mmTimerEventId = timeSetEvent(ms, g_uResolution, g_hWaitEvent, 0, TIME_ONESHOT | TIME_CALLBACK_EVENT_SET); if (mmTimerEventId != 0) { /* now halt until that timer pulses our wait event object */ WaitForSingleObject(g_hWaitEvent, INFINITE); ResetEvent(g_hWaitEvent); /* relieve the timer from its duties */ timeKillEvent(mmTimerEventId); } } /** * Main function of the windows simulator. * @param hInstance Instance handle given by the operating system. * @param hPrevInstance This parameter has no meaning in Win32. * @param lpCmdLine Pointer to a null terminated command line string. * @param nCmdShow Flags for showing the window minimized, maximized and so on. * @return Exit code, always 0 here. */ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hWnd; MSG msg; HANDLE hLoopThread; UINT_PTR uTimerId; int nExitCode = 0; if ((hWnd = simCreateWindow(hInstance, nCmdShow)) != NULL) { /* retrieve minimum timer resolution */ if (simSetMinimumTimerResolution() == TIMERR_NOERROR) { /* event handle for multimedia timer (for the wait() function) */ g_hWaitEvent = CreateEventA(NULL, TRUE, FALSE, "Local\\WaitEvent"); if (g_hWaitEvent != NULL) { /* start the display loop thread */ hLoopThread = CreateThread(NULL, 0, simLoop, NULL, 0, NULL); if (hLoopThread != NULL) { SetThreadPriority(hLoopThread, THREAD_PRIORITY_TIME_CRITICAL); /* issue a UI timer message every 40 ms (roughly 25 fps) */ uTimerId = SetTimer(hWnd, 23, 40, NULL); if (uTimerId != 0) { /* standard Windows(R) message loop */ while (GetMessageA(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); } nExitCode = msg.wParam; KillTimer(hWnd, uTimerId); } else { fprintf(stderr, g_strErrCreateUITimer); } TerminateThread(hLoopThread, 0); } else { fprintf(stderr, g_strErrCreateThread); } /* relieve wait event object from its duties */ CloseHandle(g_hWaitEvent); } else { fprintf(stderr, g_strErrCreateEvent); } /* relieve timer resolution constraints */ timeEndPeriod(g_uResolution); } else { fprintf(stderr, g_strErrTimerResolution); } simDestroyWindow(hWnd, hInstance); } else { fprintf(stderr, g_strErrCreateWindow); } return nExitCode; } /*@}*/