#include "mungal4.h" #pragma hdrstop #include "l4plasmascreen.h" #include "l4app.h" namespace { const char plasmaWindowClass[] = "RPPlasmaScreen"; LRESULT CALLBACK PlasmaWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { PlasmaScreen *screen = (PlasmaScreen *) GetWindowLongPtrA(hwnd, GWLP_USERDATA); switch (message) { case WM_PAINT: if (screen != NULL) { // handled below through the friend-free public surface: // the window procedure only asks the screen to repaint. PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // PlasmaScreen stores what StretchDIBits needs in // window properties to keep the class decoupled. const BITMAPINFO *bmi = (const BITMAPINFO *) GetPropA(hwnd, "PlasmaBitmapInfo"); const void *pixels = GetPropA(hwnd, "PlasmaPixels"); RECT client; GetClientRect(hwnd, &client); if (bmi != NULL && pixels != NULL) { SetStretchBltMode(hdc, COLORONCOLOR); StretchDIBits( hdc, 0, 0, client.right, client.bottom, 0, 0, PlasmaScreen::plasmaWidth, PlasmaScreen::plasmaHeight, pixels, bmi, DIB_RGB_COLORS, SRCCOPY ); } EndPaint(hwnd, &ps); return 0; } break; case WM_CLOSE: // The glass is part of the cockpit; just hide it. ShowWindow(hwnd, SW_HIDE); return 0; case WM_ERASEBKGND: return 1; } return DefWindowProcA(hwnd, message, wParam, lParam); } } //######################################################################## //############################ PlasmaScreen ############################## //######################################################################## PlasmaScreen *PlasmaScreen::activeInstance = NULL; void PlasmaScreen::Position(void *parent, int x, int y, int client_width, int client_height) { if (activeInstance == NULL || activeInstance->window == NULL) { return; } HWND glass = (HWND) activeInstance->window; int window_w = client_width; int window_h = client_height; if (parent != NULL) { // fold the glass into the cockpit window as a chrome-less pane SetWindowLongPtrA(glass, GWL_STYLE, WS_CHILD | WS_VISIBLE); SetParent(glass, (HWND) parent); } else { DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; RECT bounds; bounds.left = 0; bounds.top = 0; bounds.right = client_width; bounds.bottom = client_height; AdjustWindowRect(&bounds, style, FALSE); window_w = bounds.right - bounds.left; window_h = bounds.bottom - bounds.top; } SetWindowPos( glass, NULL, x, y, window_w, window_h, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW ); } void PlasmaScreen::Hide() { if (activeInstance != NULL && activeInstance->window != NULL) { ShowWindow((HWND) activeInstance->window, SW_HIDE); } } PlasmaScreen::PlasmaScreen(): Video8BitBuffered(plasmaWidth, plasmaHeight) { Check_Pointer(this); window = NULL; bitmapInfo = NULL; scale = 4; const char *scale_string = getenv("L4PLASMASCALE"); if (scale_string != NULL) { int requested = atoi(scale_string); if (requested >= 1 && requested <= 16) { scale = requested; } } //--------------------------------------------------------------- // Build the 8bpp DIB header with the plasma-orange palette: // index 0 is the dark glass, everything else lights up. //--------------------------------------------------------------- BITMAPINFOHEADER *header = (BITMAPINFOHEADER *) new char[ sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]; memset(header, 0, sizeof(BITMAPINFOHEADER)); header->biSize = sizeof(BITMAPINFOHEADER); header->biWidth = plasmaWidth; header->biHeight = -plasmaHeight; // top-down, matching PixelMap8 header->biPlanes = 1; header->biBitCount = 8; header->biCompression = BI_RGB; header->biClrUsed = 256; RGBQUAD *palette = (RGBQUAD *)((char *) header + sizeof(BITMAPINFOHEADER)); for (int i = 0; i < 256; ++i) { if (i == 0) { palette[i].rgbRed = 24; palette[i].rgbGreen = 10; palette[i].rgbBlue = 4; } else { palette[i].rgbRed = 255; palette[i].rgbGreen = 144; palette[i].rgbBlue = 32; } palette[i].rgbReserved = 0; } bitmapInfo = header; CreateGlassWindow(); Tell("PlasmaScreen: on-screen plasma display, scale x" << scale << "\n"); } PlasmaScreen::~PlasmaScreen() { Check_Pointer(this); if (activeInstance == this) { activeInstance = NULL; } if (window != NULL) { RemovePropA((HWND) window, "PlasmaBitmapInfo"); RemovePropA((HWND) window, "PlasmaPixels"); DestroyWindow((HWND) window); window = NULL; } delete [] (char *) bitmapInfo; bitmapInfo = NULL; } Logical PlasmaScreen::TestInstance() const { return valid; } void PlasmaScreen::CreateGlassWindow() { HINSTANCE instance = GetModuleHandleA(NULL); static Logical class_registered = False; if (!class_registered) { class_registered = True; WNDCLASSA window_class; memset(&window_class, 0, sizeof(window_class)); window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.lpfnWndProc = PlasmaWndProc; window_class.hInstance = instance; window_class.hCursor = LoadCursor(NULL, IDC_ARROW); window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); window_class.lpszClassName = plasmaWindowClass; RegisterClassA(&window_class); } RECT bounds; bounds.left = 0; bounds.top = 0; bounds.right = plasmaWidth * scale; bounds.bottom = plasmaHeight * scale; AdjustWindowRect(&bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE); int window_w = bounds.right - bounds.left; int window_h = bounds.bottom - bounds.top; //--------------------------------------------------------------- // Default position: directly below the main view (the pod mounts // the glass adjacent to the displays). The main window sits at // (0,0) with the -res size; clamp to the work area in case the // main view fills the screen. L4PLASMAPOS=x,y overrides. //--------------------------------------------------------------- int x = 0; int y = (int) L4Application::GetScreenHeight(); const char *pos_string = getenv("L4PLASMAPOS"); if (pos_string != NULL) { int px = 0, py = 0; if (sscanf(pos_string, "%d,%d", &px, &py) == 2) { x = px; y = py; } } else { RECT work; if (SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0)) { if (y + window_h > work.bottom) { y = work.bottom - window_h; } if (y < 0) { y = 0; } } } window = CreateWindowExA( 0, plasmaWindowClass, "Plasma Display", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, x, y, window_w, window_h, NULL, NULL, instance, NULL ); if (window != NULL) { SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this); SetPropA((HWND) window, "PlasmaBitmapInfo", bitmapInfo); SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer); ShowWindow((HWND) window, SW_SHOWNOACTIVATE); activeInstance = this; } else { DEBUG_STREAM << "PlasmaScreen: window creation failed\n" << std::flush; } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Called from the gauge renderer's background task, same slot the serial // plasma used for streaming. Repaint when any line changed. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Logical PlasmaScreen::Update(Logical forceall) { Check(this); Logical dirty = forceall; for (int y = 0; y < plasmaHeight; ++y) { if (changedLine[y]) { changedLine[y] = 0; dirty = True; } } if (dirty && window != NULL) { // paint now - queued WM_PAINTs starve behind the game loop's // one-message-per-frame pump RedrawWindow((HWND) window, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); } return False; // 'done' - nothing left to stream } void PlasmaScreen::WaitForUpdate() { Check(this); }