/* * MiniWCK for Windows. * $Id$ * * a minimalist implementation of the WCK interface, for Windows. * * History: * 2006-04-27 fl Created (based on code from PIL) * * Copyright (c) 2003-2006 by Fredrik Lundh. * * See the README file for information on usage and redistribution. */ #include "Python.h" #include "structmember.h" #include "_wckdraw.h" #define UNICODE #define WINDOWS_LEAN_AND_MEAN #include #ifndef WM_UNICHAR #define WM_UNICHAR 0x109 #endif #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL 0x20A #endif #ifndef UNICODE_NOCHAR #define UNICODE_NOCHAR 0xffff #endif #ifndef GET_WHEEL_DELTA_WPARAM #define GET_WHEEL_DELTA_WPARAM(wParam) ((short) HIWORD(wParam)) #endif #if _MSC_VER <= 1100 /* alphacanvas hacks for VC 5.0 */ typedef struct _BLENDFUNCTION { BYTE BlendOp; BYTE BlendFlags; BYTE SourceConstantAlpha; BYTE AlphaFormat; } BLENDFUNCTION; #endif #ifndef AC_SRC_OVER #define AC_SRC_OVER 0x00 #endif #ifndef AC_SRC_ALPHA #define AC_SRC_ALPHA 0x01 #endif #ifndef WS_EX_LAYERED #define WS_EX_LAYERED 0x80000 #endif #ifndef ULW_COLORKEY #define ULW_COLORKEY 0x1 #endif #ifndef ULW_ALPHA #define ULW_ALPHA 0x2 #endif #ifndef ULW_OPAQUE #define ULW_OPAQUE 0x4 #endif BOOL (WINAPI *WCKUpdateLayeredWindow)( HWND hwnd, HDC hdcDst, POINT* pptDst, SIZE* psize, HDC hdcSrc, POINT* pptSrc,COLORREF crKey, BLENDFUNCTION* pblend, DWORD dwFlags ); /* -------------------------------------------------------------------- */ /* event type definition (from uiToolkit) */ typedef struct { PyObject_HEAD char* type; int serial; int time; int x, y; int num; int delta; int state; int keycode; char* keysym; int char_; PyObject* widget; int _message; /* FIXME: rename */ int _wParam; long _lParam; PyObject* _status; } EventObject; staticforward PyTypeObject Event_Type; /* -------------------------------------------------------------------- */ static int serial = 0; static PyObject* event_new(int message, int wParam, long lParam) { EventObject* self; self = PyObject_NEW(EventObject, &Event_Type); if (self == NULL) return NULL; /* low-level event codes (for debugging) */ self->_message = message; self->_wParam = wParam; self->_lParam = lParam; self->type = "??"; self->time = GetTickCount(); self->serial = ++serial; self->x = self->y = self->num = self->delta = 0; self->char_ = 0; self->keycode = 0; self->keysym = "??"; Py_INCREF(Py_None); self->widget = Py_None; Py_INCREF(Py_None); self->_status = Py_None; switch (message) { case WM_MOUSEMOVE: self->type = "Motion"; goto mouse; case WM_MOUSEWHEEL: self->type = "MouseWheel"; self->delta = GET_WHEEL_DELTA_WPARAM(wParam); goto mouse; case WM_LBUTTONDOWN: case WM_LBUTTONUP: self->num = 1; goto mouse; case WM_MBUTTONDOWN: self->num = 3; goto mouse; case WM_RBUTTONDOWN: case WM_RBUTTONUP: self->num = 2; mouse: switch (message) { case WM_LBUTTONDOWN: self->type = "ButtonPress"; break; case WM_LBUTTONUP: self->type = "ButtonRelease"; break; case WM_MBUTTONDOWN: self->type = "ButtonPress"; break; case WM_MBUTTONUP: self->type = "ButtonRelease"; break; case WM_RBUTTONDOWN: self->type = "ButtonPress"; break; case WM_RBUTTONUP: self->type = "ButtonRelease"; break; } self->x = LOWORD(lParam); self->y = HIWORD(lParam); self->state = wParam; break; case WM_CHAR: case WM_UNICHAR: self->type = "KeyPress"; self->char_ = wParam; break; case WM_KEYDOWN: if (!(lParam & 0x40000000)) { self->type = "KeyPress"; self->keycode = wParam; } break; case WM_KEYUP: self->type = "KeyRelease"; self->keycode = wParam; break; case WM_SETFOCUS: self->type = "FocusIn"; break; case WM_KILLFOCUS: self->type = "FocusOut"; break; } return (PyObject*) self; } static void event_dealloc(EventObject* self) { Py_XDECREF(self->widget); Py_XDECREF(self->_status); PyMem_DEL(self); } #define EVENT_OFF(x) offsetof(EventObject, x) static struct memberlist event_members[] = { /* WCK members */ {"x", T_INT, EVENT_OFF(x), RO}, {"y", T_INT, EVENT_OFF(y), RO}, {"widget", T_OBJECT, EVENT_OFF(widget)}, {"delta", T_INT, EVENT_OFF(delta), RO}, {"type", T_STRING, EVENT_OFF(type), RO}, {"keycode", T_INT, EVENT_OFF(keycode), RO}, {"keysym", T_STRING, EVENT_OFF(keysym), RO}, {"num", T_INT, EVENT_OFF(num), RO}, {"serial", T_INT, EVENT_OFF(serial), RO}, {"state", T_INT, EVENT_OFF(state), RO}, {"time", T_INT, EVENT_OFF(time), RO}, /* windows members */ {"_message", T_INT, EVENT_OFF(_message), RO}, {"_wParam", T_INT, EVENT_OFF(_wParam), RO}, {"_lParam", T_LONG, EVENT_OFF(_lParam), RO}, {"_status", T_OBJECT, EVENT_OFF(_status)}, {NULL} }; static PyObject* event_getattr(EventObject* self, char* name) { if (!strcmp(name, "char")) { if (self->char_ >= 128) return PyUnicode_FromOrdinal(self->char_); else { char character[2]; character[0] = self->char_; character[1] = 0; return PyString_FromString(character); } } return PyMember_Get((char*)self, event_members, name); } static int event_setattr(EventObject* self, char* name, PyObject* value) { return PyMember_Set((char*)self, event_members, name, value); } static PyObject * event_repr(EventObject* self) { char buffer[300]; sprintf( buffer, "<%s Event at %lx>", self->type, (long) self ); return PyString_FromString(buffer); } statichere PyTypeObject Event_Type = { PyObject_HEAD_INIT(NULL) 0, "Event", sizeof(EventObject), 0, /* methods */ (destructor)event_dealloc, /* tp_dealloc */ NULL, /* tp_print */ (getattrfunc)event_getattr, /* tp_getattr */ (setattrfunc)event_setattr, /* tp_setattr */ NULL, /* tp_compare */ (reprfunc)event_repr /* tp_repr */ }; /* -------------------------------------------------------------------- */ static int mainloop = 0; static void callback_error(const char* handler) { PyObject* sys_stderr; sys_stderr = PySys_GetObject("stderr"); if (sys_stderr) { PyFile_WriteString("*** WCK: error in ", sys_stderr); PyFile_WriteString((char*) handler, sys_stderr); PyFile_WriteString(":\n", sys_stderr); } PyErr_Print(); PyErr_Clear(); } static LRESULT CALLBACK windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; PyObject* callback = NULL; PyObject* result; PyObject* event; PyThreadState* threadstate; PyThreadState* current_threadstate; HDC dc; RECT rect; LRESULT status = 0; /* set up threadstate for messages that calls back into python */ switch (message) { case WM_CREATE: mainloop++; break; case WM_DESTROY: mainloop--; /* fall through... */ case WM_PAINT: case WM_SIZE: case WM_CHAR: case WM_UNICHAR: case WM_KEYDOWN: case WM_KEYUP: case WM_MOUSEMOVE: case WM_MOUSEWHEEL: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_SETFOCUS: case WM_KILLFOCUS: case WM_NCHITTEST: case WM_TIMER: callback = (PyObject*) GetWindowLong(wnd, 0); if (callback) { threadstate = (PyThreadState*) GetWindowLong(wnd, sizeof(PyObject*)); current_threadstate = PyThreadState_Swap(NULL); PyEval_RestoreThread(threadstate); } else return DefWindowProc(wnd, message, wParam, lParam); } /* process message */ switch (message) { case WM_PAINT: /* redraw (part of) window. this generates a WCK-style damage/clear/repair cascade */ BeginPaint(wnd, &ps); dc = GetDC(wnd); GetClientRect(wnd, &rect); /* in screen coordinates */ result = PyObject_CallFunction( callback, "siiii", "damage", ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom ); if (result) Py_DECREF(result); else callback_error("window damage callback"); result = PyObject_CallFunction( callback, "siiiii", "clear", (int) dc, 0, 0, rect.right-rect.left, rect.bottom-rect.top ); if (result) Py_DECREF(result); else callback_error("window clear callback"); result = PyObject_CallFunction( callback, "siiiii", "repair", (int) dc, 0, 0, rect.right-rect.left, rect.bottom-rect.top ); if (result) Py_DECREF(result); else callback_error("window repair callback"); ReleaseDC(wnd, dc); EndPaint(wnd, &ps); break; case WM_SIZE: /* resize window */ result = PyObject_CallFunction( callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam) ); if (result) { InvalidateRect(wnd, NULL, 1); Py_DECREF(result); } else callback_error("window resize callback"); break; case WM_DESTROY: /* destroy window */ result = PyObject_CallFunction( callback, "s", "destroy" ); if (result) Py_DECREF(result); else callback_error("window destroy callback"); Py_DECREF(callback); break; case WM_UNICHAR: if (wParam == UNICODE_NOCHAR) { status = 1; /* tell windows we're willing to handle this */ break; } /* else fall through to the event handler */ case WM_CHAR: case WM_KEYDOWN: case WM_KEYUP: case WM_MOUSEMOVE: case WM_MOUSEWHEEL: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_SETFOCUS: case WM_KILLFOCUS: case WM_NCHITTEST: SetFocus(wnd); event = event_new(message, wParam, lParam); result = PyObject_CallFunction(callback, "sO", "event", event); Py_XDECREF(event); if (result) { Py_DECREF(result); } else { callback_error("widget event callback"); } /* check event status */ result = ((EventObject*) event)->_status; if (PyInt_Check(result)) status = PyInt_AsLong(result); else if (message == WM_NCHITTEST) status = DefWindowProc(wnd, message, wParam, lParam); break; case WM_TIMER: /* timer notification */ result = PyObject_CallFunction( callback, "si", "timer", wParam ); if (result) Py_DECREF(result); else callback_error("timer callback"); break; default: status = DefWindowProc(wnd, message, wParam, lParam); } if (callback) { /* restore thread state */ PyEval_SaveThread(); PyThreadState_Swap(threadstate); } return status; } static PyObject* WCK_CreateWindow(PyObject* self, PyObject* args, PyObject* kw) { HWND wnd; WNDCLASSW windowClass; PyObject* callback; PyUnicodeObject* title; int width = 0, height = 0; int layered = 0; static char* kwlist[] = { "", "title", "width", "height", "layered", NULL }; if (!PyArg_ParseTupleAndKeywords( args, kw, "OU|iii", kwlist, &callback, &title, &width, &height, &layered )) return NULL; if (width <= 0) width = CW_USEDEFAULT; if (height <= 0) height = CW_USEDEFAULT; /* register toplevel window class */ windowClass.style = CS_CLASSDC; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = sizeof(PyObject*) + sizeof(PyThreadState*); windowClass.hInstance = GetModuleHandle(NULL); windowClass.hbrBackground = NULL; windowClass.lpszMenuName = NULL; windowClass.lpszClassName = L"WCK_Window"; windowClass.lpfnWndProc = windowCallback; windowClass.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(1)); windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); /* CROSS? */ RegisterClassW(&windowClass); /* FIXME: check return status */ wnd = CreateWindowEx( 0, windowClass.lpszClassName, PyUnicode_AS_UNICODE(title), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, HWND_DESKTOP, NULL, NULL, NULL ); if (!wnd) { PyErr_SetString(PyExc_IOError, "failed to create window"); return NULL; } /* register window callback */ Py_INCREF(callback); SetWindowLong(wnd, 0, (LONG) callback); SetWindowLong(wnd, sizeof(callback), (LONG) PyThreadState_Get()); if (layered) SetWindowLong( wnd, GWL_EXSTYLE, GetWindowLong(wnd, GWL_EXSTYLE) | WS_EX_LAYERED ); Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL); SetForegroundWindow(wnd); /* to make sure it's visible */ Py_END_ALLOW_THREADS return PyLong_FromVoidPtr(wnd); } static PyObject* WCK_EventLoop(PyObject* self, PyObject* args) { MSG msg; Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } Py_END_ALLOW_THREADS Py_INCREF(Py_None); return Py_None; } static PyObject* wck_damage(PyObject* self, PyObject* args) { HWND wnd; RECT rect; PyObject* wnd_; int x0 = -1, y0 = -1, x1 = -1, y1 = -1; if (!PyArg_ParseTuple(args, "O|dddd", &wnd_, &x0, &y0, &x1, &y1)) return NULL; wnd = PyLong_AsVoidPtr(wnd_); if (x0 == -1 && y0 == -1 && x1 == -1 && y1 == -1) InvalidateRect(wnd, NULL, FALSE); else { rect.left = x0; rect.top = y0; rect.right = x1; rect.bottom = y1; InvalidateRect(wnd, &rect, FALSE); } Py_INCREF(Py_None); return Py_None; } static PyObject* wck_clear(PyObject* self, PyObject* args) { RECT rect; int dc_; int x0, y0, x1, y1; if (!PyArg_ParseTuple(args, "iiiii", &dc_, &x0, &y0, &x1, &y1)) return NULL; rect.left = x0; rect.top = y0; rect.right = x1; rect.bottom = y1; FillRect((HDC) dc_, &rect, (HBRUSH) (COLOR_BTNFACE+1)); Py_INCREF(Py_None); return Py_None; } static PyObject* alpha_update(PyObject* self, PyObject* args) { /* update window content */ HDC scrdc; HDC memdc; HBITMAP bitmap; HGDIOBJ old_bitmap; BITMAPINFO info; unsigned char *bits; BLENDFUNCTION blend; POINT pt; SIZE sz; long wnd; int xsize, ysize; unsigned char* data; int data_size; int alpha = -1; if (!PyArg_ParseTuple(args, "l(ii)s#|i:update", &wnd, &xsize, &ysize, &data, &data_size, &alpha)) return NULL; if (!WCKUpdateLayeredWindow) { PyErr_SetString( PyExc_IOError, "UpdateLayeredWindow not available on this platform" ); return NULL; } if (data_size != xsize * ysize * 4) { PyErr_SetString( PyExc_ValueError, "wrong number of bytes in image" ); return NULL; } memset(&info, 0, sizeof(BITMAPINFOHEADER)); info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); info.bmiHeader.biWidth = xsize; info.bmiHeader.biHeight = ysize; info.bmiHeader.biPlanes = 1; info.bmiHeader.biBitCount = 32; info.bmiHeader.biCompression = BI_RGB; scrdc = GetDC(NULL); memdc = CreateCompatibleDC(NULL); bitmap = CreateDIBSection(memdc, &info, DIB_RGB_COLORS, &bits, NULL, 0); old_bitmap = SelectObject(memdc, bitmap); memcpy(bits, data, data_size); if (alpha >= 0) { /* constant alpha */ blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = alpha; blend.AlphaFormat = 0; } else { /* per-pixel alpha */ blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = 255; blend.AlphaFormat = AC_SRC_ALPHA; } pt.x = pt.y = 0; sz.cx = xsize; sz.cy = ysize; WCKUpdateLayeredWindow( (HWND) wnd, NULL, NULL, &sz, memdc, &pt, 0, &blend, ULW_ALPHA ); DeleteObject(SelectObject(memdc, old_bitmap)); DeleteDC(memdc); Py_INCREF(Py_None); return Py_None; } static PyObject* alpha_move(PyObject* self, PyObject* args) { /* move window to new location */ POINT xy; long wnd; int x, y; if (!PyArg_ParseTuple(args, "lii:move", &wnd, &x, &y)) return NULL; if (!WCKUpdateLayeredWindow) { PyErr_SetString( PyExc_IOError, "UpdateLayeredWindow not available on this platform" ); return NULL; } xy.x = x; xy.y = y; WCKUpdateLayeredWindow( (HWND) wnd, NULL, &xy, NULL, NULL, NULL, 0, NULL, 0 ); Py_INCREF(Py_None); return Py_None; } /* -------------------------------------------------------------------- */ static PyObject* timer_set(PyObject* self, PyObject* args) { PyObject* wnd; int id; int delay; if (!PyArg_ParseTuple(args, "Oii", &wnd, &id, &delay)) return NULL; /* FIXME: check return value */ SetTimer((HWND) PyLong_AsVoidPtr(wnd), id, delay, NULL); Py_INCREF(Py_None); return Py_None; } static PyObject* timer_kill(PyObject* self, PyObject* args) { PyObject* wnd; int id; if (!PyArg_ParseTuple(args, "Oi", &wnd, &id)) return NULL; /* FIXME: check return value */ KillTimer((HWND) PyLong_AsVoidPtr(wnd), id); Py_INCREF(Py_None); return Py_None; } /* -------------------------------------------------------------------- */ static PyMethodDef functions[] = { /* GDI+ layer */ {"Graphics", (PyCFunction) wckdraw_graphics_dc, METH_VARARGS}, {"Pen", (PyCFunction) wckdraw_pen, METH_VARARGS|METH_KEYWORDS}, {"Brush", (PyCFunction) wckdraw_brush, METH_VARARGS|METH_KEYWORDS}, {"Font", (PyCFunction) wckdraw_font, METH_VARARGS|METH_KEYWORDS}, /* Window layer */ {"createwindow", (PyCFunction)WCK_CreateWindow, METH_VARARGS|METH_KEYWORDS}, {"eventloop", (PyCFunction)WCK_EventLoop, METH_VARARGS}, {"damage", (PyCFunction)wck_damage, METH_VARARGS}, {"clear", (PyCFunction)wck_clear, METH_VARARGS}, {"timer_set", (PyCFunction)timer_set, METH_VARARGS}, {"timer_kill", (PyCFunction)timer_kill, METH_VARARGS}, /* AlphaCanvas layer */ {"alpha_update", (PyCFunction)alpha_update, METH_VARARGS}, {"alpha_move", (PyCFunction)alpha_move, METH_VARARGS}, /* Image layer */ {"Image", (PyCFunction) wckdraw_image_new, METH_VARARGS}, {"image_new", (PyCFunction) wckdraw_image_new, METH_VARARGS}, {"image_open", (PyCFunction) wckdraw_image_open, METH_VARARGS}, {"image_draw", (PyCFunction) wckdraw_graphics, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; DL_EXPORT(void) init_wck(void) { HMODULE dll; Event_Type.ob_type = &PyType_Type; wckdraw_init(); Py_InitModule("_wck", functions); dll = LoadLibrary(L"user32"); WCKUpdateLayeredWindow = (void*) GetProcAddress(dll, "UpdateLayeredWindow"); }