| /* |
| * $Id: win32.c,v 1.38 2004/02/07 06:32:01 phil Exp $ |
| * Win32 support for XY display simulator |
| * Phil Budne <phil@ultimate.com> |
| * September 2003 |
| * Revised by Douglas A. Gwyn, 05 Feb. 2004 |
| */ |
| |
| /* |
| * Copyright (c) 2003-2004, Philip L. Budne |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Except as contained in this notice, the names of the authors shall |
| * not be used in advertising or otherwise to promote the sale, use or |
| * other dealings in this Software without prior written authorization |
| * from the authors. |
| */ |
| |
| /* use a thread to handle windows messages; */ |
| #define THREADS |
| |
| /* |
| * BUGS: |
| * Does not allow you to close display window; |
| * would need to tear down both system, and system independent data. |
| * |
| * now tries to handle PAINT message, as yet untested!! |
| */ |
| |
| #include <windows.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "ws.h" |
| #include "display.h" |
| |
| #ifndef PIX_SIZE |
| #define PIX_SIZE 1 |
| #endif |
| |
| #define APP_CLASS "XYAppClass" |
| #define APP_MENU "XYAppMenu" /* ?? */ |
| |
| /* |
| * light pen location |
| * see ws.h for full description |
| */ |
| int ws_lp_x = -1; |
| int ws_lp_y = -1; |
| |
| static HWND static_wh; |
| static HINSTANCE static_inst; |
| static int xpixels, ypixels; |
| static char *window_name; |
| static HBRUSH white_brush; |
| static HBRUSH black_brush; |
| #ifdef SWITCH_CURSORS |
| static HCURSOR cross, arrow; |
| #endif |
| |
| static __inline int |
| map_key(int k) |
| { |
| switch (k) { |
| case 186: return ';'; /* VK_OEM_1? */ |
| case 222: return '\''; /* VK_OEM_7? */ |
| } |
| return k; |
| } |
| |
| static void |
| keydown(int k) |
| { |
| display_keydown(map_key(k)); |
| } |
| |
| static void |
| keyup(int k) |
| { |
| display_keyup(map_key(k)); |
| } |
| |
| /* |
| * here on any button click, or if mouse dragged while a button down |
| */ |
| static void |
| mousepos(DWORD lp) |
| { |
| int x, y; |
| |
| x = LOWORD(lp); |
| y = HIWORD(lp); |
| |
| /* convert to display coordinates */ |
| #if PIX_SIZE > 1 |
| x /= PIX_SIZE; |
| y /= PIX_SIZE; |
| #endif |
| y = ypixels - 1 - y; |
| |
| /* if window has been stretched, can get out of range bits!! */ |
| if (x >= 0 && x < xpixels && y >= 0 && y < ypixels) { |
| /* checked by display_add_point() */ |
| ws_lp_x = x; |
| ws_lp_y = y; |
| } |
| } |
| |
| /* thoingggg!! "message for you sir!!!" */ |
| static LRESULT CALLBACK |
| patsy(HWND wh, UINT msg, WPARAM wp, LPARAM lp) /* "WndProc" */ |
| { |
| /* printf("msg %d\n", msg); */ |
| switch (msg) { |
| case WM_DESTROY: |
| PostQuitMessage(0); |
| return 0; |
| |
| case WM_MOUSEMOVE: |
| if (wp & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON)) { |
| #ifdef SWITCH_CURSORS |
| if (ws_lp_x == -1 && !display_tablet) |
| SetCursor(cross); |
| #endif |
| mousepos(lp); |
| } |
| #ifdef SWITCH_CURSORS |
| else if (ws_lp_x != -1 && !display_tablet) |
| SetCursor(arrow); |
| #endif |
| break; /* return?? */ |
| |
| case WM_LBUTTONDOWN: |
| display_lp_sw = 1; |
| case WM_MBUTTONDOWN: |
| case WM_RBUTTONDOWN: |
| #ifdef SWITCH_CURSORS |
| if (!display_tablet) |
| SetCursor(cross); |
| #endif |
| mousepos(lp); |
| break; /* return?? */ |
| |
| case WM_LBUTTONUP: |
| display_lp_sw = 0; |
| case WM_MBUTTONUP: |
| case WM_RBUTTONUP: |
| #ifdef SWITCH_CURSORS |
| if (!display_tablet) |
| SetCursor(arrow); |
| #endif |
| ws_lp_x = ws_lp_y = -1; |
| break; /* return?? */ |
| |
| case WM_KEYDOWN: |
| keydown(wp); |
| break; |
| |
| case WM_KEYUP: |
| keyup(wp); |
| break; |
| |
| case WM_PAINT: |
| display_repaint(); |
| break; /* return?? */ |
| } |
| return DefWindowProc(wh, msg, wp, lp); |
| } |
| |
| int |
| ws_poll(int *valp, int maxus) |
| { |
| #ifdef THREADS |
| /* msgthread handles window events; just delay simulator */ |
| if (maxus > 0) |
| Sleep((maxus+999)/1000); |
| #else |
| MSG msg; |
| DWORD start; |
| int maxms = (maxus + 999) / 1000; |
| |
| for (start = GetTickCount(); GetTickCount() - start < maxms; Sleep(1)) { |
| /* empty message queue without blocking */ |
| while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| } |
| #endif |
| return 1; |
| } |
| |
| /* called from non-threaded main program */ |
| int |
| ws_loop(void (*func)(void *), void *arg) |
| { |
| int val; |
| while (ws_poll(&val, 0)) |
| (*func)(arg); |
| return val; |
| } |
| |
| /* worker for display init */ |
| static void |
| ws_init2(void) { |
| WNDCLASS wc; |
| int h, w; |
| |
| #ifdef SWITCH_CURSORS |
| if (!display_tablet) { |
| arrow = LoadCursor(NULL, IDC_ARROW); |
| cross = LoadCursor(NULL, IDC_CROSS); |
| } |
| #endif |
| |
| black_brush = GetStockObject(BLACK_BRUSH); |
| white_brush = GetStockObject(WHITE_BRUSH); |
| |
| wc.lpszClassName = APP_CLASS; |
| wc.lpfnWndProc = patsy; |
| wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; |
| /* also CS_NOCLOSE? CS_SAVEBITS? */ |
| |
| wc.hInstance = static_inst = GetModuleHandleA(0); |
| wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); |
| #ifdef SWITCH_CURSORS |
| wc.hCursor = NULL; |
| #else |
| wc.hCursor = display_tablet ? NULL : LoadCursor(NULL, IDC_CROSS); |
| #endif |
| wc.hbrBackground = black_brush; |
| wc.lpszMenuName = APP_MENU; |
| wc.cbClsExtra = 0; |
| wc.cbWndExtra = 0; |
| /* WNDCLASSEX/RegisterClassEx include hIconSm (small icon) */ |
| RegisterClass(&wc); |
| |
| /* |
| * WS_OVERLAPPEDWINDOW=> |
| * WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, |
| * WS_MINIMIZEBOX, WS_MAXIMIZEBOX |
| * |
| * WS_CHILD (no menu bar), WS_POPUP (mutually exclusive) |
| */ |
| |
| /* empirical crocks to get entire screen; */ |
| w = (xpixels*PIX_SIZE)+6; |
| h = (ypixels*PIX_SIZE)+32; |
| /* XXX -- above values work with XP; Phil had +10,+30 */ |
| |
| static_wh = CreateWindow(APP_CLASS, /* registered class name */ |
| window_name, /* window name */ |
| WS_OVERLAPPED, /* style */ |
| CW_USEDEFAULT, CW_USEDEFAULT, /* X,Y */ |
| w, h, |
| NULL, /* HWND hWndParent */ |
| NULL, /* HMENU hMenu */ |
| static_inst, /* application instance */ |
| NULL); /* lpParam */ |
| |
| ShowWindow(static_wh, SW_SHOW); |
| UpdateWindow(static_wh); |
| } |
| |
| #ifdef THREADS |
| static volatile int init_done; |
| static DWORD msgthread_id; |
| |
| static DWORD WINAPI |
| msgthread(LPVOID arg) |
| { |
| MSG msg; |
| |
| ws_init2(); |
| |
| /* XXX use a mutex? */ |
| init_done = 1; |
| |
| while (GetMessage(&msg, NULL, 0, 0) > 0) { |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| return msg.wParam; |
| } |
| |
| static void |
| ws_thread_init(void) |
| { |
| HANDLE th = CreateThread(NULL, /* sec. attr. */ |
| 0, /* stack size */ |
| msgthread, |
| NULL, /* param */ |
| 0, /* flags */ |
| &msgthread_id); |
| CloseHandle(th); |
| |
| /* XXX use a mutex; don't wait forever!! */ |
| while (!init_done) |
| ; |
| } |
| #endif /* THREADS */ |
| |
| /* called from display layer on first display op */ |
| int |
| ws_init(char *name, int xp, int yp, int colors) |
| { |
| xpixels = xp; |
| ypixels = yp; |
| window_name = name; |
| |
| #ifdef THREADS |
| ws_thread_init(); |
| #else |
| ws_init2(); |
| #endif |
| return 1; /* XXX return errors!! */ |
| } |
| |
| void * |
| ws_color_rgb(int r, int g, int b) |
| { |
| /* XXX check for failure??? try GetNearestColor??? */ |
| return CreateSolidBrush(RGB(r/256, g/256, b/256)); |
| } |
| |
| void * |
| ws_color_black(void) |
| { |
| return black_brush; |
| } |
| |
| void * |
| ws_color_white(void) |
| { |
| return white_brush; |
| } |
| |
| void |
| ws_display_point(int x, int y, void *color) |
| { |
| HDC dc; |
| RECT r; |
| HBRUSH brush = color; |
| |
| if (x > xpixels || y > ypixels) |
| return; |
| |
| y = ypixels - 1 - y; /* invert y, top left origin */ |
| |
| /* top left corner */ |
| r.left = x*PIX_SIZE; |
| r.top = y*PIX_SIZE; |
| |
| /* bottom right corner, non-inclusive */ |
| r.right = (x+1)*PIX_SIZE; |
| r.bottom = (y+1)*PIX_SIZE; |
| |
| if (brush == NULL) |
| brush = black_brush; |
| |
| dc = GetDC(static_wh); |
| FillRect(dc, &r, brush); |
| ReleaseDC(static_wh, dc); |
| } |
| |
| void |
| ws_sync(void) { |
| /* noop */ |
| } |
| |
| void |
| ws_beep(void) { |
| #if 0 |
| /* play SystemDefault sound; does not work over terminal service */ |
| MessageBeep(MB_OK); |
| #else |
| /* works over terminal service? Plays default sound/beep on Win9x/ME */ |
| Beep(440, 500); /* Hz, ms. */ |
| #endif |
| } |
| |
| unsigned long |
| os_elapsed(void) |
| { |
| static int new; |
| unsigned long ret; |
| static DWORD t[2]; |
| |
| /* |
| * only returns milliseconds, but Sleep() |
| * only takes milliseconds. |
| * |
| * wraps after 49.7 days of uptime. |
| * DWORD is an unsigned long, so this should be OK |
| */ |
| t[new] = GetTickCount(); |
| if (t[!new] == 0) |
| ret = ~0L; /* +INF */ |
| else |
| ret = (t[new] - t[!new]) * 1000; |
| new = !new; /* Ecclesiastes III */ |
| return ret; |
| } |