/* vim:expandtab:ts=2 sw=2: */ /* Grafx2 - The Ultimate 256-color bitmap paint program Copyright 2018 Thomas Bernard Copyright 2008 Yves Rizoud Copyright 2007 Adrien Destugues Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) Grafx2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. Grafx2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grafx2; if not, see */ #include #include #include #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf #endif #include "screen.h" #include "errors.h" #include "windows.h" #include "input.h" #include "keyboard.h" #include "unicode.h" extern int Handle_special_key_press(void); extern int Release_control(int key_code, int modifier); extern int user_feedback_required; extern word Input_new_mouse_X; extern word Input_new_mouse_Y; extern byte Input_new_mouse_K; static HBITMAP Windows_DIB = NULL; static void *Windows_Screen = NULL; static int Windows_DIB_width = 0; static int Windows_DIB_height = 0; static HWND Win32_hwnd = NULL; static int Win32_Is_Fullscreen = 0; HWND GFX2_Get_Window_Handle() { return Win32_hwnd; } /// Blit our "framebuffer" bitmap to the Window. static void Win32_Repaint(HWND hwnd) { PAINTSTRUCT ps; HDC dc; HDC dc2; HBITMAP old_bmp; RECT rect; if (!GetUpdateRect(hwnd, &rect, FALSE)) return; //GFX2_Log(GFX2_DEBUG, "Repaint rect : (%d,%d)-(%d,%d)\n", rect.left, rect.top, rect.right, rect.bottom); dc = BeginPaint(hwnd, &ps); dc2 = CreateCompatibleDC(dc); old_bmp = (HBITMAP)SelectObject(dc2, Windows_DIB); if (!BitBlt(dc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, dc2, rect.left, rect.top, SRCCOPY)) GFX2_Log(GFX2_INFO, "BitBlt(dc, %d, %d, %d, %d, dc2, %d, %d, SRCCOPY) FAILED\n", rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, rect.left, rect.top); SelectObject(dc2, old_bmp); DeleteDC(dc2); EndPaint(hwnd, &ps); } /// WindowProc callback function static LRESULT CALLBACK Win32_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_MOVE: // Gives the client area coordinates GFX2_Log(GFX2_DEBUG, "WM_MOVE : (%hd,%hd)\n", (short)LOWORD(lParam), (short)HIWORD(lParam)); return 0; case WM_GETMINMAXINFO: // size or position is about to change { RECT rect; LPMINMAXINFO minmaxinfo = (LPMINMAXINFO)lParam; GFX2_Log(GFX2_DEBUG, "WM_GETMINMAXINFO : input ptMinTrackSize : %dx%d\n", minmaxinfo->ptMinTrackSize.x, minmaxinfo->ptMinTrackSize.y); rect.left = 0; rect.top = 0; rect.right = 320; rect.bottom = 200; // add the non client area overhead if(AdjustWindowRect(&rect, WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX, FALSE)) { minmaxinfo->ptMinTrackSize.x = rect.right - rect.left; minmaxinfo->ptMinTrackSize.y = rect.bottom - rect.top; GFX2_Log(GFX2_DEBUG, "WM_GETMINMAXINFO : return ptMinTrackSize : %dx%d\n", minmaxinfo->ptMinTrackSize.x, minmaxinfo->ptMinTrackSize.y); } } return 0; case WM_WINDOWPOSCHANGING: // window size, position, or place in the Z order is about to change { LPWINDOWPOS pos = (LPWINDOWPOS)lParam; GFX2_Log(GFX2_DEBUG, "WM_WINDOWPOSCHANGING : (%d,%d) %dx%d flags=%04x after=%x\n", pos->x, pos->y, pos->cx, pos->cy, pos->flags, pos->hwndInsertAfter); } break; case WM_WINDOWPOSCHANGED: { LPWINDOWPOS pos = (LPWINDOWPOS)lParam; GFX2_Log(GFX2_DEBUG, "WM_WINDOWPOSCHANGED : (%d,%d) %dx%d flags=%04x after=%x\n", pos->x, pos->y, pos->cx, pos->cy, pos->flags, pos->hwndInsertAfter); if (!Win32_Is_Fullscreen && !(pos->flags & SWP_NOMOVE) && !(pos->flags & SWP_NOACTIVATE)) { // Windows NT "Minimizes" windows by sending them to (-32000,-32000) if (!(pos->x == -32000 && pos->y == -32000)) { Config.Window_pos_x = pos->x; Config.Window_pos_y = pos->y; } } } break; // call DefWindowProc() in order to receive the WM_SIZE msg case WM_NCCREATE: // Sent before WM_CREATE { LPCREATESTRUCTA create = (LPCREATESTRUCTA)lParam; GFX2_Log(GFX2_DEBUG, "WM_NCCREATE : (%d,%d) %dx%d\n", create->x, create->y, create->cx, create->cy); } break; case WM_NCCALCSIZE: // Sent when the size and position of a window's client area must be calculated if(wParam) { //LPNCCALCSIZE_PARAMS p = (LPNCCALCSIZE_PARAMS)lParam; GFX2_Log(GFX2_DEBUG, "WM_NCCALCSIZE(request)\n"); } else { LPRECT rect = (LPRECT)lParam; GFX2_Log(GFX2_DEBUG, "WM_NCCALCSIZE(info): (%d,%d)-(%d,%d)\n", rect->left, rect->top, rect->right, rect->bottom); //return 0; } break; case WM_NCHITTEST: // send to test in which part of the windows the coordinates are break; case WM_NCPAINT: // The WM_NCPAINT message is sent to a window when its frame must be painted. break; case WM_NCACTIVATE: // nonclient area needs to be changed to indicate an active or inactive state. break; case WM_CREATE: break; case WM_ACTIVATE: GFX2_Log(GFX2_DEBUG, "WM_ACTIVATE : activated=%d minimized=%d Handle %08x\n", (int)LOWORD(wParam), (int)HIWORD(wParam), lParam); break; case WM_ACTIVATEAPP: GFX2_Log(GFX2_DEBUG, "WM_ACTIVATEAPP : activate=%d thread %08x\n", (int)wParam, lParam); break; case WM_SHOWWINDOW: // window is about to be hidden or shown GFX2_Log(GFX2_DEBUG, "WM_SHOWWINDOW : show=%d status=%d\n", (int)wParam, (int)lParam); break; case WM_SETFOCUS: // We gained keyboard focus break; case WM_KILLFOCUS: // We lost keyboard focus break; case WM_SIZE: GFX2_Log(GFX2_DEBUG, "WM_SIZE : %dx%d type=%d\n", LOWORD(lParam), HIWORD(lParam), wParam); if (wParam == SIZE_MINIMIZED) Window_state = GFX2_WINDOW_MINIMIZED; else { Resize_width = LOWORD(lParam); Resize_height = HIWORD(lParam); if (wParam == SIZE_MAXIMIZED) Window_state = GFX2_WINDOW_MAXIMIZED; else if (wParam == SIZE_RESTORED) Window_state = GFX2_WINDOW_STANDARD; } return 0; case WM_CLOSE: Quit_is_required = 1; user_feedback_required = 1; return 0; case WM_ERASEBKGND: // the background should be erased break; case WM_PAINT: Win32_Repaint(hwnd); return 0; case WM_SETCURSOR: if (LOWORD(lParam) == HTCLIENT) { SetCursor(NULL); return TRUE; } break; case WM_MOUSELEAVE: //ShowCursor(TRUE); return 0; //case WM_MOUSEENTER: //ShowCursor(FALSE); //return 0; case WM_NCMOUSEMOVE: // Mouse move in the non client area of the window break; case WM_MOUSEMOVE: { int x, y; x = GET_X_LPARAM(lParam) / Pixel_width; y = GET_Y_LPARAM(lParam) / Pixel_height; user_feedback_required = Move_cursor_with_constraints(x, y); } return 0; case WM_LBUTTONDOWN: SetCapture(hwnd); // capture mouse when the button is pressed Input_new_mouse_K |= 1; Handle_mouse_btn_change(); user_feedback_required = 1; return 0; case WM_LBUTTONUP: ReleaseCapture(); // Release mouse when the button is released Input_new_mouse_K &= ~1; Handle_mouse_btn_change(); user_feedback_required = 1; return 0; // WM_LBUTTONDBLCLK case WM_RBUTTONDOWN: SetCapture(hwnd); // capture mouse when the button is pressed Input_new_mouse_K |= 2; Handle_mouse_btn_change(); user_feedback_required = 1; return 0; case WM_RBUTTONUP: ReleaseCapture(); // Release mouse when the button is released Input_new_mouse_K &= ~2; Handle_mouse_btn_change(); user_feedback_required = 1; return 0; // WM_RBUTTONDBLCLK case WM_MBUTTONDOWN: Key = KEY_MOUSEMIDDLE|Get_Key_modifiers(); user_feedback_required = 1; return 0; case WM_MBUTTONUP: return 0; // WM_MBUTTONDBLCLK case WM_MOUSEWHEEL: { short delta = HIWORD(wParam); if (delta > 0) Key = KEY_MOUSEWHEELUP|Get_Key_modifiers(); else Key = KEY_MOUSEWHEELDOWN|Get_Key_modifiers(); } user_feedback_required = 1; return 0; #if (_WIN32_WINNT >= 0x0600) case WM_MOUSEHWHEEL: { short delta = HIWORD(wParam); if (delta > 0) Key = KEY_MOUSEWHEELRIGHT | Get_Key_modifiers(); else Key = KEY_MOUSEWHEELLEFT | Get_Key_modifiers(); } user_feedback_required = 1; return 0; #endif case WM_SYSKEYDOWN: // Sent when ALT is pressed case WM_KEYDOWN: // lParam & 0xffff => repeat count. (lParam >> 16) & 0x1ff => scancode // lParam & 0x20000000 : context : 0 for WM_KEYDOWN; 1 for WM_SYSKEYDOWN if ALT is pressed // lParam & 0x40000000 : previous key state (1 down, 0 up) // lParam & 0x80000000 : transition state. 0 for WM_KEYDOWN and WM_SYSKEYDOWN GFX2_Log(GFX2_DEBUG, "KEYDOWN wParam=%04x lParam=%08x\n", wParam, lParam); switch (wParam) { // Ignore isolated shift, alt, control and window keys // and numlock case VK_SHIFT: case VK_CONTROL: case VK_MENU: // ALT case VK_LWIN: case VK_RWIN: case VK_NUMLOCK: case 0xff: // ignore 0xff which is invalid but returned with some specific keys // such as laptop Fn+something combinaisons break; default: Key = wParam|Get_Key_modifiers(); Handle_special_key_press(); user_feedback_required = 1; } return 0; case WM_SYSKEYUP: case WM_KEYUP: { int mod = 0; switch (wParam) { case VK_SHIFT: mod = GFX2_MOD_SHIFT; break; case VK_CONTROL: mod = GFX2_MOD_CTRL; break; case VK_MENU: // ALT mod = GFX2_MOD_ALT; break; case VK_LWIN: case VK_RWIN: mod = GFX2_MOD_META; } Release_control(wParam, mod); } return 0; case WM_SYSCHAR: // Character key when ALT key is down GFX2_Log(GFX2_DEBUG, "WM_SYSCHAR : '%c' (0x%02x) lParam=%08lx\n", wParam, wParam, lParam); return 0; case WM_CHAR: Key_ANSI = Key_UNICODE = wParam; return 0; case WM_SYSDEADCHAR: GFX2_Log(GFX2_DEBUG, "WM_SYSDEADCHAR : '%c' (0x%02x) lParam=%08lx\n", wParam, wParam, lParam); return 0; case WM_DEADCHAR: GFX2_Log(GFX2_DEBUG, "WM_DEADCHAR : '%c' (0x%02x) lParam=%08lx\n", wParam, wParam, lParam); return 0; case WM_DROPFILES: { int file_count; HDROP hDrop = (HDROP)wParam; file_count = DragQueryFile(hDrop, (UINT)-1, NULL , 0); if (file_count > 0) { TCHAR LongDropFileName[MAX_PATH]; TCHAR ShortDropFileName[MAX_PATH]; if (DragQueryFile(hDrop, 0 , LongDropFileName ,(UINT) MAX_PATH)) { Drop_file_name_unicode = Unicode_strdup((word *)LongDropFileName); if (GetShortPathName(LongDropFileName, ShortDropFileName, MAX_PATH)) { Drop_file_name = (char *)malloc(lstrlen(ShortDropFileName) + 1); if (Drop_file_name != NULL) { int i; for (i = 0; ShortDropFileName[i] != 0; i++) Drop_file_name[i] = (char)ShortDropFileName[i]; Drop_file_name[i] = 0; } } } } } return 0; case WM_STYLECHANGING: // app can change the styles case WM_STYLECHANGED: // app can not change the styles // wParam : GWL_EXSTYLE (extended style) / GWL_STYLE // lParam : pointer to STYLESTRUC // An application should return zero if it processes this message. break; case WM_GETICON: // request icon GFX2_Log(GFX2_DEBUG, "WM_GETICON : type=%d dpi=%d\n", (int)wParam, (int)lParam); // wParam : ICON_BIG / ICON_SMALL / ICON_SMALL2 // we can return a custom HICON break; case WM_SYSCOMMAND: // Window menu command (formerly known as the system or control menu) // or maximize button, minimize button, restore button, or close button. break; case WM_ENTERSIZEMOVE: // enters the moving or sizing modal loop. break; case WM_EXITSIZEMOVE: // the moving or sizing modal loop has exited. break; #if(WINVER >= 0x0400) case WM_CAPTURECHANGED: // we lost the mouse capture to (HWND)lParam break; case WM_SIZING: // user is resizing the window { LPRECT rect = (LPRECT)lParam; GFX2_Log(GFX2_DEBUG, "WM_SIZING : (%d,%d)-(%d,%d)\n", rect->left, rect->top, rect->right, rect->bottom); } break; case WM_MOVING: // user is moving the window { LPRECT rect = (LPRECT)lParam; GFX2_Log(GFX2_DEBUG, "WM_MOVING : (%d,%d)-(%d,%d)\n", rect->left, rect->top, rect->right, rect->bottom); } break; case WM_IME_SETCONTEXT: // window is activated break; case WM_IME_NOTIFY: // change of IME function break; #endif #if(WINVER >= 0x0500) case WM_NCMOUSEHOVER: // the cursor hovers over the nonclient area of the window break; case WM_NCMOUSELEAVE: // the cursor leaves the nonclient area of the window break; #endif /* WINVER >= 0x0500 */ #if(_WIN32_WINNT >= 0x0600) case WM_DWMNCRENDERINGCHANGED: GFX2_Log(GFX2_DEBUG, "WM_DWMNCRENDERINGCHANGED : enabled=%d\n", (int)wParam); break; #endif /* WINVER >= 0x0600 */ default: GFX2_Log(GFX2_INFO, "Win32_WindowProc() unknown Message : 0x%04x wParam=%08x lParam=%08lx\n", uMsg, wParam, lParam); } return DefWindowProc(hwnd, uMsg, wParam, lParam); } int Init_Win32(HINSTANCE hInstance, HINSTANCE hPrevInstance) { WNDCLASS wc; (void)hPrevInstance; wc.style = 0; wc.lpfnWndProc = Win32_WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(100)); wc.hCursor = NULL; wc.hbrBackground = 0; wc.lpszMenuName = NULL; wc.lpszClassName = TEXT("grafx2"); if (!RegisterClass(&wc)) { Warning("RegisterClass failed\n"); Error(ERROR_INIT); return 0; } return 1; // OK } static int Video_AllocateDib(int width, int height) { BITMAPINFO *bi; BITMAP bm; HDC dc; if (Windows_DIB != NULL) { DeleteObject(Windows_DIB); Windows_DIB = NULL; Windows_Screen = NULL; } bi = (BITMAPINFO*)_alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256); memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256); bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi->bmiHeader.biWidth = width; bi->bmiHeader.biHeight = -height; bi->bmiHeader.biPlanes = 1; bi->bmiHeader.biBitCount = 8; bi->bmiHeader.biCompression = BI_RGB; dc = GetDC(NULL); Windows_DIB = CreateDIBSection(dc, bi, DIB_RGB_COLORS, &Windows_Screen, NULL, 0); if (Windows_DIB == NULL) { Warning("CreateDIBSection failed"); return -1; } ReleaseDC(NULL, dc); if (GetObject(Windows_DIB, sizeof(bm), &bm) > 0) { Windows_DIB_width = bm.bmWidthBytes; Windows_DIB_height = bm.bmHeight; } else { Windows_DIB_width = width; Windows_DIB_height = height; } return 0; } static void Win32_CreateWindow(int width, int height, int fullscreen) { DWORD style; RECT r; int x, y; if (fullscreen) { style = WS_POPUP; } else { style = WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; /* allow window to be resized */ style |= WS_THICKFRAME; style |= WS_MAXIMIZEBOX; } r.left = 0; r.top = 0; r.right = width; r.bottom = height; AdjustWindowRect(&r, style, FALSE); if (Config.Window_pos_x != 9999 && Config.Window_pos_y != 9999) { x = Config.Window_pos_x; y = Config.Window_pos_y; } else { x = y = CW_USEDEFAULT; } Win32_hwnd = CreateWindow(TEXT("grafx2"), TEXT("grafx2"), style, x, y, r.right - r.left, r.bottom - r.top, NULL, NULL, GetModuleHandle(NULL), NULL); if (Win32_hwnd == NULL) { Error(ERROR_INIT); return; } ShowWindow(Win32_hwnd, SW_SHOWNORMAL); } void GFX2_Set_mode(int *width, int *height, int fullscreen) { Win32_Is_Fullscreen = fullscreen; Video_AllocateDib(*width, *height); if (Win32_hwnd == NULL) Win32_CreateWindow(*width, *height, fullscreen); else { DWORD style = GetWindowLong(Win32_hwnd, GWL_STYLE); if (fullscreen) { style &= ~WS_OVERLAPPEDWINDOW; style |= WS_POPUP; SetWindowLong(Win32_hwnd, GWL_STYLE, style); SetWindowPos(Win32_hwnd, HWND_TOPMOST, 0, 0, *width, *height, SWP_FRAMECHANGED | SWP_NOCOPYBITS); } else if ((style & WS_POPUP) != 0) { RECT r; style &= ~WS_POPUP; style |= WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX; SetWindowLong(Win32_hwnd, GWL_STYLE, style); if (Config.Window_pos_x != 9999 && Config.Window_pos_y != 9999) { r.left = Config.Window_pos_x; r.top = Config.Window_pos_y; } else { r.left = 0; r.top = 0; } r.right = r.left + *width; r.bottom = r.top + *height; AdjustWindowRect(&r, style, FALSE); SetWindowPos(Win32_hwnd, HWND_TOPMOST, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED | SWP_NOCOPYBITS); } } } byte Get_Screen_pixel(int x, int y) { if (Windows_Screen == NULL) return 0; return *((byte *)Windows_Screen + x + y * Windows_DIB_width); } void Set_Screen_pixel(int x, int y, byte value) { if (Windows_Screen == NULL) return; *((byte *)Windows_Screen + x + y * Windows_DIB_width) = value; } byte* Get_Screen_pixel_ptr(int x, int y) { if (Windows_Screen == NULL) return NULL; return (byte *)Windows_Screen + x + y * Windows_DIB_width; } void Screen_FillRect(int x, int y, int w, int h, byte color) { int i; byte * ptr; if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x > Windows_DIB_width || y > Windows_DIB_height) return; if ((x + w) > Windows_DIB_width) w = Windows_DIB_width - x; if ((y + h) > Windows_DIB_height) h = Windows_DIB_height - y; if (w <= 0 || h <= 0) return; for (i = 0; i < h; i++) { ptr = Get_Screen_pixel_ptr(x, y + i); memset(ptr, color, w); } } void Update_rect(short x, short y, unsigned short width, unsigned short height) { if (width == 0 && height == 0) { // update whole window InvalidateRect(Win32_hwnd, NULL, FALSE/*TRUE*/); } else { RECT rect; rect.left = x * Pixel_width; rect.top = y * Pixel_height; rect.right = (x + width) * Pixel_width; rect.bottom = (y + height) * Pixel_height; InvalidateRect(Win32_hwnd, &rect, FALSE/*TRUE*/); } } void Flush_update(void) { } void Update_status_line(short char_pos, short width) { Update_rect((18+char_pos*8)*Menu_factor_X, Menu_status_Y, width*8*Menu_factor_X, 8*Menu_factor_Y); } int SetPalette(const T_Components * colors, int firstcolor, int ncolors) { int i; RGBQUAD rgb[256]; HDC dc; HDC dc2; HBITMAP old_bmp; for (i = 0; i < ncolors; i++) { rgb[i].rgbRed = colors[i].R; rgb[i].rgbGreen = colors[i].G; rgb[i].rgbBlue = colors[i].B; } dc = GetDC(Win32_hwnd); dc2 = CreateCompatibleDC(dc); old_bmp = SelectObject(dc2, Windows_DIB); SetDIBColorTable(dc2, firstcolor, ncolors, rgb); SelectObject(dc2, old_bmp); DeleteDC(dc2); ReleaseDC(Win32_hwnd, dc); InvalidateRect(Win32_hwnd, NULL, FALSE); // Refresh the whole window return 1; } void Clear_border(byte color) { (void)color; } volatile int Allow_colorcycling = 0; /// Activates or desactivates file drag-dropping in program window. void Allow_drag_and_drop(int flag) { DragAcceptFiles(GFX2_Get_Window_Handle(), flag?TRUE:FALSE); } void Define_icon(void) { // Do nothing because the icon is set in the window class // see Init_Win32() } void Set_mouse_position(void) { POINT pt; pt.x = Mouse_X * Pixel_width; pt.y = Mouse_Y * Pixel_height; if (!ClientToScreen(Win32_hwnd, &pt)) GFX2_Log(GFX2_WARNING, "ClientToScreen(%08x, %p) failed\n", Win32_hwnd, &pt); else { if (!SetCursorPos(pt.x, pt.y)) GFX2_Log(GFX2_WARNING, "SetCursorPos(%ld, %ld) failed\n", pt.x, pt.y); } }